5

I am in the process of rewriting an old ASP.NET WebAPI 2.1 project to ASP.NET Core MVC 2.1. One of the problem I am facing is about porting the old behavior of the service which configure the input and output formatters through custom attributes whom implement IControllerConfiguration interface. I have not been able to find a replacement for this interface nor any alternative to configure formatters on controller-basis, other than injecting them at global level with the AddMvc(options) method.

3 Answers 3

8

I haven't found anything that can be configured at the controller level, but I did find a solution that involves changes to each action where you need this functionality. In my case I needed to customize the JSON serializer settings, which can be done like this for the output:

[HttpGet]
public IActionResult Get()
{
    ...
    return Json(result, _serializerSettings);
}

and like this for input:

[HttpPost]
public IActionResult Post([FromBodyCustomSerializationSettings]MyPostDto postDto)
{
    ...
}

class FromBodyCustomSerializationSettingsAttribute : ModelBinderAttribute
{
    public FromBodyCustomSerializationSettingsAttribute() : base(typeof(MyModelBinder))
    {
        BindingSource = BindingSource.Body;
    }
}

class MyModelBinder : IModelBinder
{
    private readonly BodyModelBinder _bodyModelBinder;

    public MyModelBinder(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> options, IOptions<MvcJsonOptions> jsonOptions, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider)
    {
        var formatters = options.Value.InputFormatters.ToList();
        int jsonFormatterIndex = formatters.FirstIndexOf(formatter => formatter is JsonInputFormatter);
        JsonSerializerSettings myCustomSettings = ...
        formatters[jsonFormatterIndex] = new JsonInputFormatter(loggerFactory.CreateLogger("MyCustomJsonFormatter"), myCustomSettings, charPool, objectPoolProvider, options.Value, jsonOptions.Value);
        _bodyModelBinder = new BodyModelBinder(formatters, readerFactory, loggerFactory, options.Value);
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        return _bodyModelBinder.BindModelAsync(bindingContext);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

5

Actually I found a way. I created an attribute which also implements IResultFilter and here is the OnResultExecuting method, where the magic happens:

public void OnResultExecuting(ResultExecutingContext context)
{
  var objectResult = context.Result as ObjectResult;
  if (objectResult != null)
  {
    var serializerSettings = new JsonSerializerSettings
    {
        ContractResolver = new DefaultContractResolver()
    };

    var jsonFormatter = new JsonOutputFormatter(
        serializerSettings,
        ArrayPool<char>.Shared);

    objectResult.Formatters.Add(jsonFormatter);
  }
}

Basically here I am injecting a custom JSON formatter in every object result, before it is sent to the client. It appears (but I did not find any documentation about this) that in this way ASP.NET Core MVC prefers the injected formatter over the globally defined one.

I hopes it helps other because I was struggling on this...

Comments

2

In order to be able to use @dcstraw's code in ASP .Net Core 3.1, I needed to modify MyModelBinder implementation slightly:

class MyModelBinder : IModelBinder
{
    private readonly BodyModelBinder _bodyModelBinder;

    public MyModelBinder(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> options, IOptions<MvcNewtonsoftJsonOptions> jsonOptions, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider)
    {
        var formatters = options.Value.InputFormatters.ToList();
        int jsonFormatterIndex = formatters.FindIndex(formatter => formatter is NewtonsoftJsonInputFormatter);
        JsonSerializerSettings myCustomSettings = ...
        formatters[jsonFormatterIndex] = new NewtonsoftJsonInputFormatter(loggerFactory.CreateLogger("MyCustomJsonFormatter"), myCustomSettings, charPool, objectPoolProvider, options.Value, jsonOptions.Value);
        _bodyModelBinder = new BodyModelBinder(formatters, readerFactory, loggerFactory, options.Value);
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        return _bodyModelBinder.BindModelAsync(bindingContext);
    }
}

Basically:

  • JsonInputFormatter changes into NewtonsoftJsonInputFormatter
  • MvcJsonOptions changes into MvcNewtonsoftJsonOptions

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.