日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

asp编程工具_使用ASP.NET Core构建RESTful API的技术指南

發(fā)布時(shí)間:2024/9/3 asp.net 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 asp编程工具_使用ASP.NET Core构建RESTful API的技术指南 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

譯者薦語(yǔ):利用周末的時(shí)間,本人拜讀了長(zhǎng)沙.NET技術(shù)社區(qū)翻譯的技術(shù)文章《微軟RESTFul API指南》,打算按照步驟寫(xiě)一個(gè)完整的教程,后來(lái)無(wú)意中看到了這篇文章,與我要寫(xiě)的主題有不少相似之處,特意翻譯下來(lái)。

原文來(lái)自互聯(lián)網(wǎng),由長(zhǎng)沙DotNET技術(shù)社區(qū)【鄒溪源】翻譯。如譯文侵犯您的版權(quán),請(qǐng)聯(lián)系小編,小編將在24小時(shí)內(nèi)刪除。

前方高能。本文共有兩萬(wàn)字。

一步一步的構(gòu)建整潔、可維護(hù)的RESTful APIs

總覽

RESTful不是一個(gè)新名詞。它是一種架構(gòu)風(fēng)格,這種架構(gòu)風(fēng)格使用Web服務(wù)從客戶端應(yīng)用程序接收數(shù)據(jù)和向客戶端應(yīng)用程序發(fā)送數(shù)據(jù)。其目標(biāo)是集中不同客戶端應(yīng)用程序?qū)⑹褂玫臄?shù)據(jù)。

選擇正確的工具來(lái)編寫(xiě)RESTful服務(wù)至關(guān)重要,因?yàn)槲覀冃枰P(guān)注可伸縮性,維護(hù),文檔以及所有其他相關(guān)方面。在ASP.NET?Core為我們提供了一個(gè)功能強(qiáng)大、易于使用的API,使用這些API將很好的實(shí)現(xiàn)這個(gè)目標(biāo)。

在本文中,我將向您展示如何使用ASP.NET Core框架為“幾乎”現(xiàn)實(shí)世界的場(chǎng)景編寫(xiě)結(jié)構(gòu)良好的RESTful API。我將詳細(xì)介紹常見(jiàn)的模式和策略以簡(jiǎn)化開(kāi)發(fā)過(guò)程。

我還將向您展示如何集成通用框架和庫(kù),例如Entity Framework Core和AutoMapper,以提供必要的功能。

先決條件

我希望您了解面向?qū)ο蟮木幊谈拍睢?/p>

即使我將介紹C#編程語(yǔ)言的許多細(xì)節(jié),我還是建議您具有該主題的基本知識(shí)。

我還假設(shè)您知道什么是REST,HTTP協(xié)議如何工作,什么是API端點(diǎn)以及什么是JSON。這是關(guān)于此主題的出色的入門(mén)教程。最后,您需要了解關(guān)系數(shù)據(jù)庫(kù)的工作原理。

要與我一起編碼,您將必須安裝.NET Core 2.2以及Postman(我將用來(lái)測(cè)試API的工具)。我建議您使用諸如Visual Studio Code之類的代碼編輯器來(lái)開(kāi)發(fā)API。選擇您喜歡的代碼編輯器。如果選擇Visual Studio Code作為您的代碼編輯器,建議您安裝C#擴(kuò)展以更好地突出顯示代碼。

您可以在本文末尾找到該API的Github的鏈接,以檢查最終結(jié)果。

范圍

讓我們?yōu)橐患页芯帉?xiě)一個(gè)虛構(gòu)的Web API。假設(shè)我們必須實(shí)現(xiàn)以下范圍:

  • 創(chuàng)建一個(gè)RESTful服務(wù),該服務(wù)允許客戶端應(yīng)用程序管理超市的產(chǎn)品目錄。它需要公開(kāi)端點(diǎn)以創(chuàng)建,讀取,編輯和刪除產(chǎn)品類別,例如乳制品和化妝品,還需要管理這些類別的產(chǎn)品。

  • 對(duì)于類別,我們需要存儲(chǔ)其名稱。對(duì)于產(chǎn)品,我們需要存儲(chǔ)其名稱,度量單位(例如,按重量測(cè)量的產(chǎn)品為KG),包裝中的數(shù)量(例如,如果一包餅干是10,則為10)及其各自的類別。

為了簡(jiǎn)化示例,我將不處理庫(kù)存產(chǎn)品,產(chǎn)品運(yùn)輸,安全性和任何其他功能。這個(gè)范圍足以向您展示ASP.NET Core的工作方式。

要開(kāi)發(fā)此服務(wù),我們基本上需要兩個(gè)API 端點(diǎn)(譯者注:指控制器):一個(gè)用于管理類別,一個(gè)用于管理產(chǎn)品。在JSON通訊方面,我們可以認(rèn)為響應(yīng)如下:

123456789API endpoint:?/api/categoriesJSON Response (for GET requests):{ [ { "id": 1, "name": "Fruits and Vegetables" }, { "id": 2, "name": "Breads" }, … // Other categories ]}
1234567891011121314151617API endpoint:?/api/productsJSON Response (for GET requests):{ [ { "id": 1, "name": "Sugar", "quantityInPackage": 1, "unitOfMeasurement": "KG" "category": { "id": 3, "name": "Sugar" } }, … // Other products ]}

讓我們開(kāi)始編寫(xiě)應(yīng)用程序。

第1步-創(chuàng)建API

首先,我們必須為Web服務(wù)創(chuàng)建文件夾結(jié)構(gòu),然后我們必須使用.NET CLI工具來(lái)構(gòu)建基本的Web API。打開(kāi)終端或命令提示符(取決于您使用的操作系統(tǒng)),并依次鍵入以下命令:

12345mkdir src/Supermarket.APIcd src/Supermarket.APIdotnet new webapi

前兩個(gè)命令只是為API創(chuàng)建一個(gè)新目錄,然后將當(dāng)前位置更改為新文件夾。最后一個(gè)遵循Web API模板生成一個(gè)新項(xiàng)目,這是我們正在開(kāi)發(fā)的應(yīng)用程序。您可以閱讀有關(guān)這些命令和其他項(xiàng)目模板的更多信息,并可以通過(guò)檢查此鏈接來(lái)生成其他項(xiàng)目模板。
現(xiàn)在,新目錄將具有以下結(jié)構(gòu):

項(xiàng)目結(jié)構(gòu)

結(jié)構(gòu)概述

ASP.NET Core應(yīng)用程序由在類中配置的一組中間件(應(yīng)用程序流水線中的小塊應(yīng)用程序,用于處理請(qǐng)求和響應(yīng))組成Startup。如果您以前已經(jīng)使用過(guò)Express.js之類的框架,那么這個(gè)概念對(duì)您來(lái)說(shuō)并不是什么新鮮事物。

123456789101112131415161718192021222324252627282930313233343536public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } }

當(dāng)應(yīng)用程序啟動(dòng)時(shí),將調(diào)用類中的Main** **方法Program。它使用啟動(dòng)配置創(chuàng)建默認(rèn)的Web主機(jī),通過(guò)HTTP通過(guò)特定端口(默認(rèn)情況下,HTTP為5000,HTTPS為5001)公開(kāi)應(yīng)用程序。

123456789101112131415namespace Supermarket.API { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup(); } }

看一下文件夾中的ValuesController類Controllers。它公開(kāi)了API通過(guò)路由接收請(qǐng)求時(shí)將調(diào)用的方法/api/values。

12345678910111213141516171819202122232425262728293031323334353637383940[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public ActionResult Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } }

如果您不了解此代碼的某些部分,請(qǐng)不要擔(dān)心。在開(kāi)發(fā)必要的API端點(diǎn)時(shí),我將詳細(xì)介紹每一個(gè)?,F(xiàn)在,只需刪除此類,因?yàn)槲覀儾粫?huì)使用它。

第2步-創(chuàng)建領(lǐng)域模型

我將應(yīng)用一些設(shè)計(jì)概念,以使應(yīng)用程序簡(jiǎn)單易維護(hù)。

編寫(xiě)可以由您自己理解和維護(hù)的代碼并不難,但是您必須牢記您將成為團(tuán)隊(duì)的一部分。如果您不注意如何編寫(xiě)代碼,那么結(jié)果將是一個(gè)龐然大物,這將使您和您的團(tuán)隊(duì)成員頭痛不已。聽(tīng)起來(lái)很極端吧?但是相信我,這就是事實(shí)。

衡量好代碼的標(biāo)準(zhǔn)是WTF的頻率。原圖來(lái)自smitty42,發(fā)表于filckr。該圖遵循CC-BY-2.0。

在Supermarket.API目錄中,創(chuàng)建一個(gè)名為的新文件夾Domain。在新的領(lǐng)域文件夾中,創(chuàng)建另一個(gè)名為的文件夾Models。我們必須添加到此文件夾的第一個(gè)模型是Category。最初,它將是一個(gè)簡(jiǎn)單的Plain Old CLR Object(POCO)類。這意味著該類將僅具有描述其基本信息的屬性。

123456789101112using System.Collections.Generic; namespace Supermarket.API.Domain.Models { public class Category { public int Id { get; set; } public string Name { get; set; } public IList Products { get; set; } = new List(); } }

該類具有一個(gè)Id**?屬性(用于標(biāo)識(shí)類別)和一個(gè)Name屬性。以及一個(gè)Products?屬性。最后一個(gè)屬性將由Entity Framework Core使用**,大多數(shù)ASP.NET Core應(yīng)用程序使用ORM將數(shù)據(jù)持久化到數(shù)據(jù)庫(kù)中,以映射類別和產(chǎn)品之間的關(guān)系。由于類別具有許多相關(guān)產(chǎn)品,因此在面向?qū)ο蟮木幊谭矫嬉簿哂泻侠淼乃季S能力。
我們還必須創(chuàng)建產(chǎn)品模型。在同一文件夾中,添加一個(gè)新Product類。

1234567891011121314namespace Supermarket.API.Domain.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public short QuantityInPackage { get; set; } public EUnitOfMeasurement UnitOfMeasurement { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } } }

該產(chǎn)品還具有ID和名稱的屬性。屬性QuantityInPackage,它告訴我們一包中有多少個(gè)產(chǎn)品單位(請(qǐng)記住應(yīng)用范圍的餅干示例)和一個(gè)UnitOfMeasurement**?屬性,這是表示一個(gè)枚舉類型,它表示可能的度量單位的枚舉。最后兩個(gè)屬性,CategoryId?**和Category將由ORM用于映射的產(chǎn)品和類別之間的關(guān)系。它表明一種產(chǎn)品只有一個(gè)類別。

讓我們定義領(lǐng)域模型的最后一部分,EUnitOfMeasurement** **枚舉。

按照慣例,枚舉不需要在名稱前以“ E”開(kāi)頭,但是在某些庫(kù)和框架中,您會(huì)發(fā)現(xiàn)此前綴是將枚舉與接口和類區(qū)分開(kāi)的一種方式。

123456789101112131415161718192021222324252627using System.ComponentModel; namespace Supermarket.API.Domain.Models { public enum EUnitOfMeasurement : byte { [Description("UN")] Unity = 1, [Description("MG")] Milligram = 2, [Description("G")] Gram = 3, [Description("KG")] Kilogram = 4, [Description("L")] Liter = 5 } }

該代碼非常簡(jiǎn)單。在這里,我們僅定義了幾種度量單位的可能性,但是,在實(shí)際的超市系統(tǒng)中,您可能具有許多其他度量單位,并且可能還有一個(gè)單獨(dú)的模型。
注意,【Description】特性應(yīng)用于所有枚舉可能性。特性是一種在C#語(yǔ)言的類,接口,屬性和其他組件上定義元數(shù)據(jù)的方法。在這種情況下,我們將使用它來(lái)簡(jiǎn)化產(chǎn)品API端點(diǎn)的響應(yīng),但是您現(xiàn)在不必關(guān)心它。我們待會(huì)再回到這里。

我們的基本模型已準(zhǔn)備就緒,可以使用?,F(xiàn)在,我們可以開(kāi)始編寫(xiě)將管理所有類別的API端點(diǎn)。

第3步-類別API

在Controllers文件夾中,添加一個(gè)名為的新類CategoriesController。

按照慣例,該文件夾中所有后綴為“ Controller”的類都將成為我們應(yīng)用程序的控制器。這意味著他們將處理請(qǐng)求和響應(yīng)。您必須從命名空間【Microsoft.AspNetCore.Mvc】繼承Controller。

命名空間由一組相關(guān)的類,接口,枚舉和結(jié)構(gòu)組成。您可以將其視為類似于Java語(yǔ)言模塊或Java?程序包的東西。

