2016-11-22 59 views
11

我需要返回一个与所有请求返回类似结构的一致响应。在以前的.NET web api中,我使用DelegatingHandler(MessageHandlers)实现了这一点。我想要返回的对象将封装在Result元素中。所以基本上JSON响应会在这种结构:如何包装Web API响应(在.net核心中)以保持一致性?

例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" 
      }   
     ] 

    } 
} 

在.NET中的核心,它看起来像我需要这样做通过中间件。我尝试过,但我没有看到一种更好的方式来提取内容,比如在以前的Web API中如何调用HttpResponseMessage.TryGetContentValue来获取内容并将其包含在全局/通用响应模型中。

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

+0

是,中间件是一个控制点进行注册。您需要缓冲正文,重新解析它,更新它并发送结果。 MVC也可能有响应过滤器,可以在序列化之前更改动作结果。 – Tratcher

+0

你在你的项目中使用什么架构?如果你有n层逻辑,你不必将自己的对象包装到你的web项目中,你可以在业务层或者层上做到这一点。如果你想在执行动作后包装你的结果,那么中间件是一种选择,我什么都不知道。 – kizilsu

+0

@kizilsu在n层架构中,业务层包含丰富的域模型。所以在api层,它需要映射到你想暴露给你的api的“dumb”/ view/dto模型。在映射之后,需要将其设置为一致性响应模型中的Result属性/字段。 – alltej

回答

0

我可以看到至少有两个选项来完成此操作。首先,如果你想将这个包装器添加到项目中的所有API中,可以通过在项目的startup.cs部分实现中间件来实现。

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

} 
}); 

你有另一种选择是拦截在控制器的呼叫:这是在“配置”功能,以类似的方式只是app.UseMvc前加入app.Use做如下。这可以通过覆盖控制器中的OnActionExecuted功能来完成。类似于以下内容:

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

     base.OnActionExecuted(context); 
    } 
+0

我之前看到过这个解决方案,但是我正在通过避免使用'MemoryStream'来寻找更清洁的解决方案。在.NET Framework中,我可以使用HttpResponse消息的TryGetContentValue来完成此操作。 – alltej

7

我创建了一个中间件来包装响应以保持一致性。为了方便注册这个中间件,我还为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; 
     } 
    } 
} 
0

这是一个老问题,但也许这将帮助别人。

在AspNetCore 2(不知道它是否适用于以前的版本),你可以添加一个自定义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); 
    } 
} 

,然后在您的启动类

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)); 
} 
相关问题