使用ASP.NET Web API 2創建OData v4 終結點


開放數據協議(Open Data Protocol【簡稱OData】)是用於Web的數據訪問協議。OData提供了一種對數據集進行CRUD操作(Create,Read,Update,Delete)的統一方式。
Asp.Net Web API支持該協議的v3 和v4版,甚至可以創建一個和v3終結點並排運行的v4終結點。
該博文演示了如何創建支持CRUD操作的OData v4終結點。

用到的軟件版本

  • Web API 2
  • OData v4
  • VS 2013 Update 5
  • EF6
  • .Net 4.5.2

創建VS項目

在VS中創建一個新的Asp.Net Web應用項目,命名為“PersonsService”,如下圖:

然后繼續看下圖:

安裝OData Nuget包

打開Nuget包管理器控制台,輸入以下命令:

Install-Package Microsoft.AspNet.Odata

該命令會安裝最新版本的OData Nuget 包。

添加Model類

Model類是一個表示應用中的數據實體的對象。

在解決方案資源管理器中的Models文件夾下,創建一個Person類:

按照慣例,model類應該放在Models文件夾下,但是在你自己的項目中可以不這么做。

下面是我的Person類的代碼:

namespace PersonsService.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool Gender { get; set; }
        public string UserName { get; set; }
    }
}

啟用Entity Framework

這篇博客,我們使用EF的Code First模式來創建數據庫。

Web API OData不要求一定得是EF。只要數據訪問層可以將數據庫實體轉換成model,使用任何數據訪問層都可以。

首先,安裝EF的Nuget包。在包管理器控制台中使用下面的命令:

Install-Package EntityFramework

打開Web.config文件,在configuration元素中添加下面的connectionStrings節點:

  <connectionStrings>
    <add name="PersonsContext" connectionString="Server=.;Database=PersonsDB;Integrated Security=True" providerName="System.Data.SqlClient"/>
  </connectionStrings>

接下來,在Models文件夾下添加一個PersonsContext類:

using System.Data.Entity;

namespace PersonsService.Models
{
    public class PersonsContext:DbContext
    {
        public PersonsContext()
            : base("name=PersonsContext")
        {
        }

        public DbSet<Person> Persons { get; set; }
    }
}

在構造函數中,"name=PersonsContext"指定了連接字符串的命名。

配置OData終結點

打開App_Start/WebApiConfig.cs文件,配置下面的新代碼(刪除自動生成的代碼):

using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using PersonsService.Models;

namespace PersonsService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {

            //新代碼
            ODataModelBuilder builder=new ODataConventionModelBuilder();
            builder.EntitySet<Person>("Persons");
            config.MapODataServiceRoute(
                routeName:"odata",
                routePrefix:"odata",
                model:builder.GetEdmModel()
                );
        }
    }
}


上面的代碼做了兩件事:

  • 創建了一個實體數據模型(Entity Data Model【簡稱EDM】)。
  • 添加了一個路由。

EDM是一個抽象的數據模型。EDM用於創建服務元數據文檔。ODataConventionModelBuilder類使用默認的命名規范創建了一個EDM。這種方式需要寫的代碼最少。如果你想更多地控制EDM,那么你可以使用 ODataModelBuilder類來創建EDM類,這樣做就要顯式添加屬性,鍵和導航屬性。

路由(route)會告訴Web API如何將HTTP請求路由到終結點。調用MapODataServiceRoute 擴展方法可以創建一個OData v4路由。

如果你的應用有了多個OData終結點,那么要為每個終結點創建一個單獨的路由,給每個路由一個唯一的路由名和前綴(prefix)。

添加OData控制器

控制器是處理HTTP請求的一個類。在OData應用中,應該為每個實體集創建一個單獨的控制器。而在這篇博客中,我們只要為Person實體創建一個控制器就行了。

在Controllers文件夾下添加一個控制器,如下:

在Controllers文件夾上右鍵添加控制器,接下來選中上圖的選擇,因為還沒有針對OData v4的基架。

默認已經幫我們生成了下面的代碼,基本上我們不需要做什么了,CRUD全都有了,呵呵:

using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.OData;
using PersonsService.Models;

namespace PersonsService.Controllers
{
    /*
    在為此控制器添加路由之前,WebApiConfig 類可能要求你做出其他更改。請適當地將這些語句合並到 WebApiConfig 類的 Register 方法中。請注意 OData URL 區分大小寫。

    using System.Web.Http.OData.Builder;
    using System.Web.Http.OData.Extensions;
    using PersonsService.Models;
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<Person>("Persons");
    config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
    */
    public class PersonsController : ODataController
    {
        private PersonsContext db = new PersonsContext();

