【 .NET Core 3.0 】框架之二 || 后端项目搭建
前言
至于為什么要搭建.Net Core 平臺,這個網上的解釋以及鋪天蓋地,想了想,還是感覺重要的一點,跨平臺,嗯!沒錯,而且比.Net 更容易搭建,速度也更快,所有的包均由Nuget提供,不再像以前的單純引入組件,
已經沒有了之前的Assemblies和COM的引入,初次使用感覺會很別扭,不過使用多了,發現還是很方便的,所以你一定要會使用Nuget,真的很強大,這點兒設計思路感覺更像Linux了。
下邊這三點,是先對 .net core ?有一個初步的認識,看得懂或者看不懂都沒有關系,以后大家肯定都會明白的:
?
1、.net core 框架性能測試
http://www.techempower.com/benchmarks/?我們可以通過這個web框架性能測試來看看 aspcore 的性能
?
2、.net core 執行過程
3、中間件執行過程
啟動的時候先執行該中間件類的構造函數,然后一路 Next() ;下去,返回的時候,正好是反向的,執行的是該類的邏輯部分:
?
4、AOP切面
?
?
?
5、整體框架結構與數據庫表UML
?
?
?
?
?
一、創建第一個Core
? ? 說了從零開始,就得從零開始,老生常談,開始。
1、SDK 安裝
當然,前提是你得安裝.Net Core 的 SDK 環境,這里只能使用 vs 2019 且版本在 16.3 以上。
?
下載 SDK 地址 :https://dotnet.microsoft.com/download
選擇指定的平臺即可安裝:
?
這里說下,SDK 和 RunTime 的區別:1、SDK 是用來開發 NetCore 的,內部捆綁了 Runtime 運行時;
2、但是如果只想運行 NetCore 項目的話,只需要在服務器中安裝 Runtime 運行時即可;
?
怎么判斷安裝成功了呢?直接運行命令,如果有結果證明成功了:
?
?
?
2、新建項目
1、打開vs2019 ,創建一個新的項目,這里有很多模板,大家都可以試試:
?
?
2、點擊下一步 Next。
3、然后創建模板:
?
這里要注意下,關于Https選項問題,有很多小伙伴在以后的接口調用中,勾選了這個,但是還是一直使用 http 協議去訪問,導致找不到響應的接口地址。
?
1、是你的項目創建的時候,勾選了 Https 選項,如果你還沒有創建,那就可以不要勾選那個 HTTPS選項。
2、如果你的項目已經創建好了,每次訪問都是HTTPS的,但是你不想這么做,可以在 launthSettings.json 文件中,把sslPort 端口號改成0即可
?
3、刪除IIS配置,默認使用 kestrel 服務器
?
?
?
3、項目整體結構分析
接下來咱們看看這個項目都包含了哪些東西:?
?
這里默認,有一個 WeatherForecastController.cs ,打開后:
1、當前控制器繼承了 ControllerBase 基類;
2、有一個路由特性,可以配置當前路由規則,比如當前的是[Route("[controller]")],表示 Url 路徑為 :localhost:5000/weacherforecast
?? 如果你配置 [Route("[controller]/[action]")] 表明 Url 路徑為 :localhost:5000/weacherforecast/get
3、發現有一個默認的構造函數,里邊已經注入了日志 ILogger,這個依賴注入相關內容,以后會講到;
4、默認提供了一個 get 方法,隨機生成了一個天氣集合;
?
接下來點開appsettings.json文件,這里就是整個系統app的配置地址,更類似以前的web.config,以后大家會用到。
?
?
繼續往下,打開Startup.cs 文件這里是整個項目的啟動文件,所有的啟動相關的都會在這里配置,比如 依賴注入,跨域請求,Redis緩存等,更多詳情在以后的文章中都會有所提起
?
二、重要文件說明
1、Program.cs
namespace blog.core.test3._0{ public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); }public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }}
下邊的內容,如果不理解,也無所謂,以后會慢慢懂的,至少要讀幾遍,知道相關概念。
這個Program是程序的入口, 看起來很眼熟, 是因為asp.net core application實際就是控制臺程序(console application).
它是一個調用asp.net core 相關庫的console application.?
Main方法里面的內容主要是用來配置和運行程序的。
因為我們的web程序需要一個宿主,所以CreateHostBuilder這個方法就創建了一個IHostBuilder. 而且我們還需要Web Server.
asp.net core 自帶了兩種http servers, 一個是WebListener, 它只能用于windows系統, 另一個是kestrel, 它是跨平臺的.
kestrel是默認的web server, 就是通過UseKestrel()這個方法來啟用的.
但是我們開發的時候使用的是IIS Express, 調用UseIISIntegration()這個方法是啟用IIS Express, 它作為Kestrel的Reverse Proxy server來用.
?
?
如果在windows服務器上部署的話, 就應該使用IIS作為Kestrel的反向代理服務器來管理和代理請求.
如果在linux上的話, 可以使用apache, nginx等等的作為kestrel的proxy server.
當然也可以單獨使用kestrel作為web 服務器, 但是使用iis作為反向代理還是有很多有優點的: 例如,IIS可以過濾請求, 管理證書, 程序崩潰時自動重啟等.
不過目前因為跨平臺,所以使用較多的還是 nginx。
webBuilder.UseStartup<Startup>();, 這句話表示在程序啟動的時候, 我們會調用Startup這個類.
Build()完之后返回一個實現了 IHost 接口的實例(IHostBuilder), 然后調用Run()就會運行Web程序, 并且阻止這個調用的線程, 直到程序關閉.
?
如果想要對AspNetCore源碼進行研究,可以查看源碼,這里提供兩個方法:
1、F12,當然這個不能看到詳細的,你需要安裝一個組件,VS2017 Resharper
2、查看Github 開源源碼?https://github.com/aspnet/MetaPackages/tree/master/src/Microsoft.AspNetCore?
?
最后讓大家看看我的 Blog.Core 項目最終的 Services :
2、Startup.cs
public 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.AddControllers(); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }
其實Startup算是程序真正的切入點,是配置服務和中間件的啟動類。
Startup 默認構造函數,注入了配置項 IConfiguration。
ConfigureServices方法是用來把services(各種服務, 例如identity, ef, mvc等等包括第三方的, 或者自己寫的)加入(register)到container(asp.net core的容器)中去, 并配置這些services. 這個container是用來進行dependency injection的(依賴注入). 所有注入的services(此外還包括一些框架已經注冊好的services) 在以后寫代碼的時候, 都可以將它們注入(inject)進去. 例如上面的Configure方法的參數, app, env, loggerFactory都是注入進去的services.
?
Configure 方法是asp.net core程序用來具體指定如何處理每個http請求的, 例如我們可以讓這個程序知道我使用mvc來處理http請求, 那就調用 app.UseEndpoints 這個方法就行,這個是一個短路中間件,表示 http 請求到了這里就不往下走了.?
?
?
看一看我們項目的最后,Configure方法是如何配置的,這個現在看看就行,以后肯定會讓大家都慢慢掌握:
?
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBlogArticleServices _blogArticleServices) {if (Appsettings.app("AppSettings", "Middleware_RequestResponse", "Enabled").ObjToBool()) { app.UseReuestResponseLog();//記錄請求與返回數據 }
if (env.IsDevelopment()) { // 在開發環境中,使用異常頁面,這樣可以暴露錯誤堆棧信息,所以不要放在生產環境。 app.UseDeveloperExceptionPage();
//app.Use(async (context, next) => //{ // //這里會多次調用,這里測試一下就行,不要打開注釋 // //var blogs =await _blogArticleServices.GetBlogs(); // var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; // Console.WriteLine(processName); // await next(); //}); } else { app.UseExceptionHandler("/Error"); // 在非開發環境中,使用HTTP嚴格安全傳輸(or HSTS) 對于保護web安全是非常重要的。 // 強制實施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts();
}
app.UseSwagger(); app.UseSwaggerUI(c => { //根據版本名稱倒序 遍歷展示 typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); // 將swagger首頁,設置成我們自定義的頁面,記得這個字符串的寫法:解決方案名.index.html c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html");//這里是配合MiniProfiler進行性能監控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暫時先注釋掉,不影響大局。 c.RoutePrefix = ""; //路徑配置,設置為空,表示直接在根域名(localhost:8001)訪問該文件,注意localhost:8001/swagger是訪問不到的,去launchSettings.json把launchUrl去掉,如果你想換一個路徑,直接寫名字即可,比如直接寫c.RoutePrefix = "doc"; });
app.UseMiniProfiler();
//跨域第二種方法,使用策略,詳細策略信息在ConfigureService中 app.UseCors("LimitRequests");//將 CORS 中間件添加到 web 應用程序管線中, 以允許跨域請求。
//跨域第一種版本,請要ConfigureService中配置服務 services.AddCors(); // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() //.AllowAnyMethod());
// 跳轉https //app.UseHttpsRedirection(); // 使用靜態文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回錯誤碼 app.UseStatusCodePages();//把錯誤碼返回前臺,比如是404
app.UseRouting();
//此授權認證方法已經放棄,請使用下邊的官方驗證方法。但是如果你還想傳User的全局變量,還是可以繼續使用中間件,第二種寫法//app.UseMiddleware<JwtTokenAuth>(); //app.UseJwtTokenAuth();
//如果你想使用官方認證,必須在上邊ConfigureService 中,配置JWT的認證服務 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可) app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/api2/chatHub"); });
}
?
3、運行方法
.net core 調試的兩種方法
1、通過IIS調試
?
2、項目自帶的Kestrel web應用調式
?
?
?
三、注冊并使用MVC
首先,在 ConfigureServices里添加控制器服務,因為我們這里是api項目,所以只需要添加 AddController() 即可,如果是 MVC,可以使用 AddControllersWithViews();
然后在 Configure 中,配置一下中間件:
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });這樣就達到了目的,這個不僅僅是 mvc 的開發過程,其他的也都是這樣的,以后我們開發其他的,都需要按照這個過程開發即可。
四、核心知識點
1、Routing 路由
路由有兩種方式: Convention-based (按約定), attribute-based(基于路由屬性配置的).?
其中convention-based (基于約定的) 主要用于MVC (返回View或者Razor Page那種的).
Web api 推薦使用attribute-based.
這種基于屬性配置的路由可以配置Controller或者Action級別, uri會根據Http method然后被匹配到一個controller里具體的action上.
常用的Http Method有:
Get, 查詢, Attribute:?HttpGet, 例如: '/api/product', '/api/product/1'
POST, 創建,?HttpPost, '/api/product'
PUT 整體修改更新?HttpPut, '/api/product/1'
PATCH 部分更新,?HttpPatch, '/api/product/1'
DELETE 刪除,?HttpDelete, '/api/product/1
還有一個Route屬性(attribute)也可以用于Controller層, 它可以控制action級的URI前綴.
//以下不是本系列教程,就看思路即可,不用敲代碼namespace CoreBackend.Api.Controllers{ //[Route("api/product")] [] public class ProductController: Controller { [] public JsonResult GetProducts() { return new JsonResult(new List<Product> { new Product { Id = 1, Name = "牛奶", Price = 2.5f }, new Product { Id = 2, Name = "面包", Price = 4.5f } }); } }}使用[Route("api/[controller]")], 它使得整個Controller下面所有action的uri前綴變成了"/api/product", 其中[controller]表示XxxController.cs中的Xxx(其實是小寫).
也可以具體指定,?[Route("api/product")], 這樣做的好處是, 如果ProductController重構以后改名了, 只要不改Route里面的內容, 那么請求的地址不會發生變化.
然后在GetProducts方法上面, 寫上HttpGet, 也可以寫HttpGet(). 它里面還可以加參數,例如: HttpGet("all"), 那么這個Action的請求的地址就變成了 "/api/product/All".
?
2、內容協商 Content Negotiation
如果 web api提供了多種內容格式, 那么可以通過Accept Header來選擇最好的內容返回格式: 例如:
application/json,?application/xml等等
如果設定的格式在web api里面沒有, 那么web api就會使用默認的格式.
asp.net core?默認提供的是json格式, 也可以配置xml等格式.
目前只考慮 Output formatter, 就是返回的內容格式.
?
如果想輸出xml格式,就配置這里:
?
?
3、創建Post Action
以下不是本系列,就看思路即可,不用敲代碼
//以下不是本系列教程,就看思路即可,不用敲代碼 [] public IActionResult GetProduct(int id) { var product = ProductService...(x => x.Id == id); if (product == null) { return NotFound(); } return Ok(product); }[] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } var maxId = ProductService.Max(x => x.Id); var newProduct = new Product { Id = ++maxId, Name = product.Name, Price = product.Price }; ProductService.Add(newProduct);
return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct); }
[HttpPost]?表示請求的謂詞是Post. 加上Controller的Route前綴, 那么訪問這個Action的地址就應該是: 'api/product'
后邊也可以跟著自定義的路由地址, 例如 [HttpPost("create")], 那么這個Action的路由地址就應該是: 'api/product/create'.
[FromBody]?,?請求的body里面包含著方法需要的實體數據, 方法需要把這個數據Deserialize成ProductCreation, [FromBody]就是干這些活的.
客戶端程序可能會發起一個Bad的Request, 導致數據不能被Deserialize, 這時候參數product就會變成null. 所以這是一個客戶端發生的錯誤, 程序為讓客戶端知道是它引起了錯誤, 就應該返回一個Bad Request?400 (Bad Request表示客戶端引起的錯誤)的 Status Code.
傳遞進來的model類型是 ProductCreation, 而我們最終操作的類型是Product, 所以需要進行一個Map操作, 目前還是挨個屬性寫代碼進行Map吧, 以后會改成Automapper.
返回?CreatedAtRoute:?對于POST, 建議的返回Status Code 是?201 (Created), 可以使用CreatedAtRoute這個內置的Helper Method. 它可以返回一個帶有地址Header的Response, 這個Location Header將會包含一個URI, 通過這個URI可以找到我們新創建的實體數據. 這里就是指之前寫的GetProduct(int id)這個方法. 但是這個Action必須有一個路由的名字才可以引用它, 所以在GetProduct方法上的Route這個attribute里面加上Name="GetProduct", 然后在CreatedAtRoute方法第一個參數寫上這個名字就可以了, 盡管進行了引用, 但是Post方法走完的時候并不會調用GetProduct方法. CreatedAtRoute第二個參數就是對應著GetProduct的參數列表, 使用匿名類即可,?最后一個參數是我們剛剛創建的數據實體.?
運行程序試驗一下, 注意需要在Headers里面設置Content-Type: application/json.
4、Validation 驗證
針對上面的Post方法,? 如果請求沒有Body, 參數product就會是null, 這個我們已經判斷了;?如果body里面的數據所包含的屬性在product中不存在, 那么這個屬性就會被忽略.
但是如果body數據的屬性有問題, 比如說name沒有填寫, 或者name太長, 那么在執行action方法的時候就會報錯, 這時候框架會自動拋出500異常, 表示是服務器的錯誤, 這是不對的. 這種錯誤是由客戶端引起的, 所以需要返回400 Bad Request錯誤.
驗證Model/實體, asp.net core 內置可以使用?Data Annotations進行:?
//以下不是本系列教程,就看思路即可,不用敲代碼using System;using System.ComponentModel.DataAnnotations;namespace CoreBackend.Api.Dtos{ public class ProductCreation { [] [] // [MinLength(2, ErrorMessage = "{0}的最小長度是{1}")] // [MaxLength(10, ErrorMessage = "{0}的長度不可以超過{1}")] [] public string Name { get; set; }
[] [] public float Price { get; set; } }}
這些Data Annotation (理解為用于驗證的注解), 可以在System.ComponentModel.DataAnnotation找到, 例如[Required]表示必填, [MinLength]表示最小長度, [StringLength]可以同時驗證最小和最大長度, [Range]表示數值的范圍等等很多.
[Display(Name="xxx")]的用處是, 給屬性起一個比較友好的名字.
其他的驗證注解都有一個屬性叫做 ErrorMessage (string), 表示如果驗證失敗, 就會把ErrorMessage的內容添加到錯誤結果里面去. 這個ErrorMessage可以使用參數,?{0}表示Display的Name屬性,?{1}表示當前注解的第一個變量,?{2}表示當前注解的第二個變量.
在Controller里面添加驗證邏輯:
//以下不是本系列教程,就看思路即可,不用敲代碼 [] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); }if (!ModelState.IsValid) { return BadRequest(ModelState); }
var maxId = ProductService.Max(x => x.Id); var newProduct = new Product { Id = ++maxId, Name = product.Name, Price = product.Price }; ProductService.Add(newProduct);
return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct); }
ModelState: 是一個Dictionary, 它里面是請求提交到Action的Name和Value的對們, 一個name對應著model的一個屬性, 它也包含了一個針對每個提交的屬性的錯誤信息的集合.
每次請求進到Action的時候, 我們在ProductCreationModel添加的那些注解的驗證, 就會被檢查. 只要其中有一個驗證沒通過, 那么ModelState.IsValid屬性就是False. 可以設置斷點查看ModelState里面都有哪些東西.
如果有錯誤的話, 我們可以把ModelState當作 Bad Request的參數一起返回到前臺.
5、PUT請求
put應該用于對model進行完整的更新.?
首先最好還是單獨為Put寫一個Dto Model, 盡管屬性可能都是一樣的, 但是也建議這樣寫, 實在不想寫也可以.
ProducModification.cs
public class ProductModification { [] [] [] public string Name { get; set; }[] [] public float Price { get; set; } }
然后編寫Controller的方法:
//以下不是本系列教程,就看思路即可,不用敲代碼[HttpPut("{id}")] public IActionResult Put(int id, [FromBody] ProductModification product) { if (product == null) { return BadRequest(); }
if (product.Name == "產品") { ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字"); }
if (!ModelState.IsValid) { return BadRequest(ModelState); }
var model = ProductService.SingleOrDefault(x => x.Id == id); if (model == null) { return NotFound(); } model.Name = product.Name; model.Price = product.Price;
// return Ok(model); return NoContent(); }
按照Http Put的約定, 需要一個id這樣的參數, 用于查找現有的model.
由于Put做的是完整的更新, 所以把ProducModification整個Model作為參數.
進來之后, 進行了一套和POST一模一樣的驗證, 這地方肯定可以改進, 如果驗證邏輯比較復雜的話, 到處寫同樣驗證邏輯肯定是不好的, 所以建議使用FluentValidation.
然后, 把ProductModification的屬性都映射查詢找到給Product, 這個以后用AutoMapper來映射.
返回: PUT建議返回NoContent(), 因為更新是客戶端發起的, 客戶端已經有了最新的值, 無須服務器再給它傳遞一次, 當然了, 如果有些值是在后臺更新的, 那么也可以使用Ok(xxx)然后把更新后的model作為參數一起傳到前臺.
?
?
?
五、結語
? ? 好啦,項目搭建就這么愉快的解決了,而且你也應該簡單了解了.Net Core API是如何安裝,創建,各個文件的意義以及如何運作,如何配置等,但是既然是接口,那一定是要前后端一起進行配置,使用,交流的平臺,從上文看出,每次都特別麻煩,而且不直觀,UI 不友好,怎么辦呢?
????下一節我們就使用一個神器 Swagger,一個快速,輕量級的項目RESTFUL接口的文檔在線自動生成+功能測試功能軟件。
?
Github && Gitee
https://github.com/anjoy8/Blog.Core.git
?
NOTE
如何不會使用Git,可以參考
https://www.jianshu.com/p/2b666a08a3b5
?
一起學習,一起進步 ? QQ群:867095512
總結
以上是生活随笔為你收集整理的【 .NET Core 3.0 】框架之二 || 后端项目搭建的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 同时支持EF+Dapper的混合仓储,助
- 下一篇: 基于ASP.NET Core 3.0的A