Reputation: 445
I'm trying to achieve something using ASP.NET Core MVC which seems like it should be trivial but from what I've researched it doesn't seem to be an easy task.
I would like to return both a ViewResult and JSON from a single endpoint (potentially other formats as well but I'll keep it simple for now). Ideally whether I return a ViewResult or JSON would be based on the built-in content negotiation.
Of course one could simply check the accept header inside the controller, if HTML has been requested return the ViewResult, otherwise return the JSON result, but this would be silly as the number of endpoints and the supported formats grew.
I assumed this would be possible to do using custom middleware, I could simply await the response of the endpoint, check what the accept header was that the user agent requested, and then either wrap the result up in a ViewResult or JSONResult. This is the ideal solution as it would work for all endpoints and save a massive amount of code duplication, but it doesn't seem like it is possible.
My overall goal is to have my endpoints set up in such a way that users can use the exact same endpoint whether in a browser or through postman and they'll get the response format they would expect, i.e. either HTML or JSON. Has anybody been able to achieve this result in a way that doesn't require hacky solutions?
Upvotes: 0
Views: 1910
Reputation: 9642
You can implement MVC Action filter to easily achieve such functionality.
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Net.Http.Headers;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class ViewJsonActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
//result must be OkObjectResult
if (context.Result is OkObjectResult okResult)
{
var accept = context.HttpContext.Request.Headers[HeaderNames.Accept];
switch (accept)
{
//changing result to ViewResult
case "text/html":
var controller = (Controller)context.Controller;
context.Result = controller.View(okResult.Value);
break;
case "application/json":
default:
//do nothing
break;
}
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
//do nothing
}
}
Then apply the attribute to the specific action/controller or globally. Always return Ok
from controller as a successful result so filter can process it as expected.
//important: controller should be derived from Controller class to contain View method
public class TestController : Controller
{
[HttpGet]
[ViewJsonActionFilter]
public IActionResult TestViewJson()
{
var model = new ViewJsonTestViewModel
{
Id = 55,
Name = "I'm a test model"
};
return Ok(model);
}
}
If you set request Accept
header to text/html
you will get html, otherwise it will return json.
Upvotes: 1