如何包装Web API响应(在.net核心中)以保持一致性?

all*_*tej 16 asp.net-core-mvc asp.net-core

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

例1:

{
    "RequestId":"some-guid-abcd-1234",
    "StatusCode":200,
    "Result":
    {
        "Id":42,
        "Todo":"Do Hello World"
    }
}
Run Code Online (Sandbox Code Playgroud)

例2:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

在.NET核心中,看起来我需要通过中间件来实现这一点.我尝试了但是我没有看到更好的方法来提取内容,就像在以前的Web API中调用HttpResponseMessage.TryGetContentValue获取内容并将其包装在全局/通用响应模型中一样.

如何在.NET核心中实现相同的目标?

all*_*tej 15

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

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

    //code removed for brevity.
    ...
}
Run Code Online (Sandbox Code Playgroud)

这是中间件代码:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @aakash这是答案/sf/ask/3665945881/ (2认同)
  • @FatihErol 检查过!但仍然不完整,没有发生异常时的实现。 (2认同)

cip*_*anp 7

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

在 AspNetCore 2(不确定它是否适用于以前的版本)中,您可以添加一个 Custom OutputFormatter. 下面是使用内置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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的 Startup 类中注册它

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));
}
Run Code Online (Sandbox Code Playgroud)


Gar*_*and 0

我可以看到至少有两种选择来实现这一目标。

首先,如果您想将此包装器添加到项目中的所有 api 中,您可以通过在项目的startup.cs 部分中实现中间件来实现。app.Use这是通过在“配置”函数之前添加 来完成的,app.UseMvc方法如下:

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());

}
});
Run Code Online (Sandbox Code Playgroud)

您的另一个选择是拦截控制器中的调用。OnActionExecuted这可以通过覆盖控制器中的函数来完成。类似于以下内容:

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

        base.OnActionExecuted(context);
    }
Run Code Online (Sandbox Code Playgroud)