zoukankan      html  css  js  c++  java
  • OData services入门使用ASP.NET Web API描述

    ODate 是一种应用层协议,设计它的目的在于提供一组通过HTTP的交互操作。除了提供一些基本的操作(像增删改查),也提供了一些高级的操作类似过滤数据和实体的导航。

    下面的文章我将用ODate 提供给ASP.NET Web API 的功能来建立一个小服务。

    ODate

    你现在可能在想为什么你的web apps需要另外的协议。JSON难道不是很简单吗?XML services 不够好?嗯,事实上,ODate扩展了上述的协议但是不是取代他们。他可以被XML(ATOM)或者JSON取代但是ODate的重要在于它符合REST原则。在某种意义上,它建立在'简单'的REST HTTP 服务上,并且有着清晰的目标——简化和标准化我们操作和查询数据的方式。如果你过去在给你的REST服务创建搜索、过滤、或者分页API的时候感觉很麻烦,那么ODate将是一个不错的选择。

    一些ODate查询语法的规则:

    • Entity set – /Artists
    • Entity by id – /Artists(1)
    • Sorting – /Artists?$orderby=Name
    • Filtering – /Artists?$filter=Name eq 'Gridlock'

    上面的这些只是冰山一角。

    概念介绍的差不多了,让我开始项目吧。很幸运,ASP.NET Web API 可以帮我们很方便的创建ODate。

    创建项目

    首先我们需要创建一个ASP.NET Web API项目。我们是不需要MVC的相关的东西(视图,js库,等)。

    ODate的功能是由一个独立的程序集提供的。请注意,现在这个程序集还是在预览版,最新的官方发布版本是0.3 RC (this blogpost 点击链接参看项目信息,这个是英文的哦)。

    很不幸,使用最新的ODataLib程序集还有一些方法(例如:过滤)没有实现,不过不用担心最新的更新将会解决这个问题。因为没有必要学习过时的API,我们将使用最新的发布版本在http://www.myget.org/F/aspnetwebstacknightly/,使用nuget。如果你不会使用nuget,可以看看这里here

    一旦你获取了nightly build nuget source。可以使用Manage NuGet Packages安装最新的Web API OData 包,确保你选择'Include Prerelease'在上面的下拉框中,如下图所示。

    另外需要注意的是Web API OData 还在开发阶段,还有一些功能还不支持。不过基本功能已经完成。

    数据模型

    我们需要一些简单的模型去操作,使用Entity Framework 和 SQL CE 4,但是Web API's OData也支持其他的数据库和持久化技术。

    CREATE TABLE [Album]
    (
        [AlbumId] INT NOT NULL IDENTITY,
        [Title] NVARCHAR(160) NOT NULL,
        [ArtistId] INT NOT NULL,
        [GenreId] INT NOT NULL,
        [ReleaseDate] DATETIME,
        CONSTRAINT [PK_Album] PRIMARY KEY  ([AlbumId])
    );
     
    CREATE TABLE [Artist]
    (
        [ArtistId] INT NOT NULL IDENTITY,
        [Name] NVARCHAR(120),
        CONSTRAINT [PK_Artist] PRIMARY KEY  ([ArtistId])
    );
     
    CREATE TABLE [Genre]
    (
        [GenreId] INT NOT NULL IDENTITY,
        [Name] NVARCHAR(120),
        [Description] NVARCHAR(1020),
        CONSTRAINT [PK_Genre] PRIMARY KEY  ([GenreId])
    );
     
    ALTER TABLE [Album] ADD CONSTRAINT [FK_AlbumArtistId]
        FOREIGN KEY ([ArtistId]) REFERENCES [Artist] ([ArtistId])
          ON DELETE NO ACTION ON UPDATE NO ACTION;
     
    CREATE INDEX [IFK_AlbumArtistId] ON [Album] ([ArtistId]);
     
    ALTER TABLE [Album] ADD CONSTRAINT [FK_AlbumGenreId]
        FOREIGN KEY ([GenreId]) REFERENCES [Genre] ([GenreId])
          ON DELETE NO ACTION ON UPDATE NO ACTION;
     
    CREATE INDEX [IFK_AlbumGenreId] ON [Album] ([GenreId]);

    你可以创建一个新的SQL CE数据库在App_Data文件夹下面,使用内置的环境去执行SQL代码。请注意执行SQL只能一行一行执行,不支持一下子执行多次语句。一旦数据库表结构完成了我们可以用VS2012向导生成Entity Data Model。

    最后我们将得到一个DbContext类去进行数据操作。

    $metadata endpoint 和 IEdmModel

    正如我之前提到的,ODate标准定义了一个特别的metadata endpoint,它包含一个定义了实体集,关系,实体类型和操作的文档。这些保证了ODate是自描述的,能够让客户端去生成表示服务端类型的客户端代码,简化了服务的访问方式。Metadata endpoint应该在/$metadata下可用。如果你熟悉SOAP服务,你可以把ODate和它类比一下。

    GET http://services.odata.org/Northwind/Northwind.svc/$metadata

    Metadata文档使用ODate通用架构定义(OData Common Schema Definition Language (CSDL))。很幸运,ASP.NET Web API可以把$metadata endpoint直接暴露给我们,只要我们的模型继承IEdmModel

    public class ModelBuilder
    {
        public IEdmModel Build()
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            modelBuilder.EntitySet<Album>("Albums");
            modelBuilder.EntitySet<Artist>("Artists");
            modelBuilder.EntitySet<Genre>("Genres");
     
            return modelBuilder.GetEdmModel();
        }
    }
    public IEdmModel BuildExplicitly()
    {
        ODataModelBuilder modelBuilder = new ODataModelBuilder();
        EntitySetConfiguration<Genre> genres = modelBuilder.EntitySet<Genre>("Genres");
        EntityTypeConfiguration<Genre> genre = genres.EntityType;
        genre.HasKey(g => g.GenreId);
        genre.Property(g => g.Name);
        genre.Property(g => g.Description);
     
        //(...)
     
        return modelBuilder.GetEdmModel();
    }

    使用ODate

    Microsoft.AspNet.WebApi.OData提供可一系列的类扩展了Web API

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var modelBuilder = new ModelBuilder();
            IEdmModel model = modelBuilder.Build();
            config.Routes.MapODataRoute("OData", null, model);
            config.EnableQuerySupport();
        }
    }

    这些代码(Global.asax.cs)做了两件事情:

    1. 通过路由注册我们的模型表示方法-
    2. 使查询可用

    现在我们的服务应该自动知道怎么去处理OData ~/$metadata 请求。很酷,不是吗?

    <edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
      <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
        <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" Namespace="Piotr.ODataWebApiService.Service.Models">
          <EntityType Name="Album">...</EntityType>
          <EntityType Name="Artist">...</EntityType>
          <EntityType Name="Genre">...</EntityType>
          <Association
            Name="Piotr_ODataWebApiService_Service_Models_Album_Artist_Piotr_ODataWebApiService_Service_Models_Artist_ArtistPartner">...</Association>
          <Association Name="Piotr_ODataWebApiService_Service_Models_Album_Genre_Piotr_ODataWebApiService_Service_Models_Genre_GenrePartner">...</Association>
          <Association Name="Piotr_ODataWebApiService_Service_Models_Artist_Albums_Piotr_ODataWebApiService_Service_Models_Album_AlbumsPartner">...</Association>
          <Association Name="Piotr_ODataWebApiService_Service_Models_Genre_Albums_Piotr_ODataWebApiService_Service_Models_Album_AlbumsPartner">...</Association>
        </Schema>
        <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" Namespace="Default">...</Schema>
      </edmx:DataServices>
    </edmx:Edmx>

    控制器

    现在是时候去检测一下我们的成果了。我们应该添加一个暴露ODate资源的控制器。正如你将看到的,这和一个平常的CRUD控制器很不一样。暴露一个ODate实体非常容易。

    [ODataRouting]
    [ODataFormatting]
    public class ArtistsController : ApiController
    {
        private AlbumsContext db = new AlbumsContext();
     
        // GET /Artists
        // GET /Artists?$filter=startswith(Name,'Grid')
        [Queryable]
        public IQueryable<Artist> Get()
        {
            return db.Artists;
        }        
     
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
    [ODataRouting]
    [ODataFormatting]
    public class ArtistsController : ApiController
    {
        private AlbumsContext _db = new AlbumsContext();
     
        // GET /Artists
        // GET /Artists?$filter=startswith(Name,'Grid')
        [Queryable]
        public IQueryable<Artist> Get()
        {
            return _db.Artists;
        }
     
        // GET /Artists(2)
        public HttpResponseMessage Get([FromODataUri]int id)
        {
            Artist artist = _db.Artists.SingleOrDefault(b => b.ArtistId == id);
            if (artist == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
     
            return Request.CreateResponse(HttpStatusCode.OK, artist);
        }
     
        public HttpResponseMessage Put([FromODataUri] int id, Artist artist)
        {
            if (!_db.Artists.Any(a => a.ArtistId == id))
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
            //overwrite any existing id, as url is more explicit
            artist.ArtistId = id;
            _db.Entry(artist).State = EntityState.Modified;
     
            try
            {
                _db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
     
            return Request.CreateResponse(HttpStatusCode.NoContent);
        }
     
        public HttpResponseMessage Post(Artist artist)
        {
            var odataPath = Request.GetODataPath();
            if (odataPath == null)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                    "ODataPath not present in the request.");
            }
     
            var entitySetPathSegment
                = odataPath.Segments.FirstOrDefault() as EntitySetPathSegment;
     
            if (entitySetPathSegment == null)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                    "ODataPath does not start with entity set path segment");
            }
     
            Artist addedArtist = _db.Artists.Add(artist);
            _db.SaveChanges();
            var response = Request
                .CreateResponse(HttpStatusCode.Created, addedArtist);
     
            response.Headers.Location = new Uri(Url.ODataLink(
                                  entitySetPathSegment,
                                  new KeyValuePathSegment(ODataUriUtils
                                .ConvertToUriLiteral(addedArtist.ArtistId
                                , ODataVersion.V3))));
            return response;
        }
     
        public HttpResponseMessage Patch([FromODataUri] int id,
            Delta<Artist> artistPatch)
        {
            Artist artist = _db.Artists
                .SingleOrDefault(p => p.ArtistId == id);
            if (artist == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
     
            artistPatch.Patch(artist);
            _db.SaveChanges();
     
            return Request.CreateResponse(HttpStatusCode.NoContent);
        }
     
        public HttpResponseMessage Delete([FromODataUri] int id)
        {
            Artist artist = _db.Artists.Find(id);
            if (artist == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
     
            _db.Artists.Remove(artist);
     
            _db.SaveChanges();
            return Request.CreateResponse(HttpStatusCode.Accepted);
        }
     
        protected override void Dispose(bool disposing)
        {
            _db.Dispose();
            base.Dispose(disposing);
        }
    }

    测试这个服务

    我将使用Fiddler去测试这个服务。

    注意 Content-Type: application/json这个头。应该加一个新的类型。如果我们想要更新实体的一部分,如下图

    现在这个类别为id=3将更新描述。

    最后,我们进行一个文章实体排序操作如下图,http://localhost:2537/Artists?$orderby=Name&$inlinecount=allpages

    正如你以上看到的,我们没有写任何一个特别的逻辑去支持这些功能,全部都由框架来提供的,当然如果你愿意,也可以自己写控制器不限于ODate指定的CRUD操作。

    ODate毫无疑问是一个有趣的协议。我感觉它更像一个加强的REST服务。

    本文的源代码在bitbucket

    译后语:

    原文地址:http://www.piotrwalat.net/getting-started-with-odata-services-in-asp-net-web-api/

    OData services作为一种最新的协议,将来可能会大规模使用,可能就没有未来。但是关注一点新的技术总没什么害处吧,万一以后你的公司用了,你可以说一句"貌似我以前看过一点",与时俱进嘛。

    翻译中遇到的一些好玩,好用的句型:

    a tip of an iceberg冰山一角

    reap the reward 获得奖励

    on steroids 加强的,这个单词词典的翻译是"类固醇",太坑爹,这里完全不是这个意思,后来看了很多例句才体会出加强这个意思。

  • 相关阅读:
    x5开源库后续知识点
    仿抖音上下滑动分页视频
    Sp效率分析和理解
    ARCGIS 数据格式
    arcEngine开发之activeView.PartialRefresh(译)
    arcEngine开发之查询的相关接口
    arcEngine开发之查看属性表
    arcEngine开发之根据点坐标创建Shp图层
    arcEngine开发之加载栅格数据
    arcEngine开发之IMap、ILayer、IFeatureLayer和IFeatureClass关系
  • 原文地址:https://www.cnblogs.com/muyoushui/p/2878844.html
Copyright © 2011-2022 走看看