[英]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.