新的控制器應(yīng)通過(guò)路由/api/categories做出響應(yīng)。我們通過(guò)Route** **在類名稱上方添加屬性,指定占位符來(lái)實(shí)現(xiàn)此目的,該占位符表示路由應(yīng)按照慣例使用不帶控制器后綴的類名稱。

12345678910using Microsoft.AspNetCore.Mvc; namespace Supermarket.API.Controllers { [Route("/api/[controller]")] public class CategoriesController : Controller { } }

讓我們開(kāi)始處理GET請(qǐng)求。首先,當(dāng)有人/api/categories通過(guò)GET動(dòng)詞請(qǐng)求數(shù)據(jù)時(shí),API需要返回所有類別。為此,我們可以創(chuàng)建類別服務(wù)。
從概念上講,服務(wù)基本上是定義用于處理某些業(yè)務(wù)邏輯的方法的類或接口。創(chuàng)建用于處理業(yè)務(wù)邏輯的服務(wù)是許多不同編程語(yǔ)言的一種常見(jiàn)做法,例如身份驗(yàn)證和授權(quán),付款,復(fù)雜的數(shù)據(jù)流,緩存和需要其他服務(wù)或模型之間進(jìn)行某些交互的任務(wù)。

使用服務(wù),我們可以將請(qǐng)求和響應(yīng)處理與完成任務(wù)所需的真實(shí)邏輯隔離開(kāi)來(lái)。

該服務(wù),我們要?jiǎng)?chuàng)建將首先定義一個(gè)單獨(dú)的行為,或方法:一個(gè)list方法。我們希望該方法返回?cái)?shù)據(jù)庫(kù)中所有現(xiàn)有的類別。

為簡(jiǎn)單起見(jiàn),在這篇博客中,我們將不處理數(shù)據(jù)分頁(yè)或過(guò)濾,(譯者注:基于RESTFul規(guī)范,提供了一套完整的分頁(yè)和過(guò)濾的規(guī)則)。將來(lái),我將寫(xiě)一篇文章,展示如何輕松處理這些功能。

為了定義C#(以及其他面向?qū)ο蟮恼Z(yǔ)言,例如Java)中某事物的預(yù)期行為,我們定義一個(gè)interface。一個(gè)接口告訴某些事情應(yīng)該如何工作,但是沒(méi)有實(shí)現(xiàn)行為的真實(shí)邏輯。邏輯在實(shí)現(xiàn)接口的類中實(shí)現(xiàn)。如果您不清楚此概念,請(qǐng)不要擔(dān)心。一段時(shí)間后您將了解它。

在Domain文件夾中,創(chuàng)建一個(gè)名為的新目錄Services。在此添加一個(gè)名為ICategoryService的接口。按照慣例,所有接口都應(yīng)以C#中的大寫(xiě)字母“ I”開(kāi)頭。定義接口代碼,如下所示:

123456789101112using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; namespace Supermarket.API.Domain.Services { public interface ICategoryService { Task> ListAsync(); } }

該ListAsync方法的實(shí)現(xiàn)必須異步返回類別的可枚舉對(duì)象。
Task封裝返回的類表示異步。由于必須等待數(shù)據(jù)庫(kù)完成操作才能返回?cái)?shù)據(jù),因此我們需要考慮執(zhí)行此過(guò)程可能需要一段時(shí)間,因此我們需要使用異步方法。另請(qǐng)注意“Async”后綴。這是一個(gè)約定,告訴我們的方法應(yīng)異步執(zhí)行。

我們有很多約定,對(duì)嗎?我個(gè)人喜歡它,因?yàn)樗箲?yīng)用程序易于閱讀,即使你在一家使用.NET技術(shù)的公司是新人。

“-好的,我們定義了此接口,但是它什么也沒(méi)做。有什么用?”

如果您來(lái)自Javascript或其他非強(qiáng)類型語(yǔ)言,則此概念可能看起來(lái)很奇怪。

接口使我們能夠從實(shí)際實(shí)現(xiàn)中抽象出所需的行為。使用稱為依賴注入的機(jī)制,我們可以實(shí)現(xiàn)這些接口并將它們與其他組件隔離。

基本上,當(dāng)您使用依賴項(xiàng)注入時(shí),您可以使用接口定義一些行為。然后,創(chuàng)建一個(gè)實(shí)現(xiàn)該接口的類。最后,將引用從接口綁定到您創(chuàng)建的類。

”-聽(tīng)起來(lái)確實(shí)令人困惑。我們不能簡(jiǎn)單地創(chuàng)建一個(gè)為我們做這些事情的類嗎?”

讓我們繼續(xù)實(shí)現(xiàn)我們的API,您將了解為什么使用這種方法。

更改CategoriesController代碼,如下所示:

12345678910111213141516171819202122232425262728using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Services; namespace Supermarket.API.Controllers { [Route("/api/[controller]")] public class CategoriesController : Controller { private readonly ICategoryService _categoryService; public CategoriesController(ICategoryService categoryService) { _categoryService = categoryService; } [HttpGet] public async Task> GetAllAsync() { var categories = await _categoryService.ListAsync(); return categories; } } }

我已經(jīng)為控制器定義了一個(gè)構(gòu)造函數(shù)(當(dāng)創(chuàng)建一個(gè)類的新實(shí)例時(shí)會(huì)調(diào)用一個(gè)構(gòu)造函數(shù)),并且它接收的實(shí)例ICategoryService。這意味著實(shí)例可以是任何實(shí)現(xiàn)服務(wù)接口的實(shí)例。我將此實(shí)例存儲(chǔ)在一個(gè)私有的只讀字段中_categoryService。我們將使用此字段訪問(wèn)類別服務(wù)實(shí)現(xiàn)的方法。
順便說(shuō)一下,下劃線前綴是表示字段的另一個(gè)通用約定。特別地,.NET的官方命名約定指南不建議使用此約定,但是這是一種非常普遍的做法,可以避免使用“ this”關(guān)鍵字來(lái)區(qū)分類字段和局部變量。我個(gè)人認(rèn)為閱讀起來(lái)要干凈得多,并且許多框架和庫(kù)都使用此約定。

在構(gòu)造函數(shù)下,我定義了用于處理請(qǐng)求的方法/api/categories。該HttpGet** **屬性告訴ASP.NET Core管道使用該屬性來(lái)處理GET請(qǐng)求(可以省略此屬性,但是最好編寫(xiě)它以便于閱讀)。

該方法使用我們的CategoryService實(shí)例列出所有類別,然后將類別返回給客戶端??蚣芄艿缹?shù)據(jù)序列化為JSON對(duì)象。IEnumerable類型告訴框架,我們想要返回一個(gè)類別的枚舉,而Task類型(使用async關(guān)鍵字修飾)告訴管道,這個(gè)方法應(yīng)該異步執(zhí)行。最后,當(dāng)我們定義一個(gè)異步方法時(shí),我們必須使用await關(guān)鍵字來(lái)處理需要一些時(shí)間的任務(wù)。

好的,我們定義了API的初始結(jié)構(gòu)?,F(xiàn)在,有必要真正實(shí)現(xiàn)類別服務(wù)。

步驟4-實(shí)現(xiàn)類別服務(wù)

在API的根文件夾(即Supermarket.API文件夾)中,創(chuàng)建一個(gè)名為的新文件夾Services。在這里,我們將放置所有服務(wù)實(shí)現(xiàn)。在新文件夾中,添加一個(gè)名為CategoryService的新類。更改代碼,如下所示:

123456789101112131415using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Services; namespace Supermarket.API.Services { public class CategoryService : ICategoryService { public async Task> ListAsync() { } } }

以上只是接口實(shí)現(xiàn)的基本代碼,我們暫時(shí)仍不處理任何邏輯。讓我們考慮一下列表方法應(yīng)該如何實(shí)現(xiàn)。
我們需要訪問(wèn)數(shù)據(jù)庫(kù)并返回所有類別,然后我們需要將此數(shù)據(jù)返回給客戶端。

服務(wù)類不是應(yīng)該處理數(shù)據(jù)訪問(wèn)的類。我們將使用一種稱為“倉(cāng)儲(chǔ)模式”的設(shè)計(jì)模式,定義倉(cāng)儲(chǔ)類,用于管理數(shù)據(jù)庫(kù)中的數(shù)據(jù)。

在使用倉(cāng)儲(chǔ)模式時(shí),我們定義了repository 類,該類基本上封裝了處理數(shù)據(jù)訪問(wèn)的所有邏輯。這些倉(cāng)儲(chǔ)類使方法可以列出,創(chuàng)建,編輯和刪除給定模型的對(duì)象,與操作集合的方式相同。在內(nèi)部,這些方法與數(shù)據(jù)庫(kù)對(duì)話以執(zhí)行CRUD操作,從而將數(shù)據(jù)庫(kù)訪問(wèn)與應(yīng)用程序的其余部分隔離開(kāi)。

我們的服務(wù)需要調(diào)用類別倉(cāng)儲(chǔ),以獲取列表對(duì)象。

從概念上講,服務(wù)可以與一個(gè)或多個(gè)倉(cāng)儲(chǔ)或其他服務(wù)“對(duì)話”以執(zhí)行操作。

創(chuàng)建用于處理數(shù)據(jù)訪問(wèn)邏輯的新定義似乎是多余的,但是您將在一段時(shí)間內(nèi)看到將這種邏輯與服務(wù)類隔離是非常有利的。

讓我們創(chuàng)建一個(gè)倉(cāng)儲(chǔ),該倉(cāng)儲(chǔ)負(fù)責(zé)與數(shù)據(jù)庫(kù)通信,作為持久化保存類別的一種方式。

步驟5-類別倉(cāng)儲(chǔ)和持久層

在該Domain文件夾內(nèi),創(chuàng)建一個(gè)名為的新目錄Repositories。然后,添加一個(gè)名為的新接口ICategoryRespository。定義接口如下:

12345678910using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; namespace Supermarket.API.Domain.Repositories { public interface ICategoryRepository { Task> ListAsync(); } }

初始代碼基本上與服務(wù)接口的代碼相同。
定義了接口之后,我們可以返回服務(wù)類并使用的實(shí)例ICategoryRepository返回?cái)?shù)據(jù)來(lái)完成實(shí)現(xiàn)list方法。

1234567891011121314151617181920212223242526using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Repositories; using Supermarket.API.Domain.Services; namespace Supermarket.API.Services { public class CategoryService : ICategoryService { private readonly ICategoryRepository _categoryRepository; public CategoryService(ICategoryRepository categoryRepository) { this._categoryRepository = categoryRepository; } public async Task> ListAsync() { return await _categoryRepository.ListAsync(); } } }

現(xiàn)在,我們必須實(shí)現(xiàn)類別倉(cāng)儲(chǔ)的真實(shí)邏輯。在這樣做之前,我們必須考慮如何訪問(wèn)數(shù)據(jù)庫(kù)。順便說(shuō)一句,我們?nèi)匀粵](méi)有數(shù)據(jù)庫(kù)!

我們將使用Entity Framework Core(為簡(jiǎn)單起見(jiàn),我將其稱為EF Core)作為我們的數(shù)據(jù)庫(kù)ORM。該框架是ASP.NET Core的默認(rèn)ORM,并公開(kāi)了一個(gè)友好的API,該API使我們能夠?qū)?yīng)用程序的類映射到數(shù)據(jù)庫(kù)表。

EF Core還允許我們先設(shè)計(jì)應(yīng)用程序,然后根據(jù)我們?cè)诖a中定義的內(nèi)容生成數(shù)據(jù)庫(kù)。此技術(shù)稱為Code First。我們將使用Code First方法來(lái)生成數(shù)據(jù)庫(kù)(實(shí)際上,在此示例中,我將使用內(nèi)存數(shù)據(jù)庫(kù),但是您可以輕松地將其更改為像SQL Server或MySQL服務(wù)器這樣的實(shí)例數(shù)據(jù)庫(kù))。

在API的根文件夾中,創(chuàng)建一個(gè)名為的新目錄Persistence。此目錄將包含我們?cè)L問(wèn)數(shù)據(jù)庫(kù)所需的所有內(nèi)容,例如倉(cāng)儲(chǔ)實(shí)現(xiàn)。

在新文件夾中,創(chuàng)建一個(gè)名為的新目錄Contexts,然后添加一個(gè)名為的新類AppDbContext。此類必須繼承DbContext,EF Core通過(guò)DBContext用來(lái)將您的模型映射到數(shù)據(jù)庫(kù)表的類。通過(guò)以下方式更改代碼:

123456789101112using Microsoft.EntityFrameworkCore; namespace Supermarket.API.Domain.Persistence.Contexts { public class AppDbContext : DbContext { public AppDbContext(DbContextOptions options) : base(options) { } } }

我們添加到此類的構(gòu)造函數(shù)負(fù)責(zé)通過(guò)依賴注入將數(shù)據(jù)庫(kù)配置傳遞給基類。稍后您將看到其工作原理。
現(xiàn)在,我們必須創(chuàng)建兩個(gè)DbSet屬性。這些屬性是將模型映射到數(shù)據(jù)庫(kù)表的集合(唯一對(duì)象的集合)。

另外,我們必須將模型的屬性映射到相應(yīng)的列,指定哪些屬性是主鍵,哪些是外鍵,列類型等。我們可以使用稱為Fluent API的功能來(lái)覆蓋OnModelCreating方法,以指定數(shù)據(jù)庫(kù)映射。更改AppDbContext類,如下所示:

該代碼是如此直觀。

123456789101112131415161718192021222324252627282930313233343536373839404142using Microsoft.EntityFrameworkCore; using Supermarket.API.Domain.Models; namespace Supermarket.API.Persistence.Contexts { public class AppDbContext : DbContext { public DbSet Categories { get; set; } public DbSet Products { get; set; } public AppDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity().ToTable("Categories"); builder.Entity().HasKey(p => p.Id); builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd(); builder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(30); builder.Entity().HasMany(p => p.Products).WithOne(p => p.Category).HasForeignKey(p => p.CategoryId); builder.Entity().HasData ( new Category { Id = 100, Name = "Fruits and Vegetables" }, // Id set manually due to in-memory provider new Category { Id = 101, Name = "Dairy" } ); builder.Entity().ToTable("Products"); builder.Entity().HasKey(p => p.Id); builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd(); builder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(50); builder.Entity().Property(p => p.QuantityInPackage).IsRequired(); builder.Entity().Property(p => p.UnitOfMeasurement).IsRequired(); } } }

我們指定我們的模型應(yīng)映射到哪些表。此外,我們?cè)O(shè)置了主鍵,使用該方法HasKey,該表的列,使用Property方法,和一些限制,例如IsRequired,HasMaxLength,和ValueGeneratedOnAdd,這些都是使用FluentApi的方式基于Lamada 表達(dá)式語(yǔ)法實(shí)現(xiàn)的(鏈?zhǔn)秸Z(yǔ)法)。
看一下下面的代碼:

1234builder.Entity() .HasMany(p => p.Products) .WithOne(p => p.Category) .HasForeignKey(p => p.CategoryId);

在這里,我們指定表之間的關(guān)系。我們說(shuō)一個(gè)類別有很多產(chǎn)品,我們?cè)O(shè)置了將映射此關(guān)系的屬性(Products,來(lái)自Category類,和Category,來(lái)自Product類)。我們還設(shè)置了外鍵(CategoryId)。
如果您想學(xué)習(xí)如何使用EF Core配置一對(duì)一和多對(duì)多關(guān)系,以及如何完整的使用它,請(qǐng)看一下本教程。

還有一種用于通過(guò)HasData方法配置種子數(shù)據(jù)的方法:

12345builder.Entity().HasData( new Category { Id = 100, Name = "Fruits and Vegetables" }, new Category { Id = 101, Name = "Dairy" });

默認(rèn)情況下,在這里我們僅添加兩個(gè)示例類別。這對(duì)我們完成后進(jìn)行API的測(cè)試來(lái)說(shuō)是非常有必要的。

注意:我們?cè)贗d這里手動(dòng)設(shè)置屬性,因?yàn)閮?nèi)存提供程序的工作機(jī)制需要。我將標(biāo)識(shí)符設(shè)置為大數(shù)字,以避免自動(dòng)生成的標(biāo)識(shí)符和種子數(shù)據(jù)之間發(fā)生沖突。

真正的關(guān)系數(shù)據(jù)庫(kù)提供程序中不存在此限制,因此,例如,如果要使用SQL Server等數(shù)據(jù)庫(kù),則不必指定這些標(biāo)識(shí)符。如果您想了解此行為,請(qǐng)檢查此Github問(wèn)題。

在實(shí)現(xiàn)數(shù)據(jù)庫(kù)上下文類之后,我們可以實(shí)現(xiàn)類別倉(cāng)儲(chǔ)。添加一個(gè)名為新的文件夾Repositories里面Persistence的文件夾,然后添加一個(gè)名為新類BaseRepository。

12345678910111213141516using Supermarket.API.Persistence.Contexts; namespace Supermarket.API.Persistence.Repositories { public abstract class BaseRepository { protected readonly AppDbContext _context; public BaseRepository(AppDbContext context) { _context = context; } } }

此類只是我們所有倉(cāng)儲(chǔ)都將繼承的抽象類。抽象類是沒(méi)有直接實(shí)例的類。您必須創(chuàng)建直接類來(lái)創(chuàng)建實(shí)例。
在BaseRepository接受我們的實(shí)例,AppDbContext通過(guò)依賴注入暴露了一個(gè)受保護(hù)的屬性稱為(只能是由子類訪問(wèn)一個(gè)屬性)_context,即可以訪問(wèn)我們需要處理數(shù)據(jù)庫(kù)操作的所有方法。

在相同文件夾中添加一個(gè)新類CategoryRepository?,F(xiàn)在,我們將真正實(shí)現(xiàn)倉(cāng)儲(chǔ)邏輯:

1234567891011121314151617181920212223using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Repositories; using Supermarket.API.Persistence.Contexts; namespace Supermarket.API.Persistence.Repositories { public class CategoryRepository : BaseRepository, ICategoryRepository { public CategoryRepository(AppDbContext context) : base(context) { } public async Task> ListAsync() { return await _context.Categories.ToListAsync(); } } }

倉(cāng)儲(chǔ)繼承BaseRepository和實(shí)現(xiàn)ICategoryRepository。
注意實(shí)現(xiàn)list方法是很簡(jiǎn)單的。我們使用Categories數(shù)據(jù)庫(kù)集訪問(wèn)類別表,然后調(diào)用擴(kuò)展方法ToListAsync,該方法負(fù)責(zé)將查詢結(jié)果轉(zhuǎn)換為類別的集合。

EF Core?將我們的方法調(diào)用轉(zhuǎn)換為SQL查詢,這是最有效的方法。這種方式僅當(dāng)您調(diào)用將數(shù)據(jù)轉(zhuǎn)換為集合的方法或使用方法獲取特定數(shù)據(jù)時(shí)才執(zhí)行查詢。

現(xiàn)在,我們有了類別控制器,服務(wù)和倉(cāng)儲(chǔ)庫(kù)的代碼實(shí)現(xiàn)。

我們將關(guān)注點(diǎn)分離開(kāi)來(lái),創(chuàng)建了只執(zhí)行應(yīng)做的事情的類。

測(cè)試應(yīng)用程序之前的最后一步是使用ASP.NET Core依賴項(xiàng)注入機(jī)制將我們的接口綁定到相應(yīng)的類。

第6步-配置依賴注入

現(xiàn)在是時(shí)候讓您最終了解此概念的工作原理了。

在應(yīng)用程序的根文件夾中,打開(kāi)Startup類。此類負(fù)責(zé)在應(yīng)用程序啟動(dòng)時(shí)配置各種配置。

該ConfigureServices和Configure方法通過(guò)框架管道在運(yùn)行時(shí)調(diào)用來(lái)配置應(yīng)用程序應(yīng)該如何工作,必須使用哪些組件。

打開(kāi)ConfigureServices方法。在這里,我們只有一行配置應(yīng)用程序以使用MVC管道,這基本上意味著該應(yīng)用程序?qū)⑹褂每刂破黝悂?lái)處理請(qǐng)求和響應(yīng)(在這段代碼背后發(fā)生了很多事情,但目前您僅需要知道這些)。

我們可以使用ConfigureServices訪問(wèn)services參數(shù)的方法來(lái)配置我們的依賴項(xiàng)綁定。清理類代碼,刪除所有注釋并按如下所示更改代碼:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Supermarket.API.Domain.Repositories; using Supermarket.API.Domain.Services; using Supermarket.API.Persistence.Contexts; using Supermarket.API.Persistence.Repositories; using Supermarket.API.Services; namespace Supermarket.API { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext(options => { options.UseInMemoryDatabase("supermarket-api-in-memory"); }); services.AddScoped(); services.AddScoped(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }

看一下這段代碼:

12345services.AddDbContext(options => { options.UseInMemoryDatabase("supermarket-api-in-memory"); });

在這里,我們配置數(shù)據(jù)庫(kù)上下文。我們告訴ASP.NET Core將其AppDbContext與內(nèi)存數(shù)據(jù)庫(kù)實(shí)現(xiàn)一起使用,該實(shí)現(xiàn)由作為參數(shù)傳遞給我們方法的字符串標(biāo)識(shí)。通常,在編寫(xiě)集成測(cè)試時(shí)才會(huì)使用內(nèi)存數(shù)據(jù)庫(kù),但是為了簡(jiǎn)單起見(jiàn),我在這里使用了內(nèi)存數(shù)據(jù)庫(kù)。這樣,我們無(wú)需連接到真實(shí)的數(shù)據(jù)庫(kù)即可測(cè)試應(yīng)用程序。
這些代碼行在內(nèi)部配置我們的數(shù)據(jù)庫(kù)上下文,以便使用確定作用域的生存周期進(jìn)行依賴注入。

scoped生存周期告訴ASP.NET Core管道,每當(dāng)它需要解析接收AppDbContext作為構(gòu)造函數(shù)參數(shù)的實(shí)例的類時(shí),都應(yīng)使用該類的相同實(shí)例。如果內(nèi)存中沒(méi)有實(shí)例,則管道將創(chuàng)建一個(gè)新實(shí)例,并在給定請(qǐng)求期間在需要它的所有類中重用它。這樣,您無(wú)需在需要使用時(shí)手動(dòng)創(chuàng)建類實(shí)例。

如果你想了解其他有關(guān)生命周期的知識(shí),可以閱讀官方文檔。

依賴注入技術(shù)為我們提供了許多優(yōu)勢(shì),例如:

  • 代碼可重用性;

  • 更高的生產(chǎn)力,因?yàn)楫?dāng)我們不得不更改實(shí)現(xiàn)時(shí),我們無(wú)需費(fèi)心去更改您使用該功能的一百個(gè)地方;

  • 您可以輕松地測(cè)試應(yīng)用程序,因?yàn)槲覀兛梢允褂胢ock(類的偽實(shí)現(xiàn))隔離必須測(cè)試的內(nèi)容,而我們必須將接口作為構(gòu)造函數(shù)參數(shù)進(jìn)行傳遞。

  • 當(dāng)一個(gè)類需要通過(guò)構(gòu)造函數(shù)接收更多的依賴關(guān)系時(shí),您不必手動(dòng)更改正在創(chuàng)建實(shí)例的所有位置(太贊了!)。

配置數(shù)據(jù)庫(kù)上下文之后,我們還將我們的服務(wù)和倉(cāng)儲(chǔ)綁定到相應(yīng)的類。

123services.AddScoped();services.AddScoped();

在這里,我們還使用了scoped生存周期,因?yàn)檫@些類在內(nèi)部必須使用數(shù)據(jù)庫(kù)上下文類。在這種情況下,指定相同的范圍是有意義的。
現(xiàn)在我們配置了依賴綁定,我們必須在Program類上進(jìn)行一些小的更改,以便數(shù)據(jù)庫(kù)正確地初始化種子數(shù)據(jù)。此步驟僅在使用內(nèi)存數(shù)據(jù)庫(kù)提供程序時(shí)才需要執(zhí)行(請(qǐng)參閱此Github問(wèn)題以了解原因)。

123456789101112131415161718192021222324252627282930313233343536373839using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Supermarket.API.Persistence.Contexts; namespace Supermarket.API { public class Program { public static void Main(string[] args) { var host = BuildWebHost(args); using(var scope = host.Services.CreateScope()) using(var context = scope.ServiceProvider.GetService()) { context.Database.EnsureCreated(); } host.Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .Build(); } }

由于我們使用的是內(nèi)存提供程序,因此有必要更改Main方法 添加“ context.Database.EnsureCreated();”代碼以確保在應(yīng)用程序啟動(dòng)時(shí)將“創(chuàng)建”數(shù)據(jù)庫(kù)。沒(méi)有此更改,將不會(huì)創(chuàng)建我們想要的初始化種子數(shù)據(jù)。
實(shí)現(xiàn)了所有基本功能后,就該測(cè)試我們的API端點(diǎn)了。

第7步-測(cè)試類別

在API根文件夾中打開(kāi)終端或命令提示符,然后鍵入以下命令:

1dotnet run

上面的命令啟動(dòng)應(yīng)用程序??刂婆_(tái)將顯示類似于以下內(nèi)容的輸出:

123456789101112131415161718192021info: Microsoft.EntityFrameworkCore.Infrastructure[10403]Entity Framework Core 2.2.0-rtm-35687 initialized ‘AppDbContext’ using provider ‘Microsoft.EntityFrameworkCore.InMemory’ with options: StoreName=supermarket-api-in-memoryinfo: Microsoft.EntityFrameworkCore.Update[30100]Saved 2 entities to in-memory store.info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]User profile is available. Using ‘C:\Users\evgomes\AppData\Local\ASP.NET\DataProtection-Keys’ as key repository and Windows DPAPI to encrypt keys at rest.Hosting environment: DevelopmentContent root path: C:\Users\evgomes\Desktop\Tutorials\src\Supermarket.APINow listening on: https://localhost:5001Now listening on: http://localhost:5000Application started. Press Ctrl+C to shut down.

