Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return a response in ASP.NET Core MVC middleware using MVC's content negotiation?

I have some ASP.NET Core MVC middleware to catch unhandled exceptions that I would like to return a response from.

While it is easy to just httpContext.Response.WriteAsync to write a string and e.g. use JsonSerializer to serialise an object to a string, I would like to use the standard serialisation settings and content negotiation so that if I change my default output formatting to XML or a text/xml accept header is sent when I have multiple output formatters configured then XML is returned, as it does if I return an ObjectResult from a controller.

Does anyone know how this can be achieved in middleware?

Here is my code so far which only writes JSON:

public class UnhandledExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IOutputFormatter _outputFormatter;
    private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;

    public UnhandledExceptionMiddleware(RequestDelegate next, JsonOutputFormatter outputFormatter, IHttpResponseStreamWriterFactory streamWriterFactory)
    {
        _next = next;
        _outputFormatter = outputFormatter;
        _streamWriterFactory = streamWriterFactory;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        var error = new ErrorResultModel("Internal Server Error", exception.Message, exception.StackTrace);
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        await _outputFormatter.WriteAsync(new OutputFormatterWriteContext(context, _streamWriterFactory.CreateWriter, typeof(ErrorResultModel), error));
    }
}

where ErrorResultModel is defined as:

public class ErrorResultModel
{
    public string ResultMessage { get; };
    public string ExceptionMessage { get; };
    public string ExceptionStackTrace { get; };

    public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
    {
        ResultMessage = resultMessage;
        ExceptionMessage = exceptionMessage;
        ExceptionStackTrace = exceptionStackTrace;
    }
}
like image 228
Joseph Earl Avatar asked Jan 12 '18 14:01

Joseph Earl


People also ask

Does MVC support content negotiation?

With plain ASP.NET MVC, you can easily build an HTTP façade without learning new things. You can negotiate content fairly easily with just a bit of code in some controller base class or in any method that needs it (or by creating a negotiated ActionResult).

Which is the IActionResult that has content negotiation built in ASP.NET Core Web API?

NET core web APIs, content negotiation works with ObjectResult return type. ObjectResult is derived from ActionResult. So if the API action returns IActionResult, . NET Core automatically wraps the object using ObjectResult concrete implementation and thus content negotiation support is added for those actions.

How does content negotiation work in Web API?

Content negotiation happens when a client specifies the media type it wants as a response to the request Accept header. By default, ASP.NET Core Web API returns a JSON formatted result and it will ignore the browser Accept header. ASP.NET Core supports the following media types by default: application/json.

What is content negotiation MVC?

Content negotiation is the process of selecting the best representation for a given response when there are multiple representations available. Means, depending on the Accept header value in the request, the server sends the response. The primary mechanism for content negotiation in HTTP are these request headers −


1 Answers

This is not possible in ASP.NET Core 2.0 MVC.

This will be possible in 2.1:

    public static class HttpContextExtensions
    {
        private static readonly RouteData EmptyRouteData = new RouteData();
    
        private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
    
        public static Task WriteResultAsync<TResult>(this HttpContext context, TResult result)
            where TResult : IActionResult
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            var executor = context.RequestServices.GetService<IActionResultExecutor<TResult>>();
    
            if (executor == null)
            {
                throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered.");
            }
    
            var routeData = context.GetRouteData() ?? EmptyRouteData;
    
            var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
    
            return executor.ExecuteAsync(actionContext, result);
        }
    }

    public class Program : StartupBase
    {
        public static Task Main(string[] args)
        {
            return BuildWebHost(args).RunAsync();
        }
    
        public static IWebHost BuildWebHost(string[] args)
        {
            return new WebHostBuilder().UseStartup<Program>().UseKestrel().Build();
        }
    
        public override void ConfigureServices(IServiceCollection services)
        {
            services.AddMvcCore().AddJsonFormatters();
        }
    
        public override void Configure(IApplicationBuilder app)
        {
            app.Use((ctx, next) =>
            {
                var model = new Person("Krisian", "Hellang");
    
                var result = new ObjectResult(model);
    
                return ctx.WriteResultAsync(result);
            });
        }
    }
    
    public class Person
    {
        public Person(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }
    
        public string FirstName { get; }
    
        public string LastName { get; }
    }
like image 192
Joseph Earl Avatar answered Sep 30 '22 17:09

Joseph Earl