C# 如何包装Web API响应(在.net core中)以实现一致性?

C# 如何包装Web API响应(在.net core中)以实现一致性?,c#,asp.net-core,asp.net-core-mvc,C#,Asp.net Core,Asp.net Core Mvc,我需要返回一个一致的响应,并为所有请求返回类似的结构。在以前的.NETWebAPI中,我能够使用DelegatingHandler(MessageHandler)实现这一点。我要返回的对象将封装在Result元素中。因此,json响应基本上是这种结构: 例1: { "RequestId":"some-guid-abcd-1234", "StatusCode":200, "Result": { "Id":42, "Todo":"Do

我需要返回一个一致的响应,并为所有请求返回类似的结构。在以前的.NETWebAPI中,我能够使用DelegatingHandler(MessageHandler)实现这一点。我要返回的对象将封装在Result元素中。因此,json响应基本上是这种结构:

例1:

{
    "RequestId":"some-guid-abcd-1234",
    "StatusCode":200,
    "Result":
    {
        "Id":42,
        "Todo":"Do Hello World"
    }
}
例2:

{
    "RequestId":"some-guid-abcd-1235",
    "StatusCode":200,
    "Result":
    {
        [
            {        
                "Id":42,
                "Todo":"Print Hello World"
            },
            {        
                "Id":43,
                "Todo":"Print Thank you"
            }           
        ]

    }
}
在.NETCore中,看起来我需要通过中间件来实现这一点。我试过了,但是我没有找到一种更好的方法来提取内容,就像在以前的web API中那样,当您可以调用
HttpResponseMessage.TryGetContentValue
来获取内容并将其包装在全局/公共响应模型中时


如何在.NET core中实现同样的功能?

我可以看到至少两个选项来实现这一点

首先,如果要将此包装器添加到项目中的所有api中,可以通过在项目的startup.cs部分实现中间件来实现。这是通过添加一个
应用程序来完成的。在
应用程序之前使用
。在“配置”功能中使用MVC
,方法如下:

app.Use(async (http, next) =>
{
//remember previous body
var currentBody = http.Response.Body;

using (var memoryStream = new MemoryStream())
{
    //set the current response to the memorystream.
    http.Response.Body = memoryStream;

    await next();

    string requestId = Guid.NewGuid().ToString();

    //reset the body as it gets replace due to https://github.com/aspnet/KestrelHttpServer/issues/940
    http.Response.Body = currentBody;
    memoryStream.Seek(0, SeekOrigin.Begin);

    //build our content wrappter.
    var content = new StringBuilder();
    content.AppendLine("{");
    content.AppendLine("  \"RequestId\":\"" + requestId + "\",");
    content.AppendLine("  \"StatusCode\":" + http.Response.StatusCode + ",");
    content.AppendLine("  \"Result\":");
    //add the original content.
    content.AppendLine(new StreamReader(memoryStream).ReadToEnd());
    content.AppendLine("}");

    await http.Response.WriteAsync(content.ToString());

}
});
另一个选项是在控制器中拦截调用。这可以通过覆盖控制器中的
OnActionExecuted
函数来实现。类似于以下内容:

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // 
        // add code to update the context.Result as needed.
        //

        base.OnActionExecuted(context);
    }