您可以看到調(diào)用了EF Core來(lái)初始化數(shù)據(jù)庫(kù)。最后幾行顯示應(yīng)用程序在哪個(gè)端口上運(yùn)行。
打開(kāi)瀏覽器,然后導(dǎo)航到?http://localhost:5000/api/categories?(或控制臺(tái)輸出上顯示的URL)。如果您發(fā)現(xiàn)由于HTTPS導(dǎo)致的安全錯(cuò)誤,則只需為應(yīng)用程序添加一個(gè)例外。

瀏覽器將顯示以下JSON數(shù)據(jù)作為輸出:

123456789101112[ { "id": 100, "name": "Fruits and Vegetables", "products": [] }, { "id": 101, "name": "Dairy", "products": [] }]

在這里,我們看到配置數(shù)據(jù)庫(kù)上下文時(shí)添加到數(shù)據(jù)庫(kù)的數(shù)據(jù)。此輸出確認(rèn)我們的代碼正在運(yùn)行。
您使用很少的代碼行創(chuàng)建了GET API端點(diǎn),并且由于當(dāng)前API項(xiàng)目的架構(gòu)模式,您的代碼結(jié)構(gòu)確實(shí)很容易更改。

現(xiàn)在,該向您展示在由于業(yè)務(wù)需要而不得不對(duì)其進(jìn)行更改時(shí),更改此代碼有多么容易。

步驟8-創(chuàng)建類別資源

如果您還記得API端點(diǎn)的規(guī)范,您會(huì)注意到我們的實(shí)際JSON響應(yīng)還有一個(gè)額外的屬性:products數(shù)組??匆幌滤桧憫?yīng)的示例:

1234567{ [ { "id": 1, "name": "Fruits and Vegetables" }, { "id": 2, "name": "Breads" }, … // Other categories ]}

產(chǎn)品數(shù)組出現(xiàn)在我們當(dāng)前的JSON響應(yīng)中,因?yàn)槲覀兊腃ategory模型具有Products,EF Core需要的屬性,以正確映射給定類別的產(chǎn)品。
我們不希望在響應(yīng)中使用此屬性,但是不能更改模型類以排除此屬性。當(dāng)我們嘗試管理類別數(shù)據(jù)時(shí),這將導(dǎo)致EF Core引發(fā)錯(cuò)誤,并且也將破壞我們的領(lǐng)域模型設(shè)計(jì),因?yàn)闆](méi)有產(chǎn)品的產(chǎn)品類別沒(méi)有意義。

要返回僅包含超級(jí)市場(chǎng)類別的標(biāo)識(shí)符和名稱的JSON數(shù)據(jù),我們必須創(chuàng)建一個(gè)資源類。

資源類是一種包含將客戶端應(yīng)用程序和API端點(diǎn)之間進(jìn)行交換的類型,通常以JSON數(shù)據(jù)的形式出現(xiàn),以表示一些特定信息的類。

來(lái)自API端點(diǎn)的所有響應(yīng)都必須返回資源。

將真實(shí)模型表示形式作為響應(yīng)返回是一種不好的做法,因?yàn)樗赡馨蛻舳藨?yīng)用程序不需要或沒(méi)有其權(quán)限的信息(例如,用戶模型可以返回用戶密碼的信息) ,這將是一個(gè)很大的安全問(wèn)題)。

我們需要一種資源來(lái)僅代表我們的類別,而沒(méi)有產(chǎn)品。

現(xiàn)在您知道什么是資源,讓我們實(shí)現(xiàn)它。首先,在命令行中按Ctrl + C停止正在運(yùn)行的應(yīng)用程序。在應(yīng)用程序的根文件夾中,創(chuàng)建一個(gè)名為Resources的新文件夾。在其中添加一個(gè)名為的新類CategoryResource。

12345678namespace Supermarket.API.Resources { public class CategoryResource { public int Id { get; set; } public string Name { get; set; } } }

我們必須將類別服務(wù)提供的類別模型集合映射到類別資源集合。
我們將使用一個(gè)名為AutoMapper的庫(kù)來(lái)處理對(duì)象之間的映射。AutoMapper是.NET世界中非常流行的庫(kù),并且在許多商業(yè)和開(kāi)源項(xiàng)目中使用。

在命令行中輸入以下命令,以將AutoMapper添加到我們的應(yīng)用程序中:

123dotnet add package AutoMapperdotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

要使用AutoMapper,我們必須做兩件事:

  • 注冊(cè)它以進(jìn)行依賴注入;

  • 創(chuàng)建一個(gè)類,該類將告訴AutoMapper如何處理類映射。

首先,打開(kāi)Startup課程。在該ConfigureServices方法的最后一行之后,添加以下代碼:

1services.AddAutoMapper();

此行處理AutoMapper的所有必需配置,例如注冊(cè)它以進(jìn)行依賴項(xiàng)注入以及在啟動(dòng)過(guò)程中掃描應(yīng)用程序以配置映射配置文件。
現(xiàn)在,在根目錄中,添加一個(gè)名為的新文件夾Mapping,然后添加一個(gè)名為的類ModelToResourceProfile。通過(guò)以下方式更改代碼:

123456789101112131415using AutoMapper; using Supermarket.API.Domain.Models; using Supermarket.API.Resources; namespace Supermarket.API.Mapping { public class ModelToResourceProfile : Profile { public ModelToResourceProfile() { CreateMap(); } } }

該類繼承Profile了AutoMapper用于檢查我們的映射如何工作的類類型。在構(gòu)造函數(shù)上,我們?cè)贑ategory模型類和CategoryResource類之間創(chuàng)建一個(gè)映射。由于類的屬性具有相同的名稱和類型,因此我們不必為其使用任何特殊的配置。
最后一步包括更改類別控制器以使用AutoMapper處理我們的對(duì)象映射。

1234567891011121314151617181920212223242526272829303132333435using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Services; using Supermarket.API.Resources; namespace Supermarket.API.Controllers { [Route("/api/[controller]")] public class CategoriesController : Controller { private readonly ICategoryService _categoryService; private readonly IMapper _mapper; public CategoriesController(ICategoryService categoryService, IMapper mapper) { _categoryService = categoryService; _mapper = mapper; } [HttpGet] public async Task> GetAllAsync() { var categories = await _categoryService.ListAsync(); var resources = _mapper.Map, IEnumerable>(categories); return resources; } } }

我更改了構(gòu)造函數(shù)以接收IMapper實(shí)現(xiàn)的實(shí)例。您可以使用這些接口方法來(lái)使用AutoMapper映射方法。
我還更改了GetAllAsync使用Map方法將類別枚舉映射到資源枚舉的方法。此方法接收我們要映射的類或集合的實(shí)例,并通過(guò)通用類型定義定義必須映射到什么類型的類或集合。

注意,我們只需將新的依賴項(xiàng)(IMapper)注入構(gòu)造函數(shù),就可以輕松地更改實(shí)現(xiàn),而不必修改服務(wù)類或倉(cāng)儲(chǔ)。

依賴注入使您的應(yīng)用程序可維護(hù)且易于更改,因?yàn)槟槐刂袛嗨写a實(shí)現(xiàn)即可添加或刪除功能。

您可能意識(shí)到,不僅控制器類,而且所有接收依賴項(xiàng)的類(包括依賴項(xiàng)本身)都會(huì)根據(jù)綁定配置自動(dòng)解析為接收正確的類。

依賴注入如此的Amazing,不是嗎?

現(xiàn)在,使用dotnet run命令再次啟動(dòng)API,然后轉(zhuǎn)到http://localhost:5000/api/categories以查看新的JSON響應(yīng)。

這是您應(yīng)該看到的響應(yīng)數(shù)據(jù)

我們已經(jīng)有了GET端點(diǎn)。現(xiàn)在,讓我們?yōu)镻OST(創(chuàng)建)類別創(chuàng)建一個(gè)新端點(diǎn)。

第9步-創(chuàng)建新類別

在處理資源創(chuàng)建時(shí),我們必須關(guān)心很多事情,例如:

  • 數(shù)據(jù)驗(yàn)證和數(shù)據(jù)完整性;

  • 授權(quán)創(chuàng)建資源;

  • 錯(cuò)誤處理;

  • 正在記錄。

在本教程中,我不會(huì)顯示如何處理身份驗(yàn)證和授權(quán),但是您可以閱讀JSON Web令牌身份驗(yàn)證教程,了解如何輕松實(shí)現(xiàn)這些功能。

另外,有一個(gè)非常流行的框架稱為ASP.NET Identity,該框架提供了有關(guān)安全性和用戶注冊(cè)的內(nèi)置解決方案,您可以在應(yīng)用程序中使用它們。它包括與EF Core配合使用的提供程序,例如IdentityDbContext可以使用的內(nèi)置程序。您可以在此處了解更多信息。

讓我們編寫(xiě)一個(gè)HTTP POST端點(diǎn),該端點(diǎn)將涵蓋其他場(chǎng)景(日志記錄除外,它可以根據(jù)不同的范圍和工具進(jìn)行更改)。

在創(chuàng)建新端點(diǎn)之前,我們需要一個(gè)新資源。此資源會(huì)將客戶端應(yīng)用程序發(fā)送到此端點(diǎn)的數(shù)據(jù)(在本例中為類別名稱)映射到我們應(yīng)用程序的類。

由于我們正在創(chuàng)建一個(gè)新類別,因此我們還沒(méi)有ID,這意味著我們需要一種資源來(lái)表示僅包含其名稱的類別。

在Resources文件夾中,添加一個(gè)新類SaveCategoryResource:

123456789101112using System.ComponentModel.DataAnnotations; namespace Supermarket.API.Resources { public class SaveCategoryResource { [Required] [MaxLength(30)] public string Name { get; set; } } }

注意Name屬性上的Required和MaxLength特性。這些屬性稱為數(shù)據(jù)注釋。ASP.NET Core管道使用此元數(shù)據(jù)來(lái)驗(yàn)證請(qǐng)求和響應(yīng)。顧名思義,類別名稱是必填項(xiàng),最大長(zhǎng)度為30個(gè)字符。
現(xiàn)在,讓我們定義新API端點(diǎn)的形狀。將以下代碼添加到類別控制器:

1234[HttpPost] public async Task PostAsync([FromBody] SaveCategoryResource resource) { }

我們使用HttpPost特性告訴框架這是一個(gè)HTTP POST端點(diǎn)。
注意此方法的響應(yīng)類型Task。控制器類中存在的方法稱為動(dòng)作,它們具有此簽名,因?yàn)樵趹?yīng)用程序執(zhí)行動(dòng)作之后,我們可以返回一個(gè)以上的可能結(jié)果。

在這種情況下,如果類別名稱無(wú)效或出現(xiàn)問(wèn)題,我們必須返回400代碼(錯(cuò)誤請(qǐng)求)響應(yīng),該響應(yīng)通常包含一條錯(cuò)誤消息,客戶端應(yīng)用程序可以使用該錯(cuò)誤消息來(lái)解決該問(wèn)題,或者我們可以如果一切正常,則對(duì)數(shù)據(jù)進(jìn)行200次響應(yīng)(成功)。

可以將多種類型的操作類型用作響應(yīng),但是通常,我們可以使用此接口,并且ASP.NET Core將為此使用默認(rèn)類。

該FromBody屬性告訴ASP.NET Core將請(qǐng)求正文數(shù)據(jù)解析為我們的新資源類。這意味著當(dāng)包含類別名稱的JSON發(fā)送到我們的應(yīng)用程序時(shí),框架將自動(dòng)將其解析為我們的新類。

現(xiàn)在,讓我們實(shí)現(xiàn)路由邏輯。我們必須遵循一些步驟才能成功創(chuàng)建新類別:

  • 首先,我們必須驗(yàn)證傳入的請(qǐng)求。如果請(qǐng)求無(wú)效,我們必須返回包含錯(cuò)誤消息的錯(cuò)誤請(qǐng)求響應(yīng);

  • 然后,如果請(qǐng)求有效,則必須使用AutoMapper將新資源映射到類別模型類。

  • 現(xiàn)在,我們需要調(diào)用我們的服務(wù),告訴它保存我們的新類別。如果執(zhí)行保存邏輯沒(méi)有問(wèn)題,它將返回一個(gè)包含我們新類別數(shù)據(jù)的響應(yīng)。如果沒(méi)有,它應(yīng)該給我們一個(gè)指示,表明該過(guò)程失敗了,并可能出現(xiàn)錯(cuò)誤消息。

  • 最后,如果有錯(cuò)誤,我們將返回錯(cuò)誤的請(qǐng)求。如果沒(méi)有,我們將新的類別模型映射到類別資源,并向客戶端返回包含新類別數(shù)據(jù)的成功響應(yīng)。

這似乎很復(fù)雜,但是使用為API構(gòu)建的服務(wù)架構(gòu)來(lái)實(shí)現(xiàn)此邏輯確實(shí)很容易。

讓我們開(kāi)始驗(yàn)證傳入的請(qǐng)求。

步驟10-使用模型狀態(tài)驗(yàn)證請(qǐng)求主體

ASP.NET Core控制器具有名為ModelState的屬性。在執(zhí)行我們的操作之前,該屬性在請(qǐng)求執(zhí)行期間填充。它是ModelStateDictionary的實(shí)例,該類包含諸如請(qǐng)求是否有效以及潛在的驗(yàn)證錯(cuò)誤消息之類的信息。

如下更改端點(diǎn)代碼:

123456[HttpPost] public async Task PostAsync([FromBody] SaveCategoryResource resource) { if (!ModelState.IsValid) return BadRequest(ModelState.GetErrorMessages()); }

這段代碼檢查模型狀態(tài)(在這種情況下為請(qǐng)求正文中發(fā)送的數(shù)據(jù))是否無(wú)效,并檢查我們的數(shù)據(jù)注釋。如果不是,則API返回錯(cuò)誤的請(qǐng)求(狀態(tài)代碼400),以及我們的注釋元數(shù)據(jù)提供的默認(rèn)錯(cuò)誤消息。
該ModelState.GetErrorMessages()方法尚未實(shí)現(xiàn)。這是一種擴(kuò)展方法(一種擴(kuò)展現(xiàn)有類或接口功能的方法),我將實(shí)現(xiàn)該方法將驗(yàn)證錯(cuò)誤轉(zhuǎn)換為簡(jiǎn)單的字符串以返回給客戶端。

Extensions在我們的API的根目錄中添加一個(gè)新文件夾,然后添加一個(gè)新類ModelStateExtensions。

1234567891011121314151617using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Supermarket.API.Extensions { public static class ModelStateExtensions { public static List GetErrorMessages(this ModelStateDictionary dictionary) { return dictionary.SelectMany(m => m.Value.Errors) .Select(m => m.ErrorMessage) .ToList(); } } }

所有擴(kuò)展方法以及聲明它們的類都應(yīng)該是靜態(tài)的。** **這意味著它們不處理特定的實(shí)例數(shù)據(jù),并且在應(yīng)用程序啟動(dòng)時(shí)僅被加載一次。
this參數(shù)聲明前面的關(guān)鍵字告訴C#編譯器將其視為擴(kuò)展方法。結(jié)果是我們可以像此類的常規(guī)方法一樣調(diào)用它,因?yàn)槲覀冊(cè)谝褂脭U(kuò)展的地方包含的特定的using代碼。

該擴(kuò)展使用LINQ查詢,這是.NET的非常有用的功能,它使我們能夠使用鏈?zhǔn)秸Z(yǔ)法來(lái)查詢和轉(zhuǎn)換數(shù)據(jù)。此處的表達(dá)式將驗(yàn)證錯(cuò)誤方法轉(zhuǎn)換為包含錯(cuò)誤消息的字符串列表。

Supermarket.API.Extensions在進(jìn)行下一步之前,將名稱空間導(dǎo)入Categories控制器。

1using Supermarket.API.Extensions;

讓我們通過(guò)將新資源映射到類別模型類來(lái)繼續(xù)實(shí)現(xiàn)端點(diǎn)邏輯。

步驟11-映射新資源

我們已經(jīng)定義了映射配置文件,可以將模型轉(zhuǎn)換為資源?,F(xiàn)在,我們需要一個(gè)與之相反的新配置項(xiàng)。

ResourceToModelProfile在Mapping文件夾中添加一個(gè)新類:

123456789101112131415using AutoMapper; using Supermarket.API.Domain.Models; using Supermarket.API.Resources; namespace Supermarket.API.Mapping { public class ResourceToModelProfile : Profile { public ResourceToModelProfile() { CreateMap(); } } }

這里沒(méi)有新內(nèi)容。由于依賴注入的魔力,AutoMapper將在應(yīng)用程序啟動(dòng)時(shí)自動(dòng)注冊(cè)此配置文件,而我們無(wú)需更改任何其他位置即可使用它。
現(xiàn)在,我們可以將新資源映射到相應(yīng)的模型類:

123456789[HttpPost] public async Task PostAsync([FromBody] SaveCategoryResource resource) { if (!ModelState.IsValid) return BadRequest(ModelState.GetErrorMessages()); var category = _mapper.Map(resource); }

第12步-應(yīng)用請(qǐng)求-響應(yīng)模式來(lái)處理保存邏輯

現(xiàn)在我們必須實(shí)現(xiàn)最有趣的邏輯:保存一個(gè)新類別。我們希望我們的服務(wù)能夠做到。

由于連接到數(shù)據(jù)庫(kù)時(shí)出現(xiàn)問(wèn)題,或者由于任何內(nèi)部業(yè)務(wù)規(guī)則使我們的數(shù)據(jù)無(wú)效,因此保存邏輯可能會(huì)失敗。

如果出現(xiàn)問(wèn)題,我們不能簡(jiǎn)單地拋出一個(gè)錯(cuò)誤,因?yàn)樗赡軙?huì)停止API,并且客戶端應(yīng)用程序也不知道如何處理該問(wèn)題。另外,我們可能會(huì)有某種日志記錄機(jī)制來(lái)記錄錯(cuò)誤。

保存方法的約定(即方法的簽名和響應(yīng)類型)需要指示我們是否正確執(zhí)行了該過(guò)程。如果處理正常,我們將接收類別數(shù)據(jù)。如果沒(méi)有,我們至少必須收到一條錯(cuò)誤消息,告訴您該過(guò)程失敗的原因。

我們可以通過(guò)應(yīng)用request-response模式來(lái)實(shí)現(xiàn)此功能。這種企業(yè)設(shè)計(jì)模式將我們的請(qǐng)求和響應(yīng)參數(shù)封裝到類中,以封裝我們的服務(wù)將用于處理某些任務(wù)并將信息返回給正在使用該服務(wù)的類的信息。

這種模式為我們提供了一些優(yōu)勢(shì),例如:

  • 如果我們需要更改服務(wù)以接收更多參數(shù),則不必破壞其簽名;

  • 我們可以為我們的請(qǐng)求和/或響應(yīng)定義標(biāo)準(zhǔn)合同;

  • 我們可以在不停止應(yīng)用程序流程的情況下處理業(yè)務(wù)邏輯和潛在的失敗,并且我們不需要使用大量的try-catch塊。

讓我們?yōu)樘幚頂?shù)據(jù)更改的服務(wù)方法創(chuàng)建一個(gè)標(biāo)準(zhǔn)響應(yīng)類型。對(duì)于這種類型的每個(gè)請(qǐng)求,我們都想知道該請(qǐng)求是否被正確執(zhí)行。如果失敗,我們要向客戶端返回錯(cuò)誤消息。

在Domain文件夾的內(nèi)部Services,添加一個(gè)名為的新目錄Communication。在此處添加一個(gè)名為的新類BaseResponse。

123456789101112131415namespace Supermarket.API.Domain.Services.Communication { public abstract class BaseResponse { public bool Success { get; protected set; } public string Message { get; protected set; } public BaseResponse(bool success, string message) { Success = success; Message = message; } } }

那是我們的響應(yīng)類型將繼承的抽象類。
抽象定義了一個(gè)Success屬性和一個(gè)Message屬性,該屬性將告知請(qǐng)求是否已成功完成,如果失敗,該屬性將顯示錯(cuò)誤消息。

請(qǐng)注意,這些屬性是必需的,只有繼承的類才能設(shè)置此數(shù)據(jù),因?yàn)樽宇惐仨毻ㄟ^(guò)構(gòu)造函數(shù)傳遞此信息。

提示:為所有內(nèi)容定義基類不是一個(gè)好習(xí)慣,因?yàn)榛悤?huì)耦合您的代碼并阻止您輕松對(duì)其進(jìn)行修改。優(yōu)先使用組合而不是繼承。

在此API的范圍內(nèi),使用基類并不是真正的問(wèn)題,因?yàn)槲覀兊姆?wù)不會(huì)增長(zhǎng)太多。如果您意識(shí)到服務(wù)或應(yīng)用程序會(huì)經(jīng)常增長(zhǎng)和更改,請(qǐng)避免使用基類。

現(xiàn)在,在同一文件夾中,添加一個(gè)名為的新類SaveCategoryResponse。

12345678910111213141516171819202122232425262728293031323334using Supermarket.API.Domain.Models; namespace Supermarket.API.Domain.Services.Communication { public class SaveCategoryResponse : BaseResponse { public Category Category { get; private set; } private SaveCategoryResponse(bool success, string message, Category category) : base(success, message) { Category = category; } /// /// Creates a success response. /// /// Saved category. /// Response. public SaveCategoryResponse(Category category) : this(true, string.Empty, category) { } /// /// Creates am error response. /// /// Error message. /// Response. public SaveCategoryResponse(string message) : this(false, message, null) { } } }

響應(yīng)類型還設(shè)置了一個(gè)Category屬性,如果請(qǐng)求成功完成,該屬性將包含我們的類別數(shù)據(jù)。
請(qǐng)注意,我為此類定義了三種不同的構(gòu)造函數(shù):

  • 一個(gè)私有的,它將把成功和消息參數(shù)傳遞給基類,并設(shè)置Category屬性。

  • 僅接收類別作為參數(shù)的構(gòu)造函數(shù)。這將創(chuàng)建一個(gè)成功的響應(yīng),調(diào)用私有構(gòu)造函數(shù)來(lái)設(shè)置各自的屬性;

  • 第三個(gè)構(gòu)造函數(shù)僅指定消息。這將用于創(chuàng)建故障響應(yīng)。

因?yàn)镃#支持多個(gè)構(gòu)造函數(shù),所以我們僅通過(guò)使用不同的構(gòu)造函數(shù)就簡(jiǎn)化了響應(yīng)的創(chuàng)建過(guò)程,而無(wú)需定義其他方法來(lái)處理此問(wèn)題。

現(xiàn)在,我們可以更改服務(wù)界面以添加新的保存方法合同。

更改ICategoryService接口,如下所示:

1234567891011121314using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Services.Communication; namespace Supermarket.API.Domain.Services { public interface ICategoryService { Task> ListAsync(); Task SaveAsync(Category category); } }

我們只需將類別傳遞給此方法,它將處理保存模型數(shù)據(jù),編排倉(cāng)儲(chǔ)和其他必要服務(wù)所需的所有邏輯。
請(qǐng)注意,由于我們不需要任何其他參數(shù)來(lái)執(zhí)行此任務(wù),因此我不在此處創(chuàng)建特定的請(qǐng)求類。計(jì)算機(jī)編程中有一個(gè)名為KISS的概念?—Keep It Simple,Stupid的簡(jiǎn)稱?;旧?#xff0c;它說(shuō)您應(yīng)該使您的應(yīng)用程序盡可能簡(jiǎn)單。

設(shè)計(jì)應(yīng)用程序時(shí)請(qǐng)記住這一點(diǎn):僅應(yīng)用解決問(wèn)題所需的內(nèi)容。不要過(guò)度設(shè)計(jì)您的應(yīng)用程序。

現(xiàn)在我們可以完成端點(diǎn)邏輯:

123456789101112131415161718[HttpPost] public async Task PostAsync([FromBody] SaveCategoryResource resource) { if (!ModelState.IsValid) return BadRequest(ModelState.GetErrorMessages()); var category = _mapper.Map(resource); var result = await _categoryService.SaveAsync(category); if (!result.Success) return BadRequest(result.Message); var categoryResource = _mapper.Map(result.Category); return Ok(categoryResource); }

在驗(yàn)證請(qǐng)求數(shù)據(jù)并將資源映射到我們的模型之后,我們將其傳遞給我們的服務(wù)以保留數(shù)據(jù)。
如果失敗,則API返回錯(cuò)誤的請(qǐng)求。如果沒(méi)有,API會(huì)將新類別(現(xiàn)在包括諸如new的數(shù)據(jù)Id)映射到我們先前創(chuàng)建的類別CategoryResource,并將其發(fā)送給客戶端。

現(xiàn)在,讓我們?yōu)榉?wù)實(shí)現(xiàn)真正的邏輯。

第13步—數(shù)據(jù)庫(kù)邏輯和工作單元模式

由于我們要將數(shù)據(jù)持久化到數(shù)據(jù)庫(kù)中,因此我們需要在倉(cāng)儲(chǔ)中使用一種新方法。

向ICategoryRepository接口添加AddAsync新方法:

12345public interface ICategoryRepository { Task> ListAsync(); Task AddAsync(Category category); }

現(xiàn)在,讓我們?cè)谡嬲膫}(cāng)儲(chǔ)類中實(shí)現(xiàn)此方法:

1234567891011121314151617public class CategoryRepository : BaseRepository, ICategoryRepository { public CategoryRepository(AppDbContext context) : base(context) { } public async Task> ListAsync() { return await _context.Categories.ToListAsync(); } public async Task AddAsync(Category category) { await _context.Categories.AddAsync(category); } }

在這里,我們只是在集合中添加一個(gè)新類別。
當(dāng)我們向中添加類時(shí)DBSet<>,EF Core將開(kāi)始跟蹤模型發(fā)生的所有更改,并在當(dāng)前狀態(tài)下使用此數(shù)據(jù)生成將插入,更新或刪除模型的查詢。

當(dāng)前的實(shí)現(xiàn)只是將模型添加到我們的集合中,但是我們的數(shù)據(jù)仍然不會(huì)保存。

在上下文類中提供了SaveChanges的方法,我們必須調(diào)用該方法才能真正將查詢執(zhí)行到數(shù)據(jù)庫(kù)中。我之所以沒(méi)有在這里調(diào)用它,是因?yàn)閭}(cāng)儲(chǔ)不應(yīng)該持久化數(shù)據(jù),它只是一種內(nèi)存集合對(duì)象。

即使在經(jīng)驗(yàn)豐富的.NET開(kāi)發(fā)人員之間,該主題也引起很大爭(zhēng)議,但是讓我向您解釋為什么您不應(yīng)該在倉(cāng)儲(chǔ)類中調(diào)用SaveChanges方法。

我們可以從概念上將倉(cāng)儲(chǔ)像.NET框架中存在的任何其他集合一樣。在.NET(和許多其他編程語(yǔ)言,例如Javascript和Java)中處理集合時(shí),通??梢?#xff1a;

  • 向其中添加新項(xiàng)(例如,當(dāng)您將數(shù)據(jù)推送到列表,數(shù)組和字典時(shí));

  • 查找或過(guò)濾項(xiàng)目;

  • 從集合中刪除一個(gè)項(xiàng)目;

  • 替換給定的項(xiàng)目,或更新它。

想一想現(xiàn)實(shí)世界中的清單。想象一下,您正在編寫(xiě)一份購(gòu)物清單以在超市購(gòu)買(mǎi)東西(巧合,不是嗎?)。

在列表中,寫(xiě)下您需要購(gòu)買(mǎi)的所有水果。您可以將水果添加到此列表中,如果放棄購(gòu)買(mǎi)就刪除水果,也可以替換水果的名稱。但是您無(wú)法將水果保存到列表中。用簡(jiǎn)單的英語(yǔ)說(shuō)這樣的話是沒(méi)有意義的。

提示:在使用面向?qū)ο蟮木幊陶Z(yǔ)言設(shè)計(jì)類和接口時(shí),請(qǐng)嘗試使用自然語(yǔ)言來(lái)檢查您所做的工作是否正確。

例如,說(shuō)人實(shí)現(xiàn)了person的接口是有道理的,但是說(shuō)一個(gè)人實(shí)現(xiàn)了一個(gè)帳戶卻沒(méi)有道理。

如果您要“保存”水果清單(在這種情況下,要購(gòu)買(mǎi)所有水果),請(qǐng)付款,然后超市會(huì)處理庫(kù)存數(shù)據(jù)以檢查他們是否必須從供應(yīng)商處購(gòu)買(mǎi)更多水果。

編程時(shí)可以應(yīng)用相同的邏輯。倉(cāng)儲(chǔ)不應(yīng)保存,更新或刪除數(shù)據(jù)。相反,他們應(yīng)該將其委托給其他類來(lái)處理此邏輯。

將數(shù)據(jù)直接保存到倉(cāng)儲(chǔ)中時(shí),還有另一個(gè)問(wèn)題:您不能使用transaction。

想象一下,我們的應(yīng)用程序具有一種日志記錄機(jī)制,該機(jī)制存儲(chǔ)一些用戶名,并且每次對(duì)API數(shù)據(jù)進(jìn)行更改時(shí)都會(huì)執(zhí)行操作。

現(xiàn)在想象一下,由于某種原因,您調(diào)用了一個(gè)更新用戶名的服務(wù)(這是不常見(jiàn)的情況,但讓我們考慮一下)。

您同意要更改虛擬用戶表中的用戶名,首先必須更新所有日志以正確告訴誰(shuí)執(zhí)行了該操作,對(duì)嗎?

現(xiàn)在想象我們已經(jīng)為用戶和不同倉(cāng)儲(chǔ)中的日志實(shí)現(xiàn)了update方法,它們都調(diào)用了SaveChanges。如果這些方法之一在更新過(guò)程中失敗,會(huì)發(fā)生什么?最終會(huì)導(dǎo)致數(shù)據(jù)不一致。

只有在一切完成之后,我們才應(yīng)該將更改保存到數(shù)據(jù)庫(kù)中。為此,我們必須使用transaction,這基本上是大多數(shù)數(shù)據(jù)庫(kù)實(shí)現(xiàn)的功能,只有在完成復(fù)雜的操作后才能保存數(shù)據(jù)。

“-好的,所以如果我們不能在這里保存東西,我們應(yīng)該在哪里做?”

處理此問(wèn)題的常見(jiàn)模式是工作單元模式。此模式包含一個(gè)類,該類將我們的AppDbContext實(shí)例作為依賴項(xiàng)接收,并公開(kāi)用于開(kāi)始,完成或中止事務(wù)的方法。

在這里,我們將使用工作單元的簡(jiǎn)單實(shí)現(xiàn)來(lái)解決我們的問(wèn)題。

Repositories在Domain層的倉(cāng)儲(chǔ)文件夾Repositories內(nèi)添加一個(gè)新接口IUnitOfWork:

12345678910using System.Threading.Tasks; namespace Supermarket.API.Domain.Repositories { public interface IUnitOfWork { Task CompleteAsync(); } }

如您所見(jiàn),它僅公開(kāi)一種將異步完成數(shù)據(jù)管理操作的方法。
現(xiàn)在讓我們添加實(shí)際的實(shí)現(xiàn)。

在Persistence層RepositoriesRepositories文件夾中的添加一個(gè)名為的UnitOfWork的新類:

123456789101112131415161718192021222324using System.Threading.Tasks; using Supermarket.API.Domain.Repositories; using Supermarket.API.Persistence.Contexts; namespace Supermarket.API.Persistence.Repositories { public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _context; public UnitOfWork(AppDbContext context) { _context = context; } public async Task CompleteAsync() { await _context.SaveChangesAsync(); } } }

這是一個(gè)簡(jiǎn)單,干凈的實(shí)現(xiàn),僅在使用倉(cāng)儲(chǔ)修改完所有更改后,才將所有更改保存到數(shù)據(jù)庫(kù)中。
如果研究工作單元模式的實(shí)現(xiàn),則會(huì)發(fā)現(xiàn)實(shí)現(xiàn)回滾操作的更復(fù)雜的模式。

由于EF Core已經(jīng)在后臺(tái)實(shí)現(xiàn)了倉(cāng)儲(chǔ)模式和工作單元,因此我們不必在意回滾方法。

“ - 什么?那么為什么我們必須創(chuàng)建所有這些接口和類?”

將持久性邏輯與業(yè)務(wù)規(guī)則分開(kāi)在代碼可重用性和維護(hù)方面具有許多優(yōu)勢(shì)。如果直接使用EF Core,我們最終將擁有更復(fù)雜的類,這些類將很難更改。

想象一下,將來(lái)您決定將ORM框架更改為其他框架,例如Dapper,或者由于性能而必須實(shí)施純SQL查詢。如果將查詢邏輯與服務(wù)耦合在一起,將很難更改該邏輯,因?yàn)槟仨氃谠S多類中進(jìn)行此操作。

使用倉(cāng)儲(chǔ)模式,您可以簡(jiǎn)單地實(shí)現(xiàn)一個(gè)新的倉(cāng)儲(chǔ)類并使用依賴注入將其綁定。

因此,基本上,如果您直接在服務(wù)中使用EF Core,并且必須進(jìn)行一些更改,那么您將獲得:

就像我說(shuō)的那樣,EF Core在后臺(tái)實(shí)現(xiàn)了工作單元和倉(cāng)儲(chǔ)模式。我們可以將DbSet<>屬性視為倉(cāng)儲(chǔ)。而且,SaveChanges僅在所有數(shù)據(jù)庫(kù)操作成功的情況下才保留數(shù)據(jù)。

現(xiàn)在,您知道什么是工作單元以及為什么將其與倉(cāng)儲(chǔ)一起使用,讓我們實(shí)現(xiàn)真實(shí)服務(wù)的邏輯。

1234567891011121314151617181920212223242526272829303132333435public class CategoryService : ICategoryService { private readonly ICategoryRepository _categoryRepository; private readonly IUnitOfWork _unitOfWork; public CategoryService(ICategoryRepository categoryRepository, IUnitOfWork unitOfWork) { _categoryRepository = categoryRepository; _unitOfWork = unitOfWork; } public async Task> ListAsync() { return await _categoryRepository.ListAsync(); } public async Task SaveAsync(Category category) { try { await _categoryRepository.AddAsync(category); await _unitOfWork.CompleteAsync(); return new SaveCategoryResponse(category); } catch (Exception ex) { // Do some logging stuff return new SaveCategoryResponse($"An error occurred when saving the category: {ex.Message}"); } } }

多虧了我們的解耦架構(gòu),我們可以簡(jiǎn)單地將實(shí)例UnitOfWork作為此類的依賴傳遞。
我們的業(yè)務(wù)邏輯非常簡(jiǎn)單。

首先,我們嘗試將新類別添加到數(shù)據(jù)庫(kù)中,然后API嘗試保存新類別,將所有內(nèi)容包裝在try-catch塊中。

如果失敗,則API會(huì)調(diào)用一些虛構(gòu)的日志記錄服務(wù),并返回指示失敗的響應(yīng)。

如果該過(guò)程順利完成,則應(yīng)用程序?qū)⒎祷爻晒憫?yīng),并發(fā)送我們的類別數(shù)據(jù)。簡(jiǎn)單吧?

提示:在現(xiàn)實(shí)世界的應(yīng)用程序中,您不應(yīng)將所有內(nèi)容包裝在通用的try-catch塊中,而應(yīng)分別處理所有可能的錯(cuò)誤。

簡(jiǎn)單地添加一個(gè)try-catch塊并不能解決大多數(shù)可能的失敗情況。請(qǐng)確保正確實(shí)現(xiàn)錯(cuò)誤處理。

測(cè)試我們的API之前的最后一步是將工作單元接口綁定到其各自的類。

將此新行添加到類的ConfigureServices方法中Startup:

1services.AddScoped();

現(xiàn)在讓我們測(cè)試一下!第14步-使用Postman測(cè)試我們的POST端點(diǎn)

重新啟動(dòng)我們的應(yīng)用程序dotnet run。

我們無(wú)法使用瀏覽器測(cè)試POST端點(diǎn)。讓我們使用Postman測(cè)試我們的端點(diǎn)。這是測(cè)試RESTful API的非常有用的工具。

打開(kāi)Postman,然后關(guān)閉介紹性消息。您會(huì)看到這樣的屏幕:

屏幕顯示測(cè)試端點(diǎn)的選項(xiàng)

GET默認(rèn)情況下,將所選內(nèi)容更改為選擇框POST。

在Enter request URL字段中輸入API地址。

我們必須提供請(qǐng)求正文數(shù)據(jù)以發(fā)送到我們的API。單擊Body菜單項(xiàng),然后將其下方顯示的選項(xiàng)更改為raw。

Postman將在右側(cè)顯示一個(gè)Text選項(xiàng),將其更改為JSON (application/json)并粘貼以下JSON數(shù)據(jù):

123{ "name": ""}

發(fā)送請(qǐng)求前的屏幕

如您所見(jiàn),我們將向我們的新端點(diǎn)發(fā)送一個(gè)空的名稱字符串。

點(diǎn)擊Send按鈕。您將收到如下輸出:

如您所見(jiàn),我們的驗(yàn)證邏輯有效!

您還記得我們?yōu)槎它c(diǎn)創(chuàng)建的驗(yàn)證邏輯嗎?此輸出是它起作用的證明!

