ASP.NET Core自定义响应内容
問題
在業務開發中,對Web API的返回格式有一定要求,需要是定制化的Json結構,用于前端統一處理:
{Status?:?0,Message:?"",Info?:?xxx }Status表示響應的狀態碼,0為成功;
Message表示錯誤消息,Status不為0時返回;
Info表示API返回的實際數據,Json格式;
簡單實現
當然,你可以定義一個數據結構作為每個API的返回值:
public?class?ResponseData<T> {public?int?Status?{?get;?set;?}?=?0;public?string?Message?{?get;?set;?}public?T?Info?{?get;?set;?}public?ResponseData(T?obj){Info?=?obj;} }[HttpGet] public?ResponseData<IEnumerable<WeatherForecast>>?Get() {var?rng?=?new?Random();var?data?=?Enumerable.Range(1,?5).Select(index?=>?new?WeatherForecast{Date?=?DateTime.Now.AddDays(index),TemperatureC?=?rng.Next(-20,?55),Summary?=?Summaries[rng.Next(Summaries.Length)]}).ToArray();return?new?ResponseData<IEnumerable<WeatherForecast>>(data); }但是如果這樣實現,每一個API方法都必須修改,實例化一個ResponseData對象返回。如果以后業務修改,要移除這個自定義結構又是件麻煩事。
有沒有一勞永逸、并且更加優雅的實現方式呢?
自定義響應內容
既然這個Json結構是在原有的返回數據外圍再包了一層,那么我們直接獲取Web API的原始Response.Body,然后格式化成新的JSon在賦值給Response.Body不就可以了!?
但是,實際驗證時發現在.NET 5下已經無法改寫,無任何數據返回。示例代碼如下:
app.Use(async?(context,?next)?=> {var?newContent?=?string.Empty;using?(var?newBody?=?new?MemoryStream()){context.Response.Body?=?newBody;await?next();context.Response.Body?=?new?MemoryStream();newBody.Seek(0,?SeekOrigin.Begin);newContent?=?new?StreamReader(newBody).ReadToEnd();newContent?+=?",?World!";await?context.Response.WriteAsync(newContent);} });難道這條路走不通?
IHttpResponseBodyFeature
在aspnetcore的源代碼中找到了ResponseCompressionMiddleware(https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs)。
它是用來處理響應壓縮的中間件,也就是說對響應做了處理,看看它的實現方式:
public?async?Task?Invoke(HttpContext?context) {if?(!_provider.CheckRequestAcceptsCompression(context)){await?_next(context);return;}var?originalBodyFeature?=?context.Features.Get<IHttpResponseBodyFeature>();var?originalCompressionFeature?=?context.Features.Get<IHttpsCompressionFeature>();Debug.Assert(originalBodyFeature?!=?null);var?compressionBody?=?new?ResponseCompressionBody(context,?_provider,?originalBodyFeature);context.Features.Set<IHttpResponseBodyFeature>(compressionBody);context.Features.Set<IHttpsCompressionFeature>(compressionBody);try{await?_next(context);await?compressionBody.FinishCompressionAsync();}finally{context.Features.Set(originalBodyFeature);context.Features.Set(originalCompressionFeature);} }它將IHttpResponseBodyFeature進行了替換:
context.Features.Set<IHttpResponseBodyFeature>(compressionBody);那IHttpResponseBodyFeature到底是個什么玩意?
“ASP.NET Core 中的請求功能”(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/request-features?view=aspnetcore-5.0)作出了相應的解釋:?
ASP.NET Core 在 Microsoft.AspNetCore.Http.Features 中定義了許多常見的 HTTP 功能接口,各種服務器和中間件共享這些接口來標識其支持的功能。服務器和中間件還可以提供自己的具有附加功能的接口。?
ResponseCustomBody
那我們就依葫蘆畫瓢,實現我們的ResponseCustomBody:
public?class?ResponseCustomBody?:?Stream,?IHttpResponseBodyFeature {private?readonly?HttpContext?_context;private?readonly?IHttpResponseBodyFeature?_innerBodyFeature;private?readonly?Stream?_innerStream;public?ResponseCustomBody(HttpContext?context,IHttpResponseBodyFeature?innerBodyFeature){_context?=?context;_innerBodyFeature?=?innerBodyFeature;_innerStream?=?innerBodyFeature.Stream;}?public?Stream?Stream?=>?this;public?PipeWriter?Writer?=>?throw?new?NotImplementedException();public?override?bool?CanRead?=>?false;public?override?bool?CanSeek?=>?false;public?override?bool?CanWrite?=>?_innerStream.CanWrite;public?override?long?Length?=>?throw?new?NotImplementedException();public?override?long?Position?{?get?=>?throw?new?NotImplementedException();?set?=>?throw?new?NotImplementedException();?}public?async?Task?CompleteAsync(){await?_innerBodyFeature.CompleteAsync();}public?void?DisableBuffering(){_innerBodyFeature.DisableBuffering();}public?Task?SendFileAsync(string?path,?long?offset,?long??count,?CancellationToken?cancellationToken?=?default){return?_innerBodyFeature.SendFileAsync(path,?offset,?count,?cancellationToken);}public?Task?StartAsync(CancellationToken?cancellationToken?=?default){return?_innerBodyFeature.StartAsync(cancellationToken);}public?override?void?Flush(){_innerStream.Flush();}public?override?int?Read(byte[]?buffer,?int?offset,?int?count){throw?new?NotImplementedException();}public?override?long?Seek(long?offset,?SeekOrigin?origin){throw?new?NotImplementedException();}public?override?void?SetLength(long?value){throw?new?NotImplementedException();}public?override?async?Task?WriteAsync(byte[]?buffer,?int?offset,?int?count,?CancellationToken?cancellationToken){var?json?=?System.Text.Encoding.UTF8.GetString(buffer).TrimEnd('\0');json?=?"{\"Status\":0,?\"Info\":"?+?json?+?"?}";buffer?=?System.Text.Encoding.UTF8.GetBytes(json);count?=?buffer.Length;await?_innerStream.WriteAsync(buffer,?offset,?count,?cancellationToken);}public?override?void?Write(byte[]?buffer,?int?offset,?int?count){throw?new?NotImplementedException();} }關鍵代碼就是下面這段,我們取出原始響應內容,格式化后再寫入:
public?override?async?Task?WriteAsync(byte[]?buffer,?int?offset,?int?count,?CancellationToken?cancellationToken) {var?json?=?System.Text.Encoding.UTF8.GetString(buffer).TrimEnd('\0');json?=?"{\"Status\":0,?\"Info\":"?+?json?+?"?}";buffer?=?System.Text.Encoding.UTF8.GetBytes(json);count?=?buffer.Length;await?_innerStream.WriteAsync(buffer,?offset,?count,?cancellationToken); }最后,我們再定義一個中間件使用ResponseCustomBody替換IHttpResponseBodyFeature:
public?class?ResponseCustomMiddleware?:?IMiddleware {public?async?Task?InvokeAsync(HttpContext?context,?RequestDelegate?next){var?originalBodyFeature?=?context.Features.Get<IHttpResponseBodyFeature>();var?customBody?=?new?ResponseCustomBody(context,?originalBodyFeature);context.Features.Set<IHttpResponseBodyFeature>(customBody);try{await?next(context);}finally{context.Features.Set(originalBodyFeature);}}? }運行效果也能滿足我們的要求:?
結論
在本文中,我們利用了ASP.NET Core的功能接口實現了自定義格式響應。
你也可以嘗試使用功能接口實現一些有用的功能。
如果你覺得這篇文章對你有所啟發,請關注我的個人公眾號”My IO“,記住我!
總結
以上是生活随笔為你收集整理的ASP.NET Core自定义响应内容的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GeneralUpdate 2021.0
- 下一篇: FastGithub让Github畅通无