23

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). The object that I want to return will be encapsulated in the Result element. So basically the json response will be in this kind of structure:

Example 1:

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

Example 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. 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.

How can I achieve the same in .NET core?

michasaucer
  • 4,562
  • 9
  • 40
  • 91
alltej
  • 6,787
  • 10
  • 46
  • 87
  • Yes, middleware is one control point. You need to buffer the body, re-parse it, update it, and send the result. MVC may also have response filters that let you alter the action result prior to serialization. – Tratcher Nov 23 '16 at 04:15
  • What architecture are you using on your project? If you have n-tier logic, you don't have to wrap your object in your web project, you can do that in business layer or such that layer. If you want to wrap your result after action is executed, so middleware is an option and I don't know anything else. – kizilsu Nov 23 '16 at 08:52
  • @kizilsu in the n-tier architecture, the business layer contains rich domain models. so in the api layer it needs to map to "dumb"/view/dto models that you want to expose to your api. after mapping it, it needs to be set to the Result property/field in the consistent response model. – alltej Dec 15 '16 at 15:49

4 Answers4

28

I created a middleware to wrap the response for consistency. I also created an extension method to IApplicationBuilder for convenience when registering this middleware. So in Startup.cs, register middleware :

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;
        }
    }
}
alltej
  • 6,787
  • 10
  • 46
  • 87
  • Can you show your code on how are you using on Controller, also how are you wrapping during the exception...with the `errormessage`? – Rasik Dec 05 '18 at 03:07
  • 2
    @aakash here is the answer https://stackoverflow.com/questions/52370655/access-modelstate-in-asp-net-core-middleware – Fatih Erol Jul 25 '19 at 13:42
  • 3
    @FatihErol checked! but still incomplete, that doesn't have the implementation when exception occurs. – Rasik Feb 06 '20 at 04:28
  • 1
    And if you use it like `BadRequest("error message")` it will breaks at `JsonConvert.DeserializeObject(readToEnd);` You can use it like `return BadRequest(new NotImplementedException());` – Alexandre Ribeiro Oct 06 '20 at 11:33
8

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. Below is an implementation using the builtin JsonOutputFormatter.

Note that this wasn't tested thoroughly and I'm not 100% that changing the context is ok. I looked in the aspnet source code and it didn't seem to matter but I might be wrong.

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

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));
}
ciprianp
  • 81
  • 1
  • 3
4

For those looking for a modern turn-key solution, you can now use AutoWrapper for this.

It's very easy to use; just add the following to your Startup.cs file:

app.UseApiResponseAndExceptionWrapper();
0

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. 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(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. Something similar to the following:

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

        base.OnActionExecuted(context);
    }
Gary Holland
  • 2,565
  • 1
  • 16
  • 17
  • 1
    I saw this solution before but I was looking for a cleaner solution by avoiding the use of `MemoryStream`. In .NET Framework, I can do this with TryGetContentValue of the HttpResponse message. – alltej Dec 30 '16 at 15:35