简体   繁体   English

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

[英]How can I wrap Web API responses(in .net core) for consistency?

I need to return a consistent response with a similar structure returned for all requests.我需要为所有请求返回具有类似结构的一致响应。 In the previous .NET web api, I was able to achieve this using DelegatingHandler (MessageHandlers).在之前的 .NET web api 中,我能够使用 DelegatingHandler (MessageHandlers) 实现这一点。 The object that I want to return will be encapsulated in the Result element.我要返回的对象将封装在 Result 元素中。 So basically the json response will be in this kind of structure:所以基本上 json 响应将采用这种结构:

Example 1:示例 1:

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

Example 2:示例 2:

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

    }
}

In .NET core, it looks like I need to do this via middleware.在 .NET 核心中,看起来我需要通过中间件来做到这一点。 I tried but I don't see a nicer way to extract the content like how in the previous web API when you can call HttpResponseMessage.TryGetContentValue to get the content and wrap it in global/common response model.我尝试过,但我没有看到更好的方法来提取内容,就像在以前的 Web API 中那样,当您可以调用HttpResponseMessage.TryGetContentValue来获取内容并将其包装在全局/通用响应模型中时。

How can I achieve the same in .NET core?如何在 .NET 核心中实现相同的目标?

I created a middleware to wrap the response for consistency.我创建了一个中间件来包装响应以保持一致性。 I also created an extension method to IApplicationBuilder for convenience when registering this middleware.为了方便注册这个中间件,我还为 IApplicationBuilder 创建了一个扩展方法。 So in Startup.cs, register middleware :所以在 Startup.cs 中,注册中间件:

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

    //code removed for brevity.
    ...
}

And here's the middleware code:这是中间件代码:

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;
        }
    }
}

This is an old question but maybe this will help others.这是一个老问题,但也许这会对其他人有所帮助。

In AspNetCore 2(not sure if it applies to previous versions) you can add a Custom OutputFormatter .在 AspNetCore 2(不确定它是否适用于以前的版本)中,您可以添加一个 Custom OutputFormatter Below is an implementation using the builtin JsonOutputFormatter .下面是使用内置JsonOutputFormatter的实现。

Note that this wasn't tested thoroughly and I'm not 100% that changing the context is ok.请注意,这并没有经过彻底的测试,我不是 100% 认为更改上下文是可以的。 I looked in the aspnet source code and it didn't seem to matter but I might be wrong.我查看了 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);
    }
}

and then register it in your Startup class然后在你的 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));
}

For those looking for a modern turn-key solution, you can now use AutoWrapper for this.对于那些正在寻找现代交钥匙解决方案的人,您现在可以使用AutoWrapper

It's very easy to use;它非常易于使用; just add the following to your Startup.cs file:只需将以下内容添加到您的Startup.cs文件中:

app.UseApiResponseAndExceptionWrapper();

I can see at least two options to accomplish this.我可以看到至少有两个选项可以实现这一点。

Firstly, if you want to add this wrapper to all api in the project, you can do this by implementing middleware in the startup.cs part of your project.首先,如果您想将此包装器添加到项目中的所有 api,您可以通过在项目的 startup.cs 部分中实现中间件来实现。 This is done by adding an app.Use just before the app.UseMvc in the "Configure" function in a similar way as follows:这是通过添加一个做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());

}
});

The other option you have is to intercept the call in a controller.您拥有的另一个选择是拦截控制器中的调用。 This can be done by overriding the OnActionExecuted function in the controller.这可以通过覆盖控制器中的OnActionExecuted函数来完成。 Something similar to the following:类似于以下内容:

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

        base.OnActionExecuted(context);
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将 ASP.NET Core 5 Web API 控制器操作的失败模型验证结果包装到另一个类中并将响应返回为 OK - How can I wrap failed model validation result of an ASP.NET Core 5 Web API controller action into another class and return response as OK 如何调整模型验证(.NET Core Web Api)响应中的哪些字段? - How I can adjust which fields I will have in the response of a model validation (.NET Core Web Api)? 如何将文件发送到列表 - .net core web api - 邮递员 - How can I send file to list - .net core web api - postman 如何在ASP.Net core 2.1 Web API中查看“默认控制器”页面? - How can I make a view of a Controller as Default page in ASP.Net core 2.1 Web API? 如何在新的ASP.NET Core中调用Web API非默认构造函数 - How can I call Web API non-default constructor in new ASP.NET Core ASP.NET Core Web API,如何在启动类中访问 HttpContext - ASP.NET Core Web API, How can I access HttpContext in startup class 如何为 ASP.NET Core Web api 添加全局验证? - How can i add a global validation for a ASP.NET Core web api? 如何通过验证将 JSON 正文属性映射为 .NET Core Web API 操作方法的动态参数? - How can I map JSON body properties as dynamic parameters of a .NET Core web API action method with validation? 如何将图像从 UWP 发布到 .NET core web api? - How can I post image from UWP to .NET core web api? 如何在 ASP.NET Core Web API 参数中发送 URL 参数 - How can I send URL parameter in ASP.NET Core Web API parameters
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM