ASP.NET Core自定义响应内容
問(wèn)題
在業(yè)務(wù)開(kāi)發(fā)中,對(duì)Web API的返回格式有一定要求,需要是定制化的Json結(jié)構(gòu),用于前端統(tǒng)一處理:
{Status?:?0,Message:?"",Info?:?xxx }Status表示響應(yīng)的狀態(tài)碼,0為成功;
Message表示錯(cuò)誤消息,Status不為0時(shí)返回;
Info表示API返回的實(shí)際數(shù)據(jù),Json格式;
簡(jiǎn)單實(shí)現(xiàn)
當(dāng)然,你可以定義一個(gè)數(shù)據(jù)結(jié)構(gòu)作為每個(gè)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); }但是如果這樣實(shí)現(xiàn),每一個(gè)API方法都必須修改,實(shí)例化一個(gè)ResponseData對(duì)象返回。如果以后業(yè)務(wù)修改,要移除這個(gè)自定義結(jié)構(gòu)又是件麻煩事。
有沒(méi)有一勞永逸、并且更加優(yōu)雅的實(shí)現(xiàn)方式呢?
自定義響應(yīng)內(nèi)容
既然這個(gè)Json結(jié)構(gòu)是在原有的返回?cái)?shù)據(jù)外圍再包了一層,那么我們直接獲取Web API的原始Response.Body,然后格式化成新的JSon在賦值給Response.Body不就可以了!?
但是,實(shí)際驗(yàn)證時(shí)發(fā)現(xiàn)在.NET 5下已經(jīng)無(wú)法改寫,無(wú)任何數(shù)據(jù)返回。示例代碼如下:
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)。
它是用來(lái)處理響應(yīng)壓縮的中間件,也就是說(shuō)對(duì)響應(yīng)做了處理,看看它的實(shí)現(xiàn)方式:
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進(jìn)行了替換:
context.Features.Set<IHttpResponseBodyFeature>(compressionBody);那IHttpResponseBodyFeature到底是個(gè)什么玩意?
“ASP.NET Core 中的請(qǐng)求功能”(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/request-features?view=aspnetcore-5.0)作出了相應(yīng)的解釋:?
ASP.NET Core 在 Microsoft.AspNetCore.Http.Features 中定義了許多常見(jiàn)的 HTTP 功能接口,各種服務(wù)器和中間件共享這些接口來(lái)標(biāo)識(shí)其支持的功能。服務(wù)器和中間件還可以提供自己的具有附加功能的接口。?
ResponseCustomBody
那我們就依葫蘆畫瓢,實(shí)現(xiàn)我們的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();} }關(guān)鍵代碼就是下面這段,我們?nèi)〕鲈柬憫?yīng)內(nèi)容,格式化后再寫入:
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); }最后,我們?cè)俣x一個(gè)中間件使用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);}}? }運(yùn)行效果也能滿足我們的要求:?
結(jié)論
在本文中,我們利用了ASP.NET Core的功能接口實(shí)現(xiàn)了自定義格式響應(yīng)。
你也可以嘗試使用功能接口實(shí)現(xiàn)一些有用的功能。
如果你覺(jué)得這篇文章對(duì)你有所啟發(fā),請(qǐng)關(guān)注我的個(gè)人公眾號(hào)”My IO“,記住我!
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core自定义响应内容的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: GeneralUpdate 2021.0
- 下一篇: WPF等待动画