2016-11-22 92 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)); 
} 
相關問題