前言:從2019年年中入坑.net core已半年有余,總體上來說雖然感覺坑多,但是用起來還是比較香的。本來我是不怎么喜歡寫這類實踐分享或填坑記錄的博客的,因為初步實踐坑多,文章肯定也會有各種錯誤,跟別人優秀的文章比起來,好像我寫的東西沒有什么存在的價值。但是入坑.net core以來,這種思想開始慢慢改變了,畢竟我依靠別人解決問題的文章也不盡是教科書般的存在,但是很使用。所以,把自己的實踐過程記錄出來,一方面是鞏固和完善自己的技術棧,另一方能幫助到其他人,或者跟他人共同探討,也不算閉門造輪子,自娛自樂了吧。.net core web api的實踐記錄,就由中間件的使用開始吧。
1、必要的知識儲備
在閱讀這篇文章的時候,我希望讀者已經了解接口的逆變與協變、泛型、委托等知識點(個人認為這是了解.net各種框架的必備知識),同時也知道.net core的依賴注入、生命周期的相關內容(園子里前幾名的大佬,對這塊都有非常優秀的講解,這里我就不作介紹,有需要的童鞋可以留言,我提供連接)。
2、.net core webapi項目中配置中間件
// 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(); } app.UseMiddleware<RequestMiddleware>(); app.UseMvc(); }
在項目的Startup.cs文件中,找到Configure方法,加上app.UseMiddleware<RequestMiddleware>(); 這里的RequestMiddleware就是自定義的中間件,我們可以簡單看下UseMiddleware的定義:
TMiddleware是一個泛型,使用UseMiddleware傳遞的就是自定義的中間件。
3、自定義中間件的實現
很遺憾我的反編譯工具未能找到UseMiddleware的實現方法,但是結合官網上的介紹(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/write?view=aspnetcore-3.1),自定義的中間件需要構造函數來接收一個RequestDelegate對象,是關於HttpRequest的一個委托,另外還需要一個名為 Invoke
或 InvokeAsync
的公共方法,用於寫一些HttpRequest的預處理邏輯。在我的項目中,我用它來進行參數預處理、登錄預處理、Session預處理以及請求的轉發功能。
public class RequestMiddleware { private readonly RequestDelegate _next; private readonly IConfig config; public RequestMiddleware(RequestDelegate next, IConfig config) { _next = next; this.config = config; } public Task Invoke(HttpContext context) { context.Request.EnableRewind(); //支持context.Request.Body重復讀取,內部調用了EnableBuffering方法,否則在使用部分方法或屬性時會報錯誤System.NotSupportedException: Specified method is not supported,例如context.Request.Body.Position // && context.Request.Path.Value == "/api/Main" if (context.Request.ContentLength != null) { Stream stream = context.Request.Body; byte[] buffer = new byte[context.Request.ContentLength.Value]; stream.Read(buffer, 0, buffer.Length); string querystring = Encoding.UTF8.GetString(buffer); RequestMiddleParam requestEntity = Newtonsoft.Json.JsonConvert.DeserializeObject<RequestMiddleParam>(querystring); if (requestEntity == null) { throw new Exception("無法處理的請求"); } string param = Newtonsoft.Json.JsonConvert.SerializeObject(requestMiddleMapParam); //參數 //todo 參數校驗 byte[] bs = Encoding.UTF8.GetBytes(param); //參數轉化為utf8碼 //context.Request.Body.Seek(0, SeekOrigin.Begin); //context.Request.EnableBuffering(); var ms = new MemoryStream(); context.Request.Body = ms; context.Request.Body.Write(bs, 0, bs.Length); context.Request.Body.Position = 0; //重置context.Request.Body的Stream指針,否則報A non-empty request body is required.錯誤 } // Call the next delegate/middleware in the pipeline return this._next(context); } }
值得注意的是,構造函數可以使用Startup.cs中ConfigureServices方法里的注入項,上面實例代碼中的IConfig就是的。(這里還有一個坑,就是中間件通過構造函數來接收services.AddDbContext的注入項,因為生命周期不一樣)
4、參數預處理遇到的坑
以一個Post請求為例,參數是Json結構,為了判斷Json參數中的某字段是否符合規則,就需要對其進行反序列化,校驗完成后,再序列化填進Body對象中。這里注意下context.Request.EnableRewind();與context.Request.Body.Position = 0;兩行代碼的添加,因為是以流的形式讀取和再寫入參數,EnableRewind方法支持重復讀取,而context.Request.Body.Position的歸0則保證在重新將參數寫入Body后,報A non-empty request body is required.錯誤。
以上就是.net core中間件的簡單介紹了,其實它的作用有些像asp.net的攔截器,將請求攔截做一些預處理,針對請求參數的處理、服務的轉發,甚至登錄校驗等需求,都是個不錯的選擇。如果還想深入了解的同學,可自行去尋找UseMiddleware的實現方式。下一篇文章,我將介紹使用.net core + Redis + Session完成分布式Session共享時遇到的坑,歡迎大家共同探討。