ASP. net mvc Controller activation, mvccontroller

Source: Internet
Author: User

ASP. net mvc Controller activation, mvccontroller

Recently I took the time to read some of the source code of ASP. NET MVC, and write an article to take notes for future reference.

In the UrlRoutingModule, the request handler is mapped to MvcHandler. Therefore, to activate the Controller, start with MvcHandler. MvcHandler implements three interfaces: IHttpAsyncHandler, IHttpHandler, and IRequiresSessionState. The processing logic is mainly implemented in the synchronous and asynchronous ProcessRequest methods. In general, this method performs the following steps during execution:

Pre-processing (add version information in the response header and remove optional route parameters without assignment) Get ControlerFactory through ControllerBuilder and create Controller using Controller Factory
Call the corresponding method (ExecuteCore or BeginExecute) in the Controller to release the Controller Based on Asynchronous processing.

The first step is to process in the ProcessRequestInit method. This article mainly analyzes how the controller is created in the second step.

The Controller is created through ControllerFactory, and the creation of ControllerFactory is completed in ControllerBuilder. Therefore, let's first understand the working principle of ControllerBuilder.

ControllerBuilder

The source code shows that the ControllerBuilder class does not directly create the controller factory, the creation of ControllerFactory is actually delegated to an instance of the SingleServiceResolver class inherited from the IResolver interface. This can be seen from the GetControllerFactory method, it creates a controller factory by calling the Current attribute of the SingleServiceResolver object.

Public IControllerFactory GetControllerFactory () {return _ serviceResolver. Current; // dependency on the IResolver interface to create a factory}

In addition, the source code also found that the SingleServiceResolver class is at the internal level, which means that external access is not allowed. How does ControllerBuilder use SingleServiceResolver to implement Factory Registration? Continue to read the code. The ControllerBuilder class and SingleServiceResolver class both have oneFunc Type of delegate field, which is called factory delegate,

// ControllerBuilder. csprivate Func
  
   
_ FactoryThunk = () => null; // factory delegate // SingleServiceResolver. csprivate Func
   
    
_ CurrentValueThunk; // factory delegate
   
  

The delegate is used to create a factory. The SetControllerFactory method only modifies the factory delegate field of the ControllerBuilder class, and does not change the factory delegate field of the SingleServiceResolver class,

Public void SetControllerFactory (IControllerFactory controllerFactory) {if (controllerFactory = null) {throw new ArgumentNullException ("controllerFactory");} _ factoryThunk = () => controllerFactory; // change the factory delegate field of ControllerBuilder}

Therefore, you must apply the corresponding changes to the SingleServiceResolver class to implement real registration. We know that if you simply assign values to a reference, changing one reference will not change another reference, for example:

FuncF1 = () => null; FuncF2 = f1; // f1 and f2 point to the same object o = new object (); f1 = () => o; // After f1 is changed, f2 still points to the previous object bool b1 = f1 () = o; // truebool b2 = f2 () = null; // true, f1 ()! = F2 ()

Therefore, when ControllerBuilder instantiates a SingleServiceResolver object, the factory delegate field is not directly assigned to the corresponding field of the SingleServiceResolver object (because of this, the delegate registered by the SetControllerFactory method cannot be applied to the SingleServiceResolver object ), instead, a delegate is used for packaging. In this way, a closure is formed and referenced in the closure, as shown below:

FuncF1 = () => null; FuncF2 = () => f1 (); // encapsulate f1 through delegation to form the closure object o = new object (); f1 = () => o; // After f1 is changed, f2 and f1 are synchronized bool b1 = f1 () = o; // truebool b2 = f2 () = o; // true, f1 () = f2 () // ControllerBuilder. csinternal ControllerBuilder (IResolver
    
     
ServiceResolver) {_ serviceResolver = serviceResolver ?? New SingleServiceResolver
     
      
() => _ FactoryThunk (), // encapsulate the delegate, and the closure references new defacontrocontrollerfactory {ControllerBuilder = this}, "ControllerBuilder. GetControllerFactory ");}
     
    

In this way, the factory delegate in the SingleServiceResolver object will be synchronized with the corresponding field in the ControllerBuilder object, and the SetControllerFactory method will replace the default factory.

Closure reference test code:

Using System; class Program {public static void Main (string [] args) {FuncF1 = () => null; FuncF2 = f1; // f1 and f2 point to the same object o = new object (); f1 = () => o; // After f1 is changed, f2 still points to the previous object bool b1 = f1 () = o; // true bool b2 = f2 () = null; // true, f1 ()! = F2 () Print ("direct Value assignment:"); Print (f1 (), "f1 () = {0}"); Print (f2 (), "f2 () = {0}"); Print (f1 () = f2 (), "f1 () = f2 ()? {0} "); FuncFf1 = () => null; FuncFf2 = () => ff1 (); // encapsulate f1 through delegation to form the closure object oo = new object (); ff1 = () => oo; // After f1 is changed, f2 and f1 are synchronized bool bb1 = ff1 () = oo; // true bool bb2 = ff2 () = oo; // true, f1 () = f2 () Print ("delegate Value assignment:"); Print (ff1 (), "ff1 () = {0 }"); print (ff2 (), "ff2 () = {0}"); Print (ff1 () = ff2 (), "ff1 () = ff2 ()? {0} "); Console. ReadLine ();} static void Print (object mess, string format =" {0} ") {string message = mess = null? "Null": mess. ToString (); Console. WriteLine (string. Format (format, message ));}}

Next, let's take a look at how the SingleServiceResolver class creates an object. This class is a generic class, which means that any type of object can be constructed, not limited to ControllerFactory. In MVC, this class has been applied in many places, such as ControllerBuilder, DefaultControllerFactory, and BuildManagerViewEngine, to create multiple objects.

SingleServiceResolver

This class implements the IResolver interface, which is mainly used to provide instances of the specified type. There are three methods to create objects in the SingleServiceResolver class:

1. private Lazy
  
   
_ CurrentValueFromResolver; // Internal call _ resolverThunk2 and private Func
   
    
_ CurrentValueThunk; // delegation method 3. private TService _ defaultValue; // default value: private Func
    
     
_ ResolverThunk; // IDependencyResolver Method
    
   
  

From the Current method, we can see their priority:

public TService Current{    get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; }}

_currentValueFromResolverActually_resolverThunkEncapsulation, internal or call_resolverThunkSo the priority is:_resolverThunk > _currentValueThunk > _defaultValueThat is, IDependencyResolver> delegation> default mode.

SingleServiceResolver implements a DefaultDependencyResolver object in the constructor by default and encapsulates it into the delegate field._resolverThunk.
The IControllerFactory interface type is specified when the SingleServiceResolver object is instantiated in the ControllerBuilder class. Therefore, the internal SingleServiceResolver object cannot create an object through the IDependencyResolver method, so the role of creating the ControllerFactory object falls_currentValueThunk(Delegated) and_defaultValue(Default mode) As mentioned above, the delegate field in the SingleServiceResolver class actually creates an object by referencing the delegate in the ControllerBuilder class through the closure. In the ControllerBuilder class, the corresponding delegate returns null by default,

private Func
  
    _factoryThunk = () => null;
  

Therefore, the second method of the SingleServiceResolver class also fails by default, so the default method can only be used to provide objects. In the ControllerBuilder class, the default value is DefaultControllerFactory:

Internal ControllerBuilder (IResolver
  
   
ServiceResolver) {_ serviceResolver = serviceResolver ?? New SingleServiceResolver
   
    
() => _ FactoryThunk (), new DefaultControllerFactory {ControllerBuilder = this}, // default value "ControllerBuilder. GetControllerFactory ");}
   
  

Therefore, the DefaultControllerFactory class is used to construct the Controller by default.
When creating a SingleServiceResolver object, you can determine from three points which method is used to create the object:

New SingleServiceResolver
  
   
(// 1. View generic interfaces. If it is an interface or abstract class, IDependencyResolver mode becomes invalid () => _ factoryThunk (), // 2. VIEW _ factoryThunk () whether to return null. If yes, the delegate mode becomes invalid. new DefaultControllerFactory {ControllerBuilder = this}, // 3. If both are invalid, the default value "ControllerBuilder" is used. getControllerFactory ");
  

The above process shows that there are two ways to replace the default object provider:

Replace the default DependencyResolver, which can be achieved through the static SetResolver method of the DependencyResolver class:

CustomDependencyResolver customResolver = new  CustomDependencyResolver();DependencyResolver.SetResolver(customResolver);

Place the preceding statement in the place where the program starts, for example: Application_Start

Use the SetControllerFactory method of the ControllerBuilder class described earlier

Note: The first method has a higher priority.

ControllerFactory

After creating the ControllerFactory object through ControllerBuilder, the following describes how to use this object to create a specific Controller. ControllerFactory implements the IControllerFactory interface.CreateControllerMethod to instantiate the Controller. The internal logic of CreateController is very simple. There are two steps: Get the Controller type and create the Controller object.

Get Controller type

The process of obtaining the Controller Type based on the Controller name requires an in-depth understanding, so that we can better locate errors when encountering problems in the future. The logic for retrieving types is encapsulated in the GetControllerType method. This process is divided into three stages for type search based on whether the route data contains namespace information:

First, if namespace information exists in the current route data, search for the corresponding type based on the Controller name and namespace in the cache. If a unique type is found, this type is returned, find multiple directly throwing exceptions. Second, if no namespace information exists in the current route data, or the corresponding type is not found in the first-stage search, and UseNamespaceFallback==trueIn this case, the namespace information set in ControllerBuilder is obtained, and the type search is performed using the information and controller name in the cache. If a unique type is found, the type is returned, when multiple directly throwing exceptions are found, if no namespace information exists in the routing data and ControllerBuilder, or the corresponding Controller type is not found in both stages, the namespace will be ignored, in the cache, only type search is performed based on the Controller name. If a unique type is found, this type is returned. If multiple types are found, an exception is thrown.

Therefore, the namespace priority is: RouteData> ControllerBuilder

When you search for Data Types in the cache, The ControllerTypeCache. EnsureInitialized method is called to load the Xml cache files stored in the hard disk to a dictionary-type memory cache. If the cached file does not exist, the system traverses all the Assembly referenced by the current application to find the Controller type with all public permissions (Judgment condition: implements the IController interface, non-abstract class, and class name ending with Controller), And then serialize these types of information in xml format to generate a cache file and save it on the hard disk so that it can be loaded directly from the cache file next time, at the same time, the type information is cached in the memory in the form of a dictionary to improve search efficiency. The dictionary key is ControllerName (without a namespace ).

Shows the Controller type search process:

Create a Controller object

After obtaining the Controller type, create a Controller object. In the DefaultControllerFactory class source code, we can see that, similar to ControllerBuilder, the constructor of this class also instantiated A SingleServiceResolver object. according to the method described earlier, we can see at a glance that, this object provides a DefaultControllerActivator object by default.

_ ActivatorResolver = activatorResolver ?? New SingleServiceResolver
  
   
(// 1. The generics are interfaces. The IDependencyResolver mode is invalid () => null, // 2. null is returned, and the new DefaultControllerActivator (dependencyResolver) is invalid ), // 3. If both methods are invalid, use the provided method "DefaultControllerFactory constructor ");
  

In fact, the DefaultControllerFactory class only implements type search. The actual creation process of an object needs to be completed by the DefaultControllerActivator class. By default, the process of creating a Controller by defacontrocontrolleractivator is very simple, because it actually uses a class called defadependdependencyresolver to create a Controller, it is called directly within this class.Activator.CreateInstance(serviceType)Method to complete Object Instantiation.

From the creation process of DefaultControllerFactory and DefaultControllerActivator, we can find that MVC provides objects in multiple ways (IDependencyResolver, delegation, and default, therefore, there are also many ways to expand MVC-related modules.

Data container in Controller

Controller involves several data containers that pass values to views: TempData, ViewData, and ViewBag. The difference between the two is that TempData only stores temporary data, and the data in it is removed after the first read, that is, it can only be read once; ViewData and ViewBag store the same data, however, ViewBag is a dynamic object that encapsulates ViewData.

Public dynamic ViewBag {get {if (_ dynamicViewDataDictionary = null) {_ dynamicViewDataDictionary = new DynamicViewDataDictionary () => ViewData); // encapsulate ViewData} return _ dynamicViewDataDictionary ;}}

The following describes the implementation principle of TempData.

TempData

First, let's take a look at how MSDN explains:

You can use the TempDataDictionary object to transmit data in the same way as the ViewDataDictionary object. However, the data in the TempDataDictionary object is only kept from one request to the next, unless you use the Keep method to mark one or more keys as to be retained. If the key is marked as to be retained, it is retained for the next request.
A typical use of TempDataDictionary objects is to transfer data from another operation method when data is redirected to one operation method. For example, the operation method may store the error information in the TempData attribute of the Controller (which returns the TempDataDictionary object) before calling the RedirectToAction method. Then, the next operation method can handle errors and display the view of error messages.

The TempData feature is that data can be transferred between two actions. It stores a copy of data to the next Action and becomes invalid as the next Action arrives. Therefore, it is used to store data between two actions. For example, in such a scenario, one of your actions accepts some post data and then submits it to another Action for processing and displays it on the page, in this case, you can use TempData to transmit the data.

TempData implements the IDictionary interface. It also contains a private field of the IDictionary type and adds related methods to control the operation of dictionary fields. This is obviously an application of the proxy mode. Because TempData needs to transmit data between actions, it is required to store its own data. TempData relies on the ITempDataProvider interface to load and save data, by default, the SessionStateTempDataProvider object is used to store data in the Session.

The following describes how TempData controls data operations. The source code of TempDataDictionary has the following definition:

internal const string TempDataSerializationKey = "__tempData";private Dictionary
  
    _data;private HashSet
   
     _initialKeys = new HashSet
    
     (StringComparer.OrdinalIgnoreCase);private HashSet
     
       _retainedKeys = new HashSet
      
       (StringComparer.OrdinalIgnoreCase);
      
     
    
   
  

The private dictionary field _ data is the place where the data is actually stored. The hash SET _ initialKeys and _ retainedKeys are used to mark the data. _ initialKeys stores the data key that has not been read, _ retainedKeys stores the keys that can be accessed multiple times.
TempDataDictionary controls data operations mainly because the corresponding data is not immediately deleted from _ data when reading data, instead, the hashset _ initialKeys and _ retainedKeys are used to mark the status of each data entry. Finally, the data is filtered based on the previously marked status when it is saved through ITempDataProvider, in this case, the accessed data is removed.

Related control methods include TryGetValue, Add, Keep, Peek, Remove, and Clear.

1. TryGetValue

public bool TryGetValue(string key, out object value){    _initialKeys.Remove(key);    return _data.TryGetValue(key, out value);}

When reading data, this method removes the corresponding key from the _ initialKeys set. As mentioned earlier, _ initialKeys is used to mark the inaccessible data status, the key is deleted from the set, and the data is deleted from the _ data dictionary when it is saved through ITempDataProvider, the next request cannot access the data corresponding to the key from TempData, that is, the data can only be used in one request.

2. Add

public void Add(string key, object value){    _data.Add(key, value);    _initialKeys.Add(key);}

When adding data, Mark _ initialKeys to indicate that the data corresponding to the key can be accessed.

3. Keep

public void Keep(string key){    _retainedKeys.Add(key);} 

When the Keep method is called, the key is added to _ retainedKeys, indicating that this record can be accessed multiple times. Why can this record be accessed multiple times, you can find the reason from the Save method:

public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider){    // Frequently called so ensure delegate is stateless    _data.RemoveFromDictionary((KeyValuePair
  
    entry, TempDataDictionary tempData) =>        {            string key = entry.Key;            return !tempData._initialKeys.Contains(key)                 && !tempData._retainedKeys.Contains(key);        }, this);    tempDataProvider.SaveTempData(controllerContext, _data);}
  

We can see that each piece of data is retrieved from _ data during storage to determine whether the key of the data exists in _ initialKeys and _ retainedKeys, if none of them exist, the data will be removed from _ data. Therefore, after the keep method adds the key to _ retainedKeys, the data will not be deleted, that is: it can be accessed in multiple requests.

4. Peek

public object Peek(string key){    object value;    _data.TryGetValue(key, out value);    return value;}

It can be seen from the code that this method only obtains data from _ data and does not remove the corresponding key from the _ initialKeys set, therefore, reading data through this method does not affect the data status. This data can still be used in the next request.

5. Remove and Clear

public bool Remove(string key){    _retainedKeys.Remove(key);    _initialKeys.Remove(key);    return _data.Remove(key);}public void Clear(){    _data.Clear();    _retainedKeys.Clear();    _initialKeys.Clear();}

There is nothing to say about the two methods, but the corresponding status is deleted when the data is deleted.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.