我创建了一个中间件来包装响应以实现一致性。我还为IApplicationBuilder创建了一个扩展方法,以方便注册此中间件。因此,在Startup.cs中,注册中间件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //code removed for brevity.
    ...
    app.UseResponseWrapper();

    //code removed for brevity.
    ...
}
以下是中间件代码:

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace RegistrationWeb.Middleware
{
    public class ResponseWrapper
    {
        private readonly RequestDelegate _next;

        public ResponseWrapper(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var currentBody = context.Response.Body;

            using (var memoryStream = new MemoryStream())
            {
                //set the current response to the memorystream.
                context.Response.Body = memoryStream;

                await _next(context);

                //reset the body 
                context.Response.Body = currentBody;
                memoryStream.Seek(0, SeekOrigin.Begin);

                var readToEnd = new StreamReader(memoryStream).ReadToEnd();
                var objResult = JsonConvert.DeserializeObject(readToEnd);
                var result = CommonApiResponse.Create((HttpStatusCode)context.Response.StatusCode, objResult, null);
                await context.Response.WriteAsync(JsonConvert.SerializeObject(result));
            }
        }

    }

    public static class ResponseWrapperExtensions
    {
        public static IApplicationBuilder UseResponseWrapper(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ResponseWrapper>();
        }
    }


    public class CommonApiResponse
    {
        public static CommonApiResponse Create(HttpStatusCode statusCode, object result = null, string errorMessage = null)
        {
            return new CommonApiResponse(statusCode, result, errorMessage);
        }

        public string Version => "1.2.3";

        public int StatusCode { get; set; }
        public string RequestId { get; }

        public string ErrorMessage { get; set; }

        public object Result { get; set; }

        protected CommonApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
        {
            RequestId = Guid.NewGuid().ToString();
            StatusCode = (int)statusCode;
            Result = result;
            ErrorMessage = errorMessage;
        }
    }
}
使用系统;
使用System.IO;
Net系统;
使用System.Threading.Tasks;
使用Microsoft.AspNetCore.Builder;
使用Microsoft.AspNetCore.Http;
使用Newtonsoft.Json;
命名空间注册web.Middleware
{
公共类响应包装器
{
private readonly RequestDelegate\u next;
公共响应包装器(RequestDelegate next)
{
_下一个=下一个;
}
公共异步任务调用(HttpContext上下文)
{
var currentBody=context.Response.Body;
使用(var memoryStream=new memoryStream())
{
//将当前响应设置为memorystream。
context.Response.Body=memoryStream;
等待下一步(上下文);
//重置主体
context.Response.Body=当前主体;
memoryStream.Seek(0,SeekOrigin.Begin);
var readToEnd=新的StreamReader(memoryStream).readToEnd();
var objResult=JsonConvert.DeserializeObject(readToEnd);
var result=CommonApiResponse.Create((HttpStatusCode)context.Response.StatusCode,objResult,null);
wait context.Response.WriteAsync(JsonConvert.SerializeObject(结果));
}
}
}
公共静态类ResponseWrapperExtensions
{
公共静态IAApplicationBuilder UserResponseWrapper(此IAApplicationBuilder)
{
返回builder.UseMiddleware();
}
}
公共类公共响应
{
公共静态CommonApiResponse创建(HttpStatusCode statusCode,对象结果=null,字符串errorMessage=null)
{
返回新的CommonApiResponse(状态码、结果、错误消息);
}
公共字符串版本=>“1.2.3”;
公共int状态码{get;set;}
公共字符串RequestId{get;}
公共字符串错误消息{get;set;}
公共对象结果{get;set;}
受保护的CommonApiResponse(HttpStatusCode statusCode,对象结果=null,字符串errorMessage=null)
{
RequestId=Guid.NewGuid().ToString();
StatusCode=(int)StatusCode;
结果=结果;
ErrorMessage=ErrorMessage;
}
}
}

这是一个老问题,但也许这会帮助其他人

在AspNetCore 2中(不确定是否适用于以前的版本),您可以添加自定义的
输出格式化程序
。下面是一个使用内置
JsonOutputFormatter
的实现

注意这没有经过彻底测试,我也不是100%认为更改上下文是可以的。我查看了aspnet源代码,这似乎无关紧要,但我可能错了

public class CustomJsonOutputFormatter : JsonOutputFormatter
{
    public CustomJsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
        : base(serializerSettings, charPool)
    { }

    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
        {
            var @object = new ApiResponse { Data = context.Object };

            var newContext = new OutputFormatterWriteContext(context.HttpContext, context.WriterFactory, typeof(ApiResponse), @object);
            newContext.ContentType = context.ContentType;
            newContext.ContentTypeIsServerDefined = context.ContentTypeIsServerDefined;

            return base.WriteResponseBodyAsync(newContext, selectedEncoding);
        }

        return base.WriteResponseBodyAsync(context, selectedEncoding);
    }
}
公共类CustomJsonOutputFormatter:JsonOutputFormatter
{
公共CustomJsonOutputFormatter(JsonSerializerSettings serializerSettings,ArrayPool charPool)
:base(序列化设置、字符池)
{ }
公共重写任务WriteResponseBodyAsync(OutputFormatterWriteContext,编码selectedEncoding)
{
if(context.HttpContext.Response.StatusCode==(int)HttpStatusCode.OK)
{
var@object=newAPIResponse{Data=context.object};
var newContext=newoutputformatterwriteContext(context.HttpContext,context.WriterFactory,typeof(ApiResponse),@object);
newContext.ContentType=context.ContentType;
newContext.ContentTypeIsServerDefined=context.ContentTypeIsServerDefined;
返回base.WriteResponseBodyAsync(newContext,selectedEncoding);
}
返回base.WriteResponseBodyAsync(上下文,selectedEncoding);
}
}
然后在启动类中注册它

public void ConfigureServices(IServiceCollection services)
{

        var jsonSettings = new JsonSerializerSettings
        {
            NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };

        options.OutputFormatters.RemoveType<JsonOutputFormatter>();
        options.OutputFormatters.Add(new WrappedJsonOutputFormatter(jsonSettings, ArrayPool<char>.Shared));
}
public void配置服务(IServiceCollection服务)
{
var jsonSettings=新的JsonSerializerSettings
{
NullValueHandling=Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver=新的CamelCasePropertyNamesContractResolver()
};
options.OutputFormatters.RemoveType();
Add(新的WrappedJsonOutputFormatter(jsonSettings,ArrayPool.Shared));
}

对于那些寻求现代解决方案的人,您现在可以使用它

它很容易使用;只需将以下内容添加到
Startup.cs
文件:

app.UseApiRespon