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.
JsonInputFormatter | CommandInputFormatter |
---|---|
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.