Original: Https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore
Released: March 2017
Environment: ASP. NET Core 1.1
Welcome to the first part of the new series, I'll dissect the MVC source code and show you how it works behind the surface. This series will analyze the interior of MVC and, if it feels dull, can stop reading. But personally, I've been reading, debugging and even freaking out, until I finally understand the source code of the ASP (or I think I understand it). By understanding the workings of the framework, we can better use them and solve the problems we encounter more easily.
I will try to explain the source of the understanding, I can not guarantee that their understanding and interpretation is 100% correct, but I will do my best. It is very difficult to explain a piece of code clearly and clearly, and I will show you the MVC source code with a small block of code and a link to the source file for easy tracking. If you do not understand it after reading, I suggest you take some time to read the source code and debug it yourself if necessary. I hope this series will arouse the interest of people like me who like to be inquisitive.
Addmvccore
In this article I will dissect what Addmvccore has done for us, while focusing on a few classes like Applicationpartmanager. The Project.json used in this article is based on the rel/1.1.2 source code and is debugged by running the MVC Sandbox project.
Note: Mvcsandbox is an example project in ASP. NET Core MVC source code. |
Because versions are constantly being updated, some classes and methods may change, especially internally. Always refer to the latest code on GitHub. For Mvcsandbox configureservices I have updated:
Public void configureservices (iservicecollection services) { services. Addmvccore ();}
Addmvccore is the extension method of iservicecollection. This pattern of extension methods is typically used when registering a set of associated services into a service collection (services collection). Addmvccore is one of two extension methods used to register MVC services when building MVC applications. Relative to the Addmvc method, Addmvccore provides fewer subsets of services. Some simple programs that do not need to use all of the MVC features can use Addmvccore. For example, in building rest APIs, there is no need to razor related components, I generally use addmvccore. You can also add additional services manually after Addmvccore, or use the more ADDMVC function directly. Implementation of Addmvccore:
Public StaticImvccorebuilder Addmvccore ( Thisiservicecollection Services) { if(Services = =NULL) { Throw NewArgumentNullException (nameof (services)); } varPartmanager =Getapplicationpartmanager (services); Services. Tryaddsingleton (Partmanager); Configuredefaultfeatureproviders (Partmanager); Configuredefaultservices (services); Addmvccoreservices (services); varBuilder =NewMvccorebuilder (services, Partmanager); returnBuilder;}
The first thing Addmvccore do is get Applicationpartmanager by Getapplicationpartmanager static method, Pass the iservicecollection as a parameter to Getapplicationpartmanager.
Implementation of Getapplicationpartmanager:
Private StaticApplicationpartmanager Getapplicationpartmanager (iservicecollection services) {varManager = Getservicefromcollection<applicationpartmanager>(services); if(Manager = =NULL) {Manager=NewApplicationpartmanager (); varEnvironment = Getservicefromcollection<ihostingenvironment>(services); if(string. IsNullOrEmpty (environment?. ApplicationName)) {returnManager; } varParts =defaultassemblypartdiscoveryprovider.discoverassemblyparts (environment. ApplicationName); foreach(varPartinchparts) {Manager. Applicationparts.add (part); } } returnManager;}
This method first checks whether there is a applicationpartmanager in the currently registered service, usually not, but in rare cases you may have registered other services before calling Addmvccore. Applicationpartmanager If it does not exist, create a new one.
The next code is to calculate the value of the Applicationparts property for Applicationpartmanager. The ihostingenvironment is first obtained from the Services Collection (service collection). If a ihostingenvironment instance is available, it obtains the program name or the package name (Application/assem bly name). The Defaultassemblypartdiscoveryprovider,discoverassemblyparts method is then passed to the static method to return the ienumerable<applicationpart>.
public static ienumerable< Applicationpart> discoverassemblyparts (string Entrypointassemblyname) { var entryassembly = assembly.load (new AssemblyName (entrypointassemblyname)); var context = Dependencycontext.load (Assembly.Load (new AssemblyName (entrypointassemblyname))); return getcandidateassemblies (entryassembly, context). Select (p = new AssemblyPart (p));}
Discoverassemblyparts first obtains encapsulated objects (Assembly object) and Dependencycontex through the encapsulation name (assembly name). This example encapsulates the name "Mvcsandbox". These values are then passed to the Getcandidateassemblies method, Getcandidateassemblies The Getcandidatelibraries method is called.
Internal StaticIenumerable<assembly>getcandidateassemblies (Assembly entryassembly, Dependencycontext dependencycontext) {if(Dependencycontext = =NULL) { //Use the entry assembly as the sole candidate. return New[] {entryassembly}; } returngetcandidatelibraries (Dependencycontext). SelectMany (Library=Library. Getdefaultassemblynames (Dependencycontext)). Select (Assembly.Load);}Internal StaticIenumerable<runtimelibrary>getcandidatelibraries (Dependencycontext dependencycontext) {if(Referenceassemblies = =NULL) { returnEnumerable.empty<runtimelibrary>(); } varCandidatesresolver =Newcandidateresolver (dependencycontext.runtimelibraries, referenceassemblies); returncandidatesresolver.getcandidates ();}
Explain what Getcandidatelibraries has done:
It returns a list of assemblies referenced in the <see cref= "Referenceassemblies"/>, which by default is the primary MVC assembly that we reference and does not contain the assembly for the project itself.
More specifically, the list of available assemblies contains all the MVC assemblies referenced in our solution.
Referenceassemblies is a static hashset<string> defined in the Defaultassemblypartdiscoveryprovider class, which contains 13 MVC default assemblies.
Internal Statichashset<string> referenceassemblies {Get; } =Newhashset<string>(stringcomparer.ordinalignorecase) {"MICROSOFT.ASPNETCORE.MVC", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.ApiExplorer", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc.Cors", "Microsoft.AspNetCore.Mvc.DataAnnotations", "Microsoft.AspNetCore.Mvc.Formatters.Json", "Microsoft.AspNetCore.Mvc.Formatters.Xml", "Microsoft.AspNetCore.Mvc.Localization", "Microsoft.AspNetCore.Mvc.Razor", "Microsoft.AspNetCore.Mvc.Razor.Host", "Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.ViewFeatures"};
Getcandidatelibraries uses the Candidateresolver class to locate and return "candidate". Candidateresolver through Runtime objects (RuntimeLibrary objects) and referenceassemblies constructs. Each run-time object iterates over and adds to a dictionary, checking that the dependency name (dependency name) is unique during the addition process, if not only throwing an exception.
PublicCandidateresolver (ireadonlylist<runtimelibrary> dependencies, iset<string>referenceassemblies) { varDependencieswithnoduplicates =Newdictionary<string, dependency>(stringcomparer.ordinalignorecase); foreach(varDependencyinchdependencies) { if(Dependencieswithnoduplicates.containskey (dependency. Name)) {Throw NewInvalidOperationException (Resources.formatcandidateresolver_differentcasedreference (dependency. Name)); } Dependencieswithnoduplicates.add (dependency. Name, CreateDependency (dependency, referenceassemblies)); } _dependencies=dependencieswithnoduplicates;}
Each dependent object (both runtimelibrary) is stored in the dictionary as a new dependent object. These objects contain a Dependencyclassification property that is used to filter the required libraries (candidates). Dependencyclassification is an enumeration type:
Private enum dependencyclassification{ 0, 1, 2, 3 }
When creating a dependency, if it matches referenceassemblies HashSet, it is marked as mvcreference and the rest is marked as unknown.
private Dependency createdependency (runtimelibrary library, Iset<string > referenceassemblies) { var classification = Dependencyclassification.unknown; if (Referenceassemblies.contains ( Library. Name) {classification = dependencyclassification.mvcreference; return new Dependency (library, classification);}
When the Candidateresolver.getcandidates method is called, the entire dependent object tree is traversed in conjunction with the Computeclassification method. Each dependent object will check all of his subkeys until they match candidate or mvcreference, and the parent dependency is the candidate type. The traversal end returns a ienumerable<runtimelibrary> containing the identified candidates. In this example, only the Mvcsandbox assembly is marked as candidate.
PublicIenumerable<runtimelibrary>getcandidates () {foreach(varDependencyinch_dependencies) { if(Computeclassification (dependency. Key) = =dependencyclassification.candidate) {yield returndependency. Value.library; } }}PrivateDependencyclassification Computeclassification (stringdependency) {Debug.Assert (_dependencies). ContainsKey (dependency)); varCandidateentry =_dependencies[dependency]; if(Candidateentry.classification! =dependencyclassification.unknown) {returncandidateentry.classification; } Else { varClassification =dependencyclassification.notcandidate; foreach(varCandidatedependencyinchcandidateEntry.Library.Dependencies) {varDependencyclassification =computeclassification (candidatedependency.name); if(dependencyclassification = = Dependencyclassification.candidate | |dependencyclassification==dependencyclassification.mvcreference) {classification=dependencyclassification.candidate; Break; }} candidateentry.classification=classification; returnclassification; }}
Discoverassemblyparts converts the returned candidates to a new assemblypart. This object is a simple encapsulation of an assembly that contains only the main encapsulation properties, such as name, type, and so on. Follow-up I might write a separate article to analyze this class.
Finally, the getapplicationpartmanager,assemblyparts was added to the Applicationpartmanager.
var parts = defaultassemblypartdiscoveryprovider.discoverassemblyparts (environment. ApplicationName); foreach (var in parts) { Manager. Applicationparts.add (part); } } return manager;
The returned Applicationpartmanager instance is added to the Services collection through the Addmvccore extension method.
Next Addmvccore calls the static Configuredefaultfeatureproviders method as a parameter, Add Controllerfeatureprovider for Applicationpartmanager's featureproviders.
Private Static void configuredefaultfeatureproviders (Applicationpartmanager manager) { if (!manager. Featureproviders.oftype<controllerfeatureprovider>(). Any ()) { Manager. Featureproviders.add (new Controllerfeatureprovider ());} }
Controllerfeatureprovider will be used to find controllers in Applicationpar instances. I will introduce controllerfeatureprovider in the following blog post. Now let's go on to the last step of Addmovcore. (Applicationpartmanager is now updated)
The private method Configuredefaultservices is called first, and the routing function is turned on by the addrouting extension method provided by Microsoft.AspNetCore.Routing. It provides the necessary services and configurations required to enable the routing feature. This article does not describe this in detail.
Addmvccore Next calls another private method, Addmvccoreservices, which is responsible for registering the MVC Core Services, including framework options, action discovery, selection and invocation, controller factory, model binding, and authentication.
Finally, Addmvccore constructs a new Mvccorebuilder object through Services collection and Applicationpartmanager. This class is called:
Allows fine-grained configuration of the MVC Foundation Service (allows fine grained configuration of essential MVC services.)
The Addmvccore extension method returns Mvccorebuilder,mvccorebuilder containing Iservicecollection and Applicationpartmanager properties. Mvccorebuilder and its extension methods are used to make some additional configuration after Addmvccore initialization. In fact, the first call in the Addmvc method is Addmvccore, and then use Mvccorebuilder to configure the additional services.
Summary
It is not easy to explain all the above problems, so simply summarize them. In this paper, we analyze the MVC underlying code, which mainly implements the service that Mvcs needs to add to iservicecollection. By tracking the creation of the Applicationpartmanager, we learned how MVC creates an internal application model (Applicationmodel) step-by-step. Although we do not see a lot of actual functions, but through the startup of the tracking analysis we found a lot of interesting things, which for the follow-up analysis laid the foundation.
The above is my preliminary study of Addmvccore, a total of 46 additional items registered in the iservcecollection. Next I will further analyze the Addmvc extension method.
Anatomy of ASP. NET Core MVC (Part 1)-Addmvccore (translate)