C# DotNet 核心中间件包装响应
C# DotNet Core Middleware Wrap Response
我有一个简单的控制器操作,如下所示:
public Task<IEnumerable<Data>> GetData()
{
IEnumerable<Data> data = new List<Data>();
return data;
}
我希望能够从中间件中检查 return 值,因此 JSON 看起来像
{
"data": [
],
"apiVersion": "1.2",
"otherInfoHere": "here"
}
所以我的负载总是在 data
之内。我知道我可以在控制器级别执行此操作,但我不想在每个动作上都执行此操作。我宁愿在中间件中一劳永逸。
这是我的中间件示例:
public class NormalResponseWrapper
{
private readonly RequestDelegate next;
public NormalResponseWrapper(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
var obj = context;
// DO something to get return value from obj
// Create payload and set data to return value
await context.Response.WriteAsync(/*RETURN NEW PAYLOAD HERE*/);
}
有什么想法吗?
现在得到了值,但是 return 已经晚了
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
object responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
// By now it is to late, above line sets the value that is going to be returned
await context.Response.WriteAsync(new BaseResponse() { data = responseBody }.toJson());
}
}
finally
{
context.Response.Body = originalBody;
}
查看评论以了解您可以做什么来包装响应。
public async Task Invoke(HttpContext context) {
//Hold on to original body for downstream calls
Stream originalBody = context.Response.Body;
try {
string responseBody = null;
using (var memStream = new MemoryStream()) {
//Replace stream for upstream calls.
context.Response.Body = memStream;
//continue up the pipeline
await next(context);
//back from upstream call.
//memory stream now hold the response data
//reset position to read data stored in response stream
memStream.Position = 0;
responseBody = new StreamReader(memStream).ReadToEnd();
}//dispose of previous memory stream.
//lets convert responseBody to something we can use
var data = JsonConvert.DeserializeObject(responseBody);
//create your wrapper response and convert to JSON
var json = new BaseResponse() {
data = data,
apiVersion = "1.2",
otherInfoHere = "here"
}.toJson();
//convert json to a stream
var buffer = Encoding.UTF8.GetBytes(json);
using(var output = new MemoryStream(buffer)) {
await output.CopyToAsync(originalBody);
}//dispose of output stream
} finally {
//and finally, reset the stream for downstream calls
context.Response.Body = originalBody;
}
}
在 .NET Core 3.1 或 .NET 5 中
创建您的响应信封对象。示例:
internal class ResponseEnvelope<T>
{
public T Data { set; get; }
public string ApiVersion { set; get; }
public string OtherInfoHere { set; get; }
}
从 ObjectResultExecutor
派生一个 class
internal class ResponseEnvelopeResultExecutor : ObjectResultExecutor
{
public ResponseEnvelopeResultExecutor(OutputFormatterSelector formatterSelector, IHttpResponseStreamWriterFactory writerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> mvcOptions) : base(formatterSelector, writerFactory, loggerFactory, mvcOptions)
{
}
public override Task ExecuteAsync(ActionContext context, ObjectResult result)
{
var response = new ResponseEnvelope<object>();
response.Data = result.Value;
response.ApiVersion = "v1";
response.OtherInfoHere = "OtherInfo";
TypeCode typeCode = Type.GetTypeCode(result.Value.GetType());
if (typeCode == TypeCode.Object)
result.Value = response;
return base.ExecuteAsync(context, result);
}
}
像这样注入DI
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IActionResultExecutor<ObjectResult>, ResponseEnvelopeResultExecutor>();
而且回复应该有一个信封。
这不适用于原始类型。
我有一个简单的控制器操作,如下所示:
public Task<IEnumerable<Data>> GetData()
{
IEnumerable<Data> data = new List<Data>();
return data;
}
我希望能够从中间件中检查 return 值,因此 JSON 看起来像
{
"data": [
],
"apiVersion": "1.2",
"otherInfoHere": "here"
}
所以我的负载总是在 data
之内。我知道我可以在控制器级别执行此操作,但我不想在每个动作上都执行此操作。我宁愿在中间件中一劳永逸。
这是我的中间件示例:
public class NormalResponseWrapper
{
private readonly RequestDelegate next;
public NormalResponseWrapper(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
var obj = context;
// DO something to get return value from obj
// Create payload and set data to return value
await context.Response.WriteAsync(/*RETURN NEW PAYLOAD HERE*/);
}
有什么想法吗?
现在得到了值,但是 return 已经晚了
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
object responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
// By now it is to late, above line sets the value that is going to be returned
await context.Response.WriteAsync(new BaseResponse() { data = responseBody }.toJson());
}
}
finally
{
context.Response.Body = originalBody;
}
查看评论以了解您可以做什么来包装响应。
public async Task Invoke(HttpContext context) {
//Hold on to original body for downstream calls
Stream originalBody = context.Response.Body;
try {
string responseBody = null;
using (var memStream = new MemoryStream()) {
//Replace stream for upstream calls.
context.Response.Body = memStream;
//continue up the pipeline
await next(context);
//back from upstream call.
//memory stream now hold the response data
//reset position to read data stored in response stream
memStream.Position = 0;
responseBody = new StreamReader(memStream).ReadToEnd();
}//dispose of previous memory stream.
//lets convert responseBody to something we can use
var data = JsonConvert.DeserializeObject(responseBody);
//create your wrapper response and convert to JSON
var json = new BaseResponse() {
data = data,
apiVersion = "1.2",
otherInfoHere = "here"
}.toJson();
//convert json to a stream
var buffer = Encoding.UTF8.GetBytes(json);
using(var output = new MemoryStream(buffer)) {
await output.CopyToAsync(originalBody);
}//dispose of output stream
} finally {
//and finally, reset the stream for downstream calls
context.Response.Body = originalBody;
}
}
在 .NET Core 3.1 或 .NET 5 中
创建您的响应信封对象。示例:
internal class ResponseEnvelope<T> { public T Data { set; get; } public string ApiVersion { set; get; } public string OtherInfoHere { set; get; } }
从 ObjectResultExecutor
派生一个 classinternal class ResponseEnvelopeResultExecutor : ObjectResultExecutor { public ResponseEnvelopeResultExecutor(OutputFormatterSelector formatterSelector, IHttpResponseStreamWriterFactory writerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> mvcOptions) : base(formatterSelector, writerFactory, loggerFactory, mvcOptions) { } public override Task ExecuteAsync(ActionContext context, ObjectResult result) { var response = new ResponseEnvelope<object>(); response.Data = result.Value; response.ApiVersion = "v1"; response.OtherInfoHere = "OtherInfo"; TypeCode typeCode = Type.GetTypeCode(result.Value.GetType()); if (typeCode == TypeCode.Object) result.Value = response; return base.ExecuteAsync(context, result); } }
像这样注入DI
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IActionResultExecutor<ObjectResult>, ResponseEnvelopeResultExecutor>();
而且回复应该有一个信封。 这不适用于原始类型。