還要注意右側(cè)顯示的400狀態(tài)代碼。該BadRequest結(jié)果自動(dòng)將此狀態(tài)碼的響應(yīng)。

現(xiàn)在,讓我們將JSON數(shù)據(jù)更改為有效數(shù)據(jù),以查看新的響應(yīng):

最后,我們期望得到的結(jié)果

API正確創(chuàng)建了我們的新資源。

到目前為止,我們的API可以列出和創(chuàng)建類別。您學(xué)到了很多有關(guān)C#語(yǔ)言,ASP.NET Core框架以及構(gòu)造API的通用設(shè)計(jì)方法的知識(shí)。

讓我們繼續(xù)我們的類別API,創(chuàng)建用于更新類別的端點(diǎn)。

從現(xiàn)在開(kāi)始,由于我向您解釋了大多數(shù)概念,因此我將加快解釋速度,并專注于新主題,以免浪費(fèi)您的時(shí)間。Let’s go!

第15步-更新類別

要更新類別,我們需要一個(gè)HTTP PUT端點(diǎn)。

我們必須編寫(xiě)的邏輯與POST邏輯非常相似:

  • 首先,我們必須使用來(lái)驗(yàn)證傳入的請(qǐng)求ModelState。

  • 如果請(qǐng)求有效,則API應(yīng)使用AutoMapper將傳入資源映射到模型類。

  • 然后,我們需要調(diào)用我們的服務(wù),告訴它更新類別,提供相應(yīng)的類別Id和更新的數(shù)據(jù);

  • 如果Id數(shù)據(jù)庫(kù)中沒(méi)有給定的類別,我們將返回錯(cuò)誤的請(qǐng)求。我們可以使用NotFound結(jié)果來(lái)代替,但是對(duì)于這個(gè)范圍而言,這并不重要,因?yàn)槲覀兿蚩蛻舳藨?yīng)用程序提供了錯(cuò)誤消息。

  • 如果正確執(zhí)行了保存邏輯,則服務(wù)必須返回包含更新的類別數(shù)據(jù)的響應(yīng)。如果不是,它應(yīng)該給我們指示該過(guò)程失敗,并顯示一條消息指示原因;

  • 最后,如果有錯(cuò)誤,則API返回錯(cuò)誤的請(qǐng)求。如果不是,它將更新的類別模型映射到類別資源,并將成功響應(yīng)返回給客戶端應(yīng)用程序。

讓我們將新PutAsync方法添加到控制器類中:

123456789101112131415161718[HttpPut("{id}")] public async Task PutAsync(int id, [FromBody] SaveCategoryResource resource) { if (!ModelState.IsValid) return BadRequest(ModelState.GetErrorMessages()); var category = _mapper.Map(resource); var result = await _categoryService.UpdateAsync(id, category); if (!result.Success) return BadRequest(result.Message); var categoryResource = _mapper.Map(result.Category); return Ok(categoryResource); }

如果將其與POST邏輯進(jìn)行比較,您會(huì)注意到這里只有一個(gè)區(qū)別:HttPut屬性指定給定路由應(yīng)接收的參數(shù)。
我們將調(diào)用此端點(diǎn),將類別指定Id 為最后一個(gè)URL片段,例如/api/categories/1。ASP.NET Core管道將此片段解析為相同名稱的參數(shù)。

現(xiàn)在我們必須UpdateAsync在ICategoryService接口中定義方法簽名:

123456public interface ICategoryService { Task> ListAsync(); Task SaveAsync(Category category); Task UpdateAsync(int id, Category category); }

現(xiàn)在讓我們轉(zhuǎn)向真正的邏輯。

第16步-更新邏輯

首先,要更新類別,我們需要從數(shù)據(jù)庫(kù)中返回當(dāng)前數(shù)據(jù)(如果存在)。我們還需要將其更新到我們的中DBSet<>。

讓我們?cè)贗CategoryService界面中添加兩個(gè)新的方法約定:

1234567public interface ICategoryRepository { Task> ListAsync(); Task AddAsync(Category category); Task FindByIdAsync(int id); void Update(Category category); }

我們已經(jīng)定義了FindByIdAsync方法,該方法將從數(shù)據(jù)庫(kù)中異步返回一個(gè)類別,以及該Update方法。請(qǐng)注意,該Update方法不是異步的,因?yàn)镋F Core API不需要異步方法來(lái)更新模型。
現(xiàn)在,讓我們?cè)贑ategoryRepository類中實(shí)現(xiàn)真正的邏輯:

12345678910public async Task FindByIdAsync(int id) { return await _context.Categories.FindAsync(id); } public void Update(Category category) { _context.Categories.Update(category); }

最后,我們可以對(duì)服務(wù)邏輯進(jìn)行編碼:

1234567891011121314151617181920212223242526public async Task UpdateAsync(int id, Category category) { var existingCategory = await _categoryRepository.FindByIdAsync(id); if (existingCategory == null) return new SaveCategoryResponse("Category not found."); existingCategory.Name = category.Name; try { _categoryRepository.Update(existingCategory); await _unitOfWork.CompleteAsync(); return new SaveCategoryResponse(existingCategory); } catch (Exception ex) { // Do some logging stuff return new SaveCategoryResponse($"An error occurred when updating the category: {ex.Message}"); } }

API嘗試從數(shù)據(jù)庫(kù)中獲取類別。如果結(jié)果為null,我們將返回一個(gè)響應(yīng),告知該類別不存在。如果類別存在,我們需要設(shè)置其新名稱。
然后,API會(huì)嘗試保存更改,例如創(chuàng)建新類別時(shí)。如果該過(guò)程完成,則該服務(wù)將返回成功響應(yīng)。如果不是,則執(zhí)行日志記錄邏輯,并且端點(diǎn)接收包含錯(cuò)誤消息的響應(yīng)。

現(xiàn)在讓我們對(duì)其進(jìn)行測(cè)試。首先,讓我們添加一個(gè)新類別Id以使用有效類別。我們可以使用播種到數(shù)據(jù)庫(kù)中的類別的標(biāo)識(shí)符,但是我想通過(guò)這種方式向您展示我們的API將更新正確的資源。

再次運(yùn)行該應(yīng)用程序,然后使用Postman將新類別發(fā)布到數(shù)據(jù)庫(kù)中:

添加新類別以供日后更新

使用一個(gè)可用的數(shù)據(jù)Id,將POST 選項(xiàng)更改PUT為選擇框,然后在URL的末尾添加ID值。將name屬性更改為其他名稱,然后發(fā)送請(qǐng)求以檢查結(jié)果:

類別數(shù)據(jù)已成功更新

您可以將GET請(qǐng)求發(fā)送到API端點(diǎn),以確保您正確編輯了類別名稱:

那是現(xiàn)在GET請(qǐng)求的結(jié)果

我們必須對(duì)類別執(zhí)行的最后一項(xiàng)操作是排除類別。讓我們創(chuàng)建一個(gè)HTTP Delete端點(diǎn)。

第17步-刪除類別

刪除類別的邏輯確實(shí)很容易實(shí)現(xiàn),因?yàn)槲覀兯璧拇蠖鄶?shù)方法都是先前構(gòu)建的。

這些是我們工作路線的必要步驟:

  • API需要調(diào)用我們的服務(wù),告訴它刪除我們的類別,并提供相應(yīng)的Id;

  • 如果數(shù)據(jù)庫(kù)中沒(méi)有具有給定ID的類別,則該服務(wù)應(yīng)返回一條消息指出該類別;

  • 如果執(zhí)行刪除邏輯沒(méi)有問(wèn)題,則服務(wù)應(yīng)返回包含我們已刪除類別數(shù)據(jù)的響應(yīng)。如果沒(méi)有,它應(yīng)該給我們一個(gè)指示,表明該過(guò)程失敗了,并可能出現(xiàn)錯(cuò)誤消息。

  • 最后,如果有錯(cuò)誤,則API返回錯(cuò)誤的請(qǐng)求。如果不是,則API會(huì)將更新的類別映射到資源,并向客戶端返回成功響應(yīng)。

讓我們開(kāi)始添加新的端點(diǎn)邏輯:

12345678910111213[HttpDelete("{id}")] public async Task DeleteAsync(int id) { var result = await _categoryService.DeleteAsync(id); if (!result.Success) return BadRequest(result.Message); var categoryResource = _mapper.Map(result.Category); return Ok(categoryResource); }

該HttpDelete屬性還定義了一個(gè)id 模板。
在將DeleteAsync簽名添加到我們的ICategoryService接口之前,我們需要做一些小的重構(gòu)。

新的服務(wù)方法必須返回包含類別數(shù)據(jù)的響應(yīng),就像對(duì)PostAsyncand UpdateAsync方法所做的一樣。我們可以SaveCategoryResponse為此目的重用,但在這種情況下我們不會(huì)保存數(shù)據(jù)。

為了避免創(chuàng)建具有相同形狀的新類來(lái)滿足此要求,我們可以將我們重命名SaveCategoryResponse為CategoryResponse。

如果您使用的是Visual Studio Code,則可以打開(kāi)SaveCategoryResponse類,將鼠標(biāo)光標(biāo)放在類名上方,然后使用選項(xiàng)Change All Occurrences* *來(lái)重命名該類:

確保也重命名文件名。

讓我們將DeleteAsync方法簽名添加到ICategoryService 接口中:

1234567public interface ICategoryService { Task> ListAsync(); Task SaveAsync(Category category); Task UpdateAsync(int id, Category category); Task DeleteAsync(int id); }

在實(shí)施刪除邏輯之前,我們需要在倉(cāng)儲(chǔ)中使用一種新方法。
將Remove方法簽名添加到ICategoryRepository接口:

1void Remove(Category category);

現(xiàn)在,在倉(cāng)儲(chǔ)類上添加真正的實(shí)現(xiàn):

1234public void Remove(Category category) { _context.Categories.Remove(category); }

EF Core要求將模型的實(shí)例傳遞給Remove方法,以正確了解我們要?jiǎng)h除的模型,而不是簡(jiǎn)單地傳遞Id。
最后,讓我們?cè)贑ategoryService類上實(shí)現(xiàn)邏輯:

1234567891011121314151617181920212223public async Task DeleteAsync(int id) { var existingCategory = await _categoryRepository.FindByIdAsync(id); if (existingCategory == null) return new CategoryResponse("Category not found."); try { _categoryRepository.Remove(existingCategory); await _unitOfWork.CompleteAsync(); return new CategoryResponse(existingCategory); } catch (Exception ex) { // Do some logging stuff return new CategoryResponse($"An error occurred when deleting the category: {ex.Message}"); } }

這里沒(méi)有新內(nèi)容。該服務(wù)嘗試通過(guò)ID查找類別,然后調(diào)用我們的倉(cāng)儲(chǔ)以刪除類別。最后,工作單元完成將實(shí)際操作執(zhí)行到數(shù)據(jù)庫(kù)中的事務(wù)。“-嘿,但是每個(gè)類別的產(chǎn)品呢?為避免出現(xiàn)錯(cuò)誤,您是否不需要先創(chuàng)建倉(cāng)儲(chǔ)并刪除產(chǎn)品?”

答案是否定的。借助EF Core跟蹤機(jī)制,當(dāng)我們從數(shù)據(jù)庫(kù)中加載模型時(shí),框架便知道了該模型具有哪些關(guān)系。如果我們刪除它,EF Core知道它應(yīng)該首先遞歸刪除所有相關(guān)模型。

在將類映射到數(shù)據(jù)庫(kù)表時(shí),我們可以禁用此功能,但這在本教程的范圍之外。如果您想了解此功能,請(qǐng)看這里。

現(xiàn)在是時(shí)候測(cè)試我們的新端點(diǎn)了。再次運(yùn)行該應(yīng)用程序,并使用Postman發(fā)送DELETE請(qǐng)求,如下所示:

如您所見(jiàn),API毫無(wú)問(wèn)題地刪除了現(xiàn)有類別

我們可以通過(guò)發(fā)送GET請(qǐng)求來(lái)檢查我們的API是否正常工作:

我們已經(jīng)完成了類別API?,F(xiàn)在是時(shí)候轉(zhuǎn)向產(chǎn)品API。

步驟18-產(chǎn)品API

到目前為止,您已經(jīng)學(xué)習(xí)了如何實(shí)現(xiàn)所有基本的HTTP動(dòng)詞來(lái)使用ASP.NET Core處理CRUD操作。讓我們進(jìn)入實(shí)現(xiàn)產(chǎn)品API的下一個(gè)層次。

我將不再詳細(xì)介紹所有HTTP動(dòng)詞,因?yàn)檫@將是詳盡無(wú)遺的。在本教程的最后一部分,我將僅介紹GET請(qǐng)求,以向您展示在從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)時(shí)如何包括相關(guān)實(shí)體,以及如何使用Description我們?yōu)镋UnitOfMeasurement 枚舉值定義的屬性。

