Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating per-request controller/action based formatters in ASP.NET 5

I'm trying to implement HATEOAS in my ASP rest API, changing the ReferenceResolverProvider.

The problem is, that depending on which controller I use, I'd like to use different ReferenceResolvers, because I need to behave differently for each Controller.

Now I have universal options:

services.AddMvc()
            .AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
            .AddJsonOptions(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
            .AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);

And I want to have something like this:

services.AddMvc()
            .AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
            .AddJsonOptions<RoomsController>(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
            .AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);
like image 778
Kuba Matjanowski Avatar asked Oct 31 '15 19:10

Kuba Matjanowski


People also ask

Which is the method of services used to add input output Formatters in ASP.NET Core Web API?

We can configure the formatters for both, input (to serialize incoming data) and output (to format responses) in AddMvc() method, using InputFormatters and OutputFormatters collections.

What is the difference between controller and ControllerBase?

Controller derives from ControllerBase and adds support for views, so it's for handling web pages, not web API requests. If the same controller must support views and web APIs, derive from Controller . The following table contains examples of methods in ControllerBase .


1 Answers

You seem to be wanting to create a per-controller specific formatters. This can be achieved by using a filter called IResourceFilter. A quick example:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CamelCaseJsonFormatterResourceFilter : Attribute, IResourceFilter
{
    private readonly JsonSerializerSettings serializerSettings;

    public CamelCaseJsonFormatterResourceFilter()
    {
        // Since the contract resolver creates the json contract for the types it needs to deserialize/serialize,
        // cache it as its expensive
        serializerSettings = new JsonSerializerSettings()
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {

    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        // remove existing input formatter and add a new one
        var camelcaseInputFormatter = new JsonInputFormatter(serializerSettings);
        var inputFormatter = context.InputFormatters.FirstOrDefault(frmtr => frmtr is JsonInputFormatter);
        if (inputFormatter != null)
        {
            context.InputFormatters.Remove(inputFormatter);
        }
        context.InputFormatters.Add(camelcaseInputFormatter);

        // remove existing output formatter and add a new one
        var camelcaseOutputFormatter = new JsonOutputFormatter(serializerSettings);
        var outputFormatter = context.OutputFormatters.FirstOrDefault(frmtr => frmtr is JsonOutputFormatter);
        if (outputFormatter != null)
        {
            context.OutputFormatters.Remove(outputFormatter);
        }
        context.OutputFormatters.Add(camelcaseOutputFormatter);
    }
}

// Here I am using the filter to indicate that only the Index action should give back a camelCamse response
public class HomeController : Controller
{
    [CamelCaseJsonFormatterResourceFilter]
    public Person Index()
    {
        return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
    }

    public Person Blah()
    {
        return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
    }

If you are curious about the filter execution order, following is an example of the sequence of them:

Inside TestAuthorizationFilter.OnAuthorization
Inside TestResourceFilter.OnResourceExecuting
Inside TestActionFilter.OnActionExecuting
Inside Home.Index
Inside TestActionFilter.OnActionExecuted
Inside TestResultFilter.OnResultExecuting
Inside TestResultFilter.OnResultExecuted
Inside TestResourceFilter.OnResourceExecuted
like image 107
Kiran Avatar answered Sep 29 '22 09:09

Kiran