欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

ASP.NET Core自定义响应内容

发布时间:2023/12/4 55 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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自定义响应内容的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。