將新控制器ProductsController添加到名為Controllers的文件夾中。

在這里編寫(xiě)任何代碼之前,我們必須創(chuàng)建產(chǎn)品資源。

讓我刷新您的記憶,再次顯示我們的資源應(yīng)如何:

123456789101112131415{ [ { "id": 1, "name": "Sugar", "quantityInPackage": 1, "unitOfMeasurement": "KG" "category": { "id": 3, "name": "Sugar" } }, … // Other products ]}

我們想要一個(gè)包含數(shù)據(jù)庫(kù)中所有產(chǎn)品的JSON數(shù)組。
JSON數(shù)據(jù)與產(chǎn)品模型有兩點(diǎn)不同:

  • 測(cè)量單位以較短的方式顯示,僅顯示其縮寫(xiě)。

  • 我們輸出類別數(shù)據(jù)而不包括CategoryId屬性。

為了表示度量單位,我們可以使用簡(jiǎn)單的字符串屬性代替枚舉類型(順便說(shuō)一下,我們沒(méi)有JSON數(shù)據(jù)的默認(rèn)枚舉類型,因此我們必須將其轉(zhuǎn)換為其他類型)。

現(xiàn)在,我們現(xiàn)在要塑造新資源,讓我們創(chuàng)建它。ProductResource在Resources文件夾中添加一個(gè)新類:

1234567891011namespace Supermarket.API.Resources { public class ProductResource { public int Id { get; set; } public string Name { get; set; } public int QuantityInPackage { get; set; } public string UnitOfMeasurement { get; set; } public CategoryResource Category {get;set;} } }

現(xiàn)在,我們必須配置模型類和新資源類之間的映射。
映射配置將與用于其他映射的配置幾乎相同,但是在這里,我們必須處理將EUnitOfMeasurement枚舉轉(zhuǎn)換為字符串的操作。

您還記得StringValue應(yīng)用于枚舉類型的屬性嗎?現(xiàn)在,我將向您展示如何使用.NET框架的強(qiáng)大功能:反射 API提取此信息。

反射 API是一組強(qiáng)大的資源工具集,可讓我們提取和操作元數(shù)據(jù)。許多框架和庫(kù)(包括ASP.NET Core本身)都利用這些資源來(lái)處理許多后臺(tái)工作。

現(xiàn)在讓我們看看它在實(shí)踐中是如何工作的。將新類添加到Extensions名為的文件夾中EnumExtensions。

123456789101112131415161718using System.ComponentModel; using System.Reflection; namespace Supermarket.API.Extensions { public static class EnumExtensions { public static string ToDescriptionString(this TEnum @enum) { FieldInfo info = @enum.GetType().GetField(@enum.ToString()); var attributes = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false); return attributes?[0].Description ?? @enum.ToString(); } } }

第一次看代碼可能會(huì)讓人感到恐懼,但這并不復(fù)雜。讓我們分解代碼定義以了解其工作原理。
首先,我們定義了一種通用方法(一種方法,該方法可以接收不止一種類型的參數(shù),在這種情況下,該方法由TEnum聲明表示),該方法接收給定的枚舉作為參數(shù)。

由于enum是C#中的保留關(guān)鍵字,因此我們?cè)趨?shù)名稱前面添加了@,以使其成為有效名稱。

該方法的第一步是使用該方法獲取參數(shù)的類型信息(類,接口,枚舉或結(jié)構(gòu)定義)GetType。

然后,該方法使用來(lái)獲取特定的枚舉值(例如Kilogram)GetField(@enum.ToString())。

下一行找到Description應(yīng)用于枚舉值的所有屬性,并將其數(shù)據(jù)存儲(chǔ)到數(shù)組中(在某些情況下,我們可以為同一屬性指定多個(gè)屬性)。

最后一行使用較短的語(yǔ)法來(lái)檢查我們是否至少有一個(gè)枚舉類型的描述屬性。如果有,我們將返回Description此屬性提供的值。如果不是,我們使用默認(rèn)的強(qiáng)制類型轉(zhuǎn)換將枚舉作為字符串返回。

?.操作者(零條件運(yùn)算)檢查該值是否null訪問(wèn)其屬性之前。

??運(yùn)算符(空合并運(yùn)算符)告訴應(yīng)用程序在左邊的返回值,如果它不為空,或者在正確的,否則價(jià)值。

現(xiàn)在我們有了擴(kuò)展方法來(lái)提取描述,讓我們配置模型和資源之間的映射。多虧了AutoMapper,我們只需要多一行就可以做到這一點(diǎn)。

打開(kāi)ModelToResourceProfile類并通過(guò)以下方式更改代碼:

123456789101112131415161718192021using AutoMapper; using Supermarket.API.Domain.Models; using Supermarket.API.Extensions; using Supermarket.API.Resources; namespace Supermarket.API.Mapping { public class ModelToResourceProfile : Profile { public ModelToResourceProfile() { CreateMap(); CreateMap() .ForMember(src => src.UnitOfMeasurement, opt => opt.MapFrom(src => src.UnitOfMeasurement.ToDescriptionString())); } } }

此語(yǔ)法告訴AutoMapper使用新的擴(kuò)展方法將我們的EUnitOfMeasurement值轉(zhuǎn)換為包含其描述的字符串。簡(jiǎn)單吧?您可以閱讀官方文檔以了解完整語(yǔ)法。
注意,我們尚未為category屬性定義任何映射配置。因?yàn)槲覀冎盀轭悇e配置了映射,并且由于產(chǎn)品模型具有相同類型和名稱的category屬性,所以AutoMapper隱式知道應(yīng)該使用各自的配置來(lái)映射它。

現(xiàn)在,我們添加端點(diǎn)代碼。更改ProductsController代碼:

12345678910111213141516171819202122232425262728293031323334using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Services; using Supermarket.API.Resources; namespace Supermarket.API.Controllers { [Route("/api/[controller]")] public class ProductsController : Controller { private readonly IProductService _productService; private readonly IMapper _mapper; public ProductsController(IProductService productService, IMapper mapper) { _productService = productService; _mapper = mapper; } [HttpGet] public async Task> ListAsync() { var products = await _productService.ListAsync(); var resources = _mapper.Map, IEnumerable>(products); return resources; } } }

基本上,為類別控制器定義的結(jié)構(gòu)相同。
讓我們進(jìn)入服務(wù)部分。將一個(gè)新IProductService接口添加到Domain層中的Services文件夾中:

123456789101112using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; namespace Supermarket.API.Domain.Services { public interface IProductService { Task> ListAsync(); } }

您應(yīng)該已經(jīng)意識(shí)到,在真正實(shí)現(xiàn)新服務(wù)之前,我們需要一個(gè)倉(cāng)儲(chǔ)。
IProductRepository在相應(yīng)的文件夾中添加一個(gè)名為的新接口:

123456789101112using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; namespace Supermarket.API.Domain.Repositories { public interface IProductRepository { Task> ListAsync(); } }

現(xiàn)在,我們實(shí)現(xiàn)倉(cāng)儲(chǔ)。除了必須在查詢數(shù)據(jù)時(shí)返回每個(gè)產(chǎn)品的相應(yīng)類別數(shù)據(jù)外,我們幾乎必須像對(duì)類別倉(cāng)儲(chǔ)一樣實(shí)現(xiàn)。
默認(rèn)情況下,EF Core在查詢數(shù)據(jù)時(shí)不包括與模型相關(guān)的實(shí)體,因?yàn)樗赡芊浅B?想象一個(gè)具有十個(gè)相關(guān)實(shí)體的模型,所有相關(guān)實(shí)體都有自己的關(guān)系)。

要包括類別數(shù)據(jù),我們只需要多一行:

123456789101112131415161718192021222324using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Repositories; using Supermarket.API.Persistence.Contexts; namespace Supermarket.API.Persistence.Repositories { public class ProductRepository : BaseRepository, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public async Task> ListAsync() { return await _context.Products.Include(p => p.Category) .ToListAsync(); } } }

請(qǐng)注意對(duì)的調(diào)用Include(p => p.Category)。我們可以鏈接此語(yǔ)法,以在查詢數(shù)據(jù)時(shí)包含盡可能多的實(shí)體。執(zhí)行選擇時(shí),EF Core會(huì)將其轉(zhuǎn)換為聯(lián)接。
現(xiàn)在,我們可以ProductService像處理類別一樣實(shí)現(xiàn)類:

12345678910111213141516171819202122232425using System.Collections.Generic; using System.Threading.Tasks; using Supermarket.API.Domain.Models; using Supermarket.API.Domain.Repositories; using Supermarket.API.Domain.Services; namespace Supermarket.API.Services { public class ProductService : IProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public async Task> ListAsync() { return await _productRepository.ListAsync(); } } }

讓我們綁定更改Startup類的新依賴項(xiàng):

12345678910111213141516171819202122public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext(options => { options.UseInMemoryDatabase("supermarket-api-in-memory"); }); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddAutoMapper(); }

最后,在測(cè)試API之前,讓我們AppDbContext在初始化應(yīng)用程序時(shí)更改類以包括一些產(chǎn)品,以便我們看到結(jié)果:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity().ToTable("Categories"); builder.Entity().HasKey(p => p.Id); builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd().HasValueGenerator>(); builder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(30); builder.Entity().HasMany(p => p.Products).WithOne(p => p.Category).HasForeignKey(p => p.CategoryId); builder.Entity().HasData ( new Category { Id = 100, Name = "Fruits and Vegetables" }, // Id set manually due to in-memory provider new Category { Id = 101, Name = "Dairy" } ); builder.Entity().ToTable("Products"); builder.Entity().HasKey(p => p.Id); builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd(); builder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(50); builder.Entity().Property(p => p.QuantityInPackage).IsRequired(); builder.Entity().Property(p => p.UnitOfMeasurement).IsRequired(); builder.Entity().HasData ( new Product { Id = 100, Name = "Apple", QuantityInPackage = 1, UnitOfMeasurement = EUnitOfMeasurement.Unity, CategoryId = 100 }, new Product { Id = 101, Name = "Milk", QuantityInPackage = 2, UnitOfMeasurement = EUnitOfMeasurement.Liter, CategoryId = 101, } ); }

我添加了兩個(gè)虛構(gòu)產(chǎn)品,將它們與初始化應(yīng)用程序時(shí)我們播種的類別相關(guān)聯(lián)。
該測(cè)試了!再次運(yùn)行API并發(fā)送GET請(qǐng)求以/api/products使用Postman:

就是這樣!恭喜你!

現(xiàn)在,您將了解如何使用解耦的代碼架構(gòu)使用ASP.NET Core構(gòu)建RESTful API。您了解了.NET Core框架的許多知識(shí),如何使用C#,EF Core和AutoMapper的基礎(chǔ)知識(shí)以及在設(shè)計(jì)應(yīng)用程序時(shí)要使用的許多有用的模式。

您可以檢查API的完整實(shí)現(xiàn),包括產(chǎn)品的其他HTTP動(dòng)詞,并檢查Github倉(cāng)儲(chǔ):

evgomes / supermarket-api

使用ASP.NET Core 2.2構(gòu)建的簡(jiǎn)單RESTful API,展示了如何使用分離的,可維護(hù)的……創(chuàng)建RESTful服務(wù)。github.com

結(jié)論

ASP.NET Core是創(chuàng)建Web應(yīng)用程序時(shí)使用的出色框架。它帶有許多有用的API,可用于構(gòu)建干凈,可維護(hù)的應(yīng)用程序。創(chuàng)建專業(yè)應(yīng)用程序時(shí),可以將其視為一種選擇。

本文并未涵蓋專業(yè)API的所有方面,但您已學(xué)習(xí)了所有基礎(chǔ)知識(shí)。您還學(xué)到了許多有用的模式,可以解決我們每天面臨的模式。

希望您喜歡這篇文章,希望對(duì)您有所幫助。期待你的反饋,以便我能進(jìn)一步提高。

進(jìn)一步學(xué)習(xí)的可用參考資料

.NET Core教程-Microsoft文檔

ASP.NET Core文檔-Microsoft文檔

本文首發(fā)于溪源的個(gè)人博客https://www.techq.xyz,純屬個(gè)人見(jiàn)解,不代表本號(hào)或長(zhǎng)沙.NET技術(shù)社區(qū)觀點(diǎn)。

如有疏漏錯(cuò)誤,還請(qǐng)批評(píng)指正。【DotNET技術(shù)圈】是長(zhǎng)沙.NET技術(shù)社區(qū)運(yùn)營(yíng)的公眾號(hào),歡迎大家投稿支持。

總結(jié)

以上是生活随笔為你收集整理的asp编程工具_使用ASP.NET Core构建RESTful API的技术指南的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。