        // GET: odata/Persons
        [EnableQuery]
        public IQueryable<Person> GetPersons()
        {
            return db.Persons;
        }

        // GET: odata/Persons(5)
        [EnableQuery]
        public SingleResult<Person> GetPerson([FromODataUri] int key)
        {
            return SingleResult.Create(db.Persons.Where(person => person.Id == key));
        }

        // PUT: odata/Persons(5)
        public IHttpActionResult Put([FromODataUri] int key, Delta<Person> patch)
        {
            Validate(patch.GetEntity());

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            Person person = db.Persons.Find(key);
            if (person == null)
            {
                return NotFound();
            }

            patch.Put(person);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PersonExists(key))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return Updated(person);
        }

        // POST: odata/Persons
        public IHttpActionResult Post(Person person)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Persons.Add(person);
            db.SaveChanges();

            return Created(person);
        }

        // PATCH: odata/Persons(5)
        [AcceptVerbs("PATCH", "MERGE")]
        public IHttpActionResult Patch([FromODataUri] int key, Delta<Person> patch)
        {
            Validate(patch.GetEntity());

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            Person person = db.Persons.Find(key);
            if (person == null)
            {
                return NotFound();
            }

            patch.Patch(person);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PersonExists(key))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return Updated(person);
        }

        // DELETE: odata/Persons(5)
        public IHttpActionResult Delete([FromODataUri] int key)
        {
            Person person = db.Persons.Find(key);
            if (person == null)
            {
                return NotFound();
            }

            db.Persons.Remove(person);
            db.SaveChanges();

            return StatusCode(HttpStatusCode.NoContent);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool PersonExists(int key)
        {
            return db.Persons.Count(e => e.Id == key) > 0;
        }
    }
}


該控制器借助EF,使用PersonsContext類來訪問數據庫。注意控制器重寫了Dispose方法來釋放 PersonsContext

生成數據庫

通過Code First的Migration生成數據庫,然后填充數據。關於如何使用CodeFirst生成數據庫不是本節的重點,所以這里一筆帶過。下面是我生成的數據庫已經填充的數據:

查詢實體集

自動生成的查詢操作如下:

 // GET: odata/Persons
        [EnableQuery]
        public IQueryable<Person> GetPersons()
        {
            return db.Persons;
        }

        // GET: odata/Persons(5)
        [EnableQuery]
        public SingleResult<Person> GetPerson([FromODataUri] int key)
        {
            return SingleResult.Create(db.Persons.Where(person => person.Id == key));
        }

無參數的GetPersons()方法會返回整個Person表的集合。
GetPerson([FromODataUri] int key)方法會返回指定Id的Person。

[EnableQuery]特性允許客戶端使用查詢選項(如$filter,$sort和$page)修改查詢。

操作演示

新增實體

允許客戶端將一個新的Person實體添加到數據庫中:

// POST: odata/Persons
        public IHttpActionResult Post(Person person)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Persons.Add(person);
            db.SaveChanges();

            return Created(person);
        }

更新實體

OData支持兩種不同語義更新實體,包括PATCH和PUT。

  • PATCH執行一個部分更新,客戶端只識別要更新的屬性。
  • PUT會替換整個實體。

PUT的劣勢在於客戶端必須發送實體的所有屬性,包括沒有改變的值。
OData說明書陳述了PATCH是首選。

下面是生成的代碼:

// PATCH: odata/Persons(5)
        [AcceptVerbs("PATCH", "MERGE")]
        public IHttpActionResult Patch([FromODataUri] int key, Delta<Person> patch)
        {
            Validate(patch.GetEntity());

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            Person person = db.Persons.Find(key);
            if (person == null)
            {
                return NotFound();
            }

            patch.Patch(person);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PersonExists(key))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return Updated(person);
        }


在PATCH中,控制器使用了Delta 類型來跟蹤改變。

刪除實體

允許客戶端從數據庫刪除一個Person:

// DELETE: odata/Persons(5)
        public IHttpActionResult Delete([FromODataUri] int key)
        {
            Person person = db.Persons.Find(key);
            if (person == null)
            {
                return NotFound();
            }

            db.Persons.Remove(person);
            db.SaveChanges();

            return StatusCode(HttpStatusCode.NoContent);
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM