Rovani's Sandbox

Rovani's Sandbox

About Me
Projects
If you like what you see (view the About Me page for more) and you're interested in exploring opportunities for us to partner up, please reach out to me at david@rovani.net or message me on LinkedIn #OpenToWork

Using an Explicit Model Constructor with a JsonInputFormatter

I may have gone down a long and twisted rabbit hole trying to figure out this problem, but I learned a lot about how model binding along the way, so I consider the whole experiment a productive use of my time, even if I end up ripping it all out in a few weeks. However, since I thought this would be a good idea, I figure others might find a good use for this knowledge. Thus, this is how I am using an explicity constructor when the input is in a JSON format and parameter is bound using the request body.

An early lesson when writing Actions is that, in Core MVC, the [FromBody] attribute is required to use the JsonInputFormatter. Setting a new convention for controllers is possible (instructions on how to), but for now I am going to stick with the tedium of remembering to apply it to every parameter.

The purpose of this is that I want the GeneratedBy and the GeneratedOn parameters to be required for every instance of a Command. This ensures that it won't be set by the user (either the end user or another user in a consuming change) since the properties' set methods are protected.

This poses a problem with using the CreatePatron command as a parameter. The default JsonInputFormatter class uses the JsonSerializer.Deserialize(JsonTextReader, Type) method to create the instance of the model. This method uses the parameterless constructor, which my objects to not have. To solve this problem, I inheritted from the JsonInputFormatter (keeping as much functionality as I could) and proceeded from there.

CommandInputFormatter Constructor

All of the fields that are passed to the JsonInputFormatter constructor are stored as private readonly fields. Therefore, I had to create my own private readonly copies of them to use in the methods that I needed to write.

CommandInputFormatter.CanReadyType

Since I wanted this formatter to only work for these specific classes, I extended the functionality of the CanReadType method to also filter for just models that implement the Command abstract base class.

CommandInputFormatter.ReadRequestBodyAsync

The simplest way for me to change the constructor that JsonInputFormatter uses to generate the model would have been if they had isolated that small piece of the ReadRequestBodyAsync method into its own protected virtual method. However, since they didn't, I have to override the entire thing. Because ASP.NET Core (including MVC) is all open source, I was able to copy the source code from the original and only replace the parts that I needed to.

There are only two lines that differ between the formatters; I was surprised by how little I had to change.

JsonInputFormatterCommandInputFormatter
object model;object model = GetModel(context);
model = jsonSerializer.Deserialize(jsonReader, type);jsonSerializer.Populate(jsonReader, model);

CommandInputFormatter.GetModel

Of course, there was a little more that I had to add, but this piece is very specific to my application. This is the block of code that creates the appropriate Command object using the explicit constructor.

I save the compiled expression in the formatter. There might be a better way to cache the results, but I can worry about that optimization later. I would also like to have a better way to pass in the identity of the user generating this command. This works for my testing purposes, but I need a better way to do this in the future. Additionally, it would be better if I could inject the DateTime.UtcNow value to be able to explicitly set it and test again in unit tests.

Include the CommandInputFormatter

I was not expecting this piece to turn into a three step process. In order to add an an input formatter with parameters, it needs to be configured through an IConfigureOptions<MvcOptions>, which is added via a ServiceDescriptor to the Services collection on the IMvcBuilder in the Startup class. I learned all this by searching through the Github repository. Once I found the path through it all, I just duplicated most of it, adapting it to my use case.

The IConfigureOptions<MvcOptions> is easy to implement. The difficulty was in finding what dependency injections were available for the constructor.

From here, I could have easily just injected the VigilMvcOptionsSetup during startup config. I suspect that there will be more customization that I will want to attach to the IMvcBuilder, so chose to follow the lead from the MVC developers and create an extension method.

The final step, to wire it all together, is to add a call to the extension method in the Startup.ConfigureServices(IServiceCollection) method. It is important that my extension method comes after the AddMvc method because I inject the CommandInputFormatter to the beginning of the InputFormatters collection. Reversing the order would put my input formatter behind all of the defaults, and the JsonInputFormatter would grab all of the requests before my input formatter was ever checked.