Discovering MVC - The ASP.NET Core MVC Stack

Published:

Filed under: Vigil Journey

Now that the Vigil Project has breached into the API layer, I started to learn all kinds of new information about what takes place before the Controller's Action method is called and what happens after that method returns. The MVC framework, especially while using Visual Studio, make it incredibly easy to forget about all of the heavy lifting that the framework does – long before my code is ever executed. It is only when I wanted to start changing how pieces worked that I really start to see the true depth and breadth of what Microsoft has created. As I begin to discover new pieces of the framework, I felt that the best way to retain this information is to repeat it; and since no one around me wants to hear about all of this, I'll just type it into a blog post.

Upon First Introduction

My first introduction to MVC (through various tutorials) gave me the impression that the full stack looks basically like this:

Initial Pipeline Assumptions

There's a lot of black box magic going on in that diagram. But you know what? It doesn't matter. When all I wanted to do was respond to a simple request, then all I had to do was create a Controller and an Action and then magic happened, and I got a functioning application. Do I have any clue about how the data got to the method? Or how the Controller was even instantiated in order for something to have an object to call my method? Nope! And that's the point.

The First Challenge - Explicit Constructor

The Create action for the BaseController is simple. It just take an instance of the CreatePatron command and publishes is to the CommandQueue. This is essentially the same as the unit test that proved that a "patron can be created".

[HttpPost]
public IActionResult Create([FromBody]CreatePatron command)
{
    if (command == null || !ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    else
    {
        CommandQueue.Publish(command);
        return Accepted(Url.Action(nameof(Get),
            new { id = command.PatronId }));
    }
}

However, this quickly goes awry the first time I execute the code, because the CreatePatron command (as with all PatronCommand classes) has no default constructor. MVC really wants a default constructor, but I really want to guarantee that the GeneratedBy and GeneratedOn values are properly populated. "Surely," though I, "this should be an easy update and I'll be able to move onto something else."

I was wrong.

What Creates an Instance of My Model

Early in the tutorials I was following, they allude to this magical Binding Handler which takes data from the request and binds it to the model and passes it to the action. I now had an idea of what I was looking for, with the assumption that the Binding Handler is what would create the instance of my model. I also assumed that whatever was doing this work was a result of some default setting.

This was the first time I started really digging into what happens during the Startup portion of the application. Startup is the default class that is created with any new MVC project, and it is wired into the WebHostBuilder during the execution of the Main event. I didn't dig into all of the different methods that automatically get generated, because I had a this specific action that I was trying to find.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc()
}

As a bit of a tangent, this is where I am estatic that .NET Core is open source. For previous frameworks, I would have to scour the web for someone to have already done all of this digging, and decided it was worth it to create a post, and then I would have to manage to find their post, and then hope it was close enough for me to get the answers I needed. Now that everything is open source, I can just go view the code itself to find out what happens.

    public static class MvcServiceCollectionExtensions
    {
        public static IMvcBuilder AddMvc(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            var builder = services.AddMvcCore();

            builder.AddApiExplorer();
            builder.AddAuthorization();

            AddDefaultFrameworkParts(builder.PartManager);

            // Order added affects options setup order

            // Default framework order
            builder.AddFormatterMappings();
            builder.AddViews();
            builder.AddRazorViewEngine();
            builder.AddRazorPages();
            builder.AddCacheTagHelper();

            // +1 order
            builder.AddDataAnnotations(); // +1 order

            // +10 order
            builder.AddJsonFormatters(); // <--- THIS LOOKS PROMISING!!!

            builder.AddCors();

            return new MvcBuilder(builder.Services, builder.PartManager);
        }

What does the AddJsonFormatters() extension method do? I can go look that up. That method calls an internal method that adds Service Descriptor to the service collection to resolve an IConfigureOptions<MvcOptions> into a MvcJsonMvcOptionsSetup. That source code for that class indicates that it adds a JsonInputFormatter to the list of Input Formatters in the MvcOptions object that gets passed to it. Continuing to dive further into the source code, I finally found the magic line of code!

namespace Microsoft.AspNetCore.Mvc.Formatters
{
    public class JsonInputFormatter : TextInputFormatter
    {
        /* snip the constructor */

        public override Task<InputFormatterResult> ReadRequestBodyAsync(
            InputFormatterContext context,
            Encoding encoding)
        {
                    /* snip a bunch of set-up code */
                    try
                    {
                        // HERE IT IS!!!
                        model = jsonSerializer.Deserialize(jsonReader, type);
                    }
                    /* snip clean-up code */
                }
            }
        }
        /* snip the rest of the class */
    }
}

What Do I Know, Now?

Getting the Input Formatter

During startup, using the AddMvc extension method, by default, will add a JsonInputFormatter to the list of Input Formatters. When reading the request body, the ReadRequestBodyAsync using the JSON serializer's Deserialize method. I don't see anyway to injet something to override just that one line - or even the object creation portion.

Zoomed in - this is the tiny piece that I learned of the whole pipeline. I have no idea if this is to scale or not, but it certainly feels like only a small step.

Getting the Input Formatter

What Do I Need To Do?

  1. Inherit from JsonInputFormatter and override the ReadRequestBodyAsync method.
  2. Create a new setup class to inject the new input formatter, implementing IConfigureOptions<MvcOptions>.
  3. Create a new static class and an extension method on IMvcBuilder to add dependency injection for the setup class.
  4. Crib unit tests from the Mvc code base.
  5. Hope that it all really works.