WebAPI

【Web API 04-3】New API Controller

江崑成 2016/12/04 17:29:01
623







主題

Web API 04-3New API Controller

文章簡介

本篇透過我們之前在 04-1、04-2 準備好的類別,來建立一個 Product 的 Web API。

作者

江崑成(Vito)

版本/產出日期

V1.0/2016.12.02




1. 前言

前二篇將專案分為 Data Access Layer 以及 Business Service Layer 並整合了 URF Framework。


接著本篇就實際上透過我們之前準備好的類別,來建立一個 Product 的 Web API。

本篇也會套用 IoC/DI 的 Framework,進一步解除介面(IProductService) 對實作(ProductService) 的直接依賴,但本篇並不會詳細說明 IoC/DI 的相關觀念,想要瞭解的人可先參考下列連結。


【Software Architecture】IoC and DI


【ASP.NET】重構之路系列v4 – 簡單使用interface之『你也會IoC』


2. Create Async API Controller with Entity Framework

Step 1. 透過 NuGet 安裝下列 Packages:Service.Pattern、Repository.Pattern.Ef6。


Step 2. 將 WebApiDb.EntitiesWebApiDb.InterfaceWebApiDb.Service 加入專案參考當中。


Step 3. 在 Hello.WebAPI 專案按右鍵,新增 Controller。


Step 4. 選擇 Web API 2 Controller with actions, using Entity Framework,並點選「Add」。


Step 5. Model class 選擇在 Entities Project 下的 Product,Data Context class 則選擇 WebApiDbContext,Controller name 命名為 ProductsController 後,點選「Add」。


Step 6. ProductsController 建立後,我們可以看到符合 HTTP 動詞開頭的方法名稱,因為 Web API 預設就支援透過 HTTP 動詞來進行 Routing,同時 HTTP 動詞也代表 CRUD 操作,我們也僅需要直接使用,並將商業邏輯的部分寫到對應的方法中即可。



ProductsController 預設程式碼:


using System;

using System.Collections.Generic;

using System.Data;

using System.Data.Entity;

using System.Data.Entity.Infrastructure;

using System.Linq;

using System.Net;

using System.Net.Http;

using System.Threading.Tasks;

using System.Web.Http;

using System.Web.Http.Description;

using WebApiDb.Entities;


namespace Hello.WebAPI.Controllers

{

    public class ProductsController : ApiController

    {

        private WebApiDbContext db = new WebApiDbContext();


        // GET: api/Products

        public IQueryable<Product> GetProducts()

        {

            return db.Products;

        }


        // GET: api/Products/5

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> GetProduct(int id)

        {

            Product product = await db.Products.FindAsync(id);

            if (product == null)

            {

                return NotFound();

            }


            return Ok(product);

        }


        // PUT: api/Products/5

        [ResponseType(typeof(void))]

        public async Task<IHttpActionResult> PutProduct(int id, Product product)

        {

            if (!ModelState.IsValid)

            {

                return BadRequest(ModelState);

            }


            if (id != product.ProductId)

            {

                return BadRequest();

            }


            db.Entry(product).State = EntityState.Modified;


            try

            {

                await db.SaveChangesAsync();

            }

            catch (DbUpdateConcurrencyException)

            {

                if (!ProductExists(id))

                {

                    return NotFound();

                }

                else

                {

                    throw;

                }

            }


            return StatusCode(HttpStatusCode.NoContent);

        }


        // POST: api/Products

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> PostProduct(Product product)

        {

            if (!ModelState.IsValid)

            {

                return BadRequest(ModelState);

            }


            db.Products.Add(product);

            await db.SaveChangesAsync();


            return CreatedAtRoute("DefaultApi"

                        new { id = product.ProductId }, product);

        }


        // DELETE: api/Products/5

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> DeleteProduct(int id)

        {

            Product product = await db.Products.FindAsync(id);

            if (product == null)

            {

                return NotFound();

            }


            db.Products.Remove(product);

            await db.SaveChangesAsync();


            return Ok(product);

        }


        protected override void Dispose(bool disposing)

        {

            if (disposing)

            {

                db.Dispose();

            }

            base.Dispose(disposing);

        }


        private bool ProductExists(int id)

        {

            return db.Products.Count(e => e.ProductId == id) > 0;

        }

    }

}


3. Test Web API(誤)

POST api/Products


可以看到 Response 的 Status 是回覆 201 Created,代表程式有被執行並且沒有錯誤,但 Database 裡卻沒有正常建立資料。


補充:這是因為專案有整合 URF Framework,所以 Web API 裡的 db.SaveChangesAsync 的行為會與預設有些不同,接下來我們要將預設產生的程式,修改為整合 URF Framework 讓資料可以正常新增。


Request :


Response :


Database:


4. API Controller Integrate URF Framework

接下來就輪到 Business Service Layer 登場,預設的 API Controller 已經幫我們套用了 Entity Framework 的程式碼,現在要開始置換在前幾篇文章中,事先準備好的 Service(此範例為 IProductService)。


ProductsController

修改後的程式碼如下:(請自行比較前後差異)

using Repository.Pattern.Infrastructure;

using Repository.Pattern.UnitOfWork;

using System;

using System.Collections.Generic;

using System.Data;

using System.Data.Entity;

using System.Data.Entity.Infrastructure;

using System.Linq;

using System.Net;

using System.Net.Http;

using System.Threading.Tasks;

using System.Web.Http;

using System.Web.Http.Description;

using WebApiDb.Entities;

using WebApiDb.Interface;


namespace Hello.WebAPI.Controllers

{

    public class ProductsController : ApiController

    {

        private readonly IUnitOfWorkAsync unitOfWorkAsync;

        private readonly IProductService productService;


        public ProductsController(

            IUnitOfWorkAsync unitOfWorkAsync,

            IProductService productService)

        {

            this.unitOfWorkAsync = unitOfWorkAsync;

            this.productService = productService;

        }


        // GET: api/Products

        public IQueryable<Product> GetProducts()

        {

            return this.productService.Queryable();

        }


        // GET: api/Products/5

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> GetProduct(int id)

        {

            Product product = await this.productService.FindAsync(id);

            if (product == null)

            {

                return NotFound();

            }


            return Ok(product);

        }


        // PUT: api/Products/5

        [ResponseType(typeof(void))]

        public async Task<IHttpActionResult> PutProduct(int id, Product product)

        {

            if (!ModelState.IsValid)

            {

                return BadRequest(ModelState);

            }


            if (id != product.ProductId)

            {

                return BadRequest();

            }


            this.productService.Update(product);


            try

            {

                await this.unitOfWorkAsync.SaveChangesAsync();

            }

            catch (DbUpdateConcurrencyException)

            {

                if (!ProductExists(id))

                {

                    return NotFound();

                }

                else

                {

                    throw;

                }

            }


            return StatusCode(HttpStatusCode.NoContent);

        }


        // POST: api/Products

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> PostProduct(Product product)

        {

            if (!ModelState.IsValid)

            {

                return BadRequest(ModelState);

            }


            this.productService.Insert(product);


            try

            {

                await this.unitOfWorkAsync.SaveChangesAsync();

            }

            catch (DbUpdateException)

            {

                if (ProductExists(product.ProductId))

                {

                    return Conflict();

                }

                throw;

            }


            return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);

        }


        // DELETE: api/Products/5

        [ResponseType(typeof(Product))]

        public async Task<IHttpActionResult> DeleteProduct(int id)

        {

            Product product = await this.productService.FindAsync(id);


            if (product == null)

            {

                return NotFound();

            }


            this.productService.Delete(product);

            await this.unitOfWorkAsync.SaveChangesAsync();


            return Ok(product);

        }


        protected override void Dispose(bool disposing)

        {

            if (disposing)

            {

                this.unitOfWorkAsync.Dispose();

            }

            base.Dispose(disposing);

        }


        private bool ProductExists(int id)

        {

            return this.productService.Query(e => e.ProductId == id).Select().Any();

        }

    }

}


IoC/DI Framework(Unity)

本系列文章中使用的是 Unity 這個 Framework,當然 IoC/DI 還有很多種優秀的 Framework,若想要瞭解就請自行搜尋囉。


Step 1. 透過 NuGet 安裝下列 Packages:Unity.MVCUnity.AspNet.WebApi


Step 2. 安裝完成後,在 APP_Start 資料夾下會多出三個檔案:



Step 3. 修改 UnityConfig.cs 中的 RegisterTypes 方法,註冊介面以及對應的實體型別為何。

修改後的程式碼如下:(請自行比較前後差異)

public static void RegisterTypes(IUnityContainer container)

{

    // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.

    // container.LoadConfiguration();


    // TODO: Register your types here

    container

        .RegisterType<IDataContextAsync, WebApiDbContext>(new PerRequestLifetimeManager())

        .RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager())

        .RegisterType<IRepositoryAsync<Product>, Repository<Product>>()

        .RegisterType<IProductService, ProductService>();

}


小結


5. Test Web API

POST api/Products


Request :


Response:


Database:


DELETE api/Products/6


Database - Before:


Request:


Response:


Database - After:


GET api/Products/


Request:


Response:


GET api/Products/5


Request:


Response:


6. 參考來源

文章內容的敘述如有錯誤及觀念不正確,請不吝嗇指教,如有侵權內容也請您與我反應。感謝您~


HTTP Verbs: POST, PUT PATCH 的應用


簡明 RESTful API 設計要點


【Software Architecture】IoC and DI


【ASP.NET】重構之路系列v4 – 簡單使用interface之『你也會IoC』

江崑成