I think that the best way to learn how to code, is to read code. That is why I think the number one tool out there for an aspiring developer is GitHub.
When I found out that the ASP.NET Core team at Microsoft were going to open source their platforms, I was ecstatic. Not just because of the open source movement, but because it would be my chance to learn the techniques used at Microsoft, and how they provide such a wonderful framework to work in.
I don't think my understanding of ASP.NET Core MVC would be half of what it is, without reading the code that actually runs. Documentation at Microsoft is fantastic, but to be able to read the bare bones of their software is the true mentor.
This said, after all this reading of code, I thought it would be a great idea to create posts around their architecture over the coming weeks. This is the first installment of those.
This blog post will provide a general overview of the ASP.NET Core MVC repository on GitHub. It will go through folder structure, project structure, and the general pattern employed by the team. I will also touch on the first topic I feel is one of the core features of the framework: model binding. I am not going to go into value providers here but I will hopefully in the future.
My plan is to release more blog posts as I slowly digest everything. Hopefully I will be able to show off the design and concepts of other popular features such as the razor engine, filters, data annotations, and so on, while at the same time doing them justice.
The main body of work is structured in a similar way to how all the repositories are for the ASP.NET team. They usually start with a "src/" folder and a "test/" folder as shown here:
It turns out that this is not just by chance. Their Korebuild systems seem to necessitate the high-level folder structure of src/, test/, build/. This post won't go any further into Korebuild, as it has become obsolete in favour of using ASP.NET teams internal build tools repository-N.B., it does seem to follow a similar way of functioning. If you would like to learn more about it, the code is available here.
At the top level of the src/ folder, you can see all the functionality that mvc provides out of the box at a glance:
"Microsoft.AspNetCore.Mvc" is the project that ties all the MVC projects together. This is where the main service collection extensions live for adding MVC to the dependency injection engine.
"Microsoft.AspNetCore.Core" is, like the name suggests, the core of the entire MVC architecture. This is where things like ControllerBase are defined, the application model, many definitions of action results, and a lot of formatters and model binding providers.
You then have many other projects that are for specific features. Things such as razor (and the new razor pages), tag helpers, CORS, and so on.
The ASP.NET MVC GitHub repository uses a variety of software design patterns.
Throughout the entire repository for MVC, there exists a multitude of classes with a suffix of -Context. The context pattern is designed to allow the storage of data and/or metadata, that each area of functionality can use together, in order to provide situational results.
The context pattern is the main way that you can extend the framework along with the provider pattern. The framework will create a context for you, and with this context-e.g., the HttpContext- you can make behavioural decisions based on the metadata provided.
The adapter pattern allows existing functionality to be plugged into a new way of working. The classic example of the adapter pattern at work in MVC, is the ValidationAttributeAdapter instances. These implement a IClientModelValidator, and are used to provide compatibility between the client model validation, and the data attribute system.
ASP.NET Model Binding
Model binding is achieved through multiple ways. One way is the use of attributes. For example, you can use the "FromBodyAttribute" to declare that an action parameter should be populated from the body of the http request-e.g., a form post.
The following diagram shows how the repository structure utilises these attributes:
Each provider shown in the diagram is registered with the dependency injection system within "MvcCoreServiceCollectionExtensions". The "DefaultApplicationModelProvider" class will be given a context full of the controller types that have been provided. The controller types will generally be provided through the application part manager which the "ControllerActionDescriptorProvider" uses.
The controller types will then be looped through, and the action methods found will be, with reflection, analysed to try and find any of the binding attributes it can find-e.g., the "FromBodyAttribute".
The provider pattern allows the user of the framework to extend the framework functionality. Usually given a context, you can create your own custom providers, that can range from extending functionality with value providers, model binder providers, and more.
An example of this is the IModelBinderProvider interface, that allows you to extend the functionality of the default model binding engine. Binder provider's are registered against the global MvcOptions, and there are default binders setup by default through the MvcCoreMvcOptionsSetup class.
The IModelBinderProvider's are then used by the ModelBinderFactory, and this factory is then used by the ParameterBinder class that will then be used to bind the value providers to your model. The diagram below shows this in action:
There are further steps in which the ParameterBinder class is used, but this is the general idea of how the core of model binding works.
The use of the global MvcOptions class is key here. The consumer of a framework can add as many custom model binder providers they want to the list registered here. Once a model binder provider returns a valid result, this will be used to produce a binding result.
I have generalised the ASP.NET Core MVC repository architecture. I have discussed the biggest design patterns used to achieve its flexibility, and the general areas they might be found. I have also gone through how the low-level code works when it comes to model binding.
This is part 1 in a series of posts that I will dedicate to the repository. The next post will go on to describe another main feature of the repository.