In this chapter, we will introduce how the Controller performs its binding function through these components. The main contents of this chapter are as follows:

● Model binding process ○ Obtaining ModelBinder ○ Obtaining ValueProvider ○ Creating ModelMetadata ○ Model binding process for DefaultModelBinder ○ Simple model binding ○ Complex model binding ● Summary

The binding process of the model

Previous articles covered the creation and execution of controllers in MVC (see: ASP.NET has no magic — instantiation and execution of ASP.NET MVC Controller), which includes temporary data loading, Action method name fetching, Action method execution, and temporary data saving.

  

While the model binding process is actually included in the execution of the Action method (ActionInvoker. InvokeAction method), The previous article “ASP.NET has no Magic — ASP.NET MVC Filters” also introduced Action execution from the perspective of filters, one of which is that when the authorization Filter is completed and approved, other filters and Action methods are executed, as shown below:

  

The core code of the Action method is as follows:

  

Parametervalues GetParameterValues GetParameterValues GetParameterValues GetParameterValues GetParameterValues GetParameterValues GetParameterValues GetParameterValues GetParameterValues

  

Get the Action parameter array, iterate over the array, bind the individual parameters using GetParameterValue, and return the value as the object stored in the dictionary with the parameter name as Key. Here is the code for the GetParameterValue method:

  

From the code, there are several steps in the binding process:

1. Obtain ModelBinder based on parameters. 2. Get the value provider from the Controller. 3. Create a model binding context that contains Metadata, model name, value provider, and so on. 4. Complete model binding with obtained ModelBinder. These core steps are then analyzed.

Get ModelBinder

GetModelBinder method is called in the code to obtain a ModelBinder according to the parameter information. The following is the implementation code:

  

The code shows that there are two ways to obtain The ModelBinder. First, obtain the ModelBinder applied to the parameter in the form of feature markers through parameter descriptors. Its core code is as follows (ParameterDescriptor default types are ReflectedParameterDescriptor, obtained by means of reflection features) :

  

Note: more about custom ModelBinder is applied by the feature may refer to: www.codeproject.com/articles/60… When ModelBinder is not specified by a feature on the parameter, ModelBinder is obtained by Binders based on the parameter type, which is essentially the ModelBinderDictionary. The dictionary holds all Modelbinders with their type names as Key. The GetBinder method for the ModelBinderDictionary is as follows:

  

First by ModelBinderProviders lookup (note: by default, ASP.NET MVC didn’t use ModelBinderProvider:www.dotnetcurry.com/aspnet-mvc/…). , then matches the corresponding ModelBinder in the dictionary by type name, and finally finds out from the model type whether ModelBinder is specified by feature. If none of the above is found, use the default ModelBinder, the structure of the ModelBinderDictionary as shown below:

  

Get ValueProvider

ValueProvider is used to provide data. ValueProvider in Controller is directly used:

  

ValueProvider in Controller is a collection of providers created by ValueProviderFactories based on the Controller context:

  

Normally all providers will be created, like the FormValue provider shown below, as long as the Controller context is not empty:

  

However, the Json value provider is special and needs to be in Json format and can be properly deserialized. This explains the problem that the Json value provider exists only when the Json request is sent at the beginning of the article. The following is the code to create the Json value provider. We must ensure that the deserialized object is not empty to save the provider to the dictionary:

  

The deserialization GetDseserializedObject method requires the request type to be Json to deserialize:

  

Create ModelMetadata

ModelMetadata is used to describe the metadata of the model. During model binding, the metadata is contained in the ModelBindingContext object. This context object contains information such as attribute filter and model name in addition to ModelMetadata.

The code shows that the metadata fetching is actually done through a component of the metadata provider. The code is as follows:

  

According to the code analysis, actually using a named CachedDataAnnotationsModelMetadataProvider Metadata providers, their access to the Metadata code is as follows:

  

Create Metadata by instantiating a Metadata object and assigning it to a specific feature in the feature list.

  

Note: ASP.NET MVC actually breaks metadata into the following categories. In addition to data type, mandatory, and other properties, it also includes a number of presentation features that will be used when rendering a View. More on this later:

  

In addition, ASP.NET MVC uses metadata components based on caching mechanism by default to improve performance. Related types are shown in the following figure, which will not be explained in detail in this article:

  

Model binding

When the above steps are complete, the model binding is done with the Initial Acquisition of ModelBinder through ControllerContext(which contains the request data) and BindingContext(which contains the value provider, model information, and so on). Before also introduced ModelBinder is according to the type of parameters to get, such as HttpPostedFileBaseModelBinder type will be used for the file data to complete, the following image code is the code of document model binding:

  

Get file data directly from the requested file based on the model name (that is, the parameter name) in the code, simple and straightforward.

There are very few special types of bindings like files. There are more basic types and custom types of bindings. Microsoft does not create a binder for each of the basic types. So DefaultModelBinder’s binding process is relatively complex compared to a specific type of model binding process.

DefaultModelBinder’s model binding process

As mentioned above, most of the model binding is done using DefaultModelBinder. The following describes how the binder performs model binding:

  

The above code does the following things: 1. Make sure there is enough stack space to execute. Net average size function. 2. If the ModelName is not empty, the data source does not contain values prefixed with the ModelName, and the parameter binding information is not prefixed, re-initialize the binding context, remove FallbackToEmptyPrefix and ModelName attribute compared to the original context, and treat the model as complex model:

  

Note: ASP.NET MVC model binding. In this example, the parameter name obj exists, but the QueryString does not use obj as a prefix, and the action parameter does not use bind as a prefix. Such a type is treated as a complex type, as shown below:

  

3. Get the validation flag and get data from ValueProvider according to the model name. The data obtained is of type ValueProviderResult. (Note: ValueProviderResult is responsible for simple type conversions, as described later, and the validation here is covered. Net is the basic implementation of System.Web, so I will not go into depth) 4. If the model belongs to a complex model, complex model binding is performed.

The default model binder sees the use of simple and complex type concepts, and you can see from the code that the core of model binding is also present in the binding process of simple and complex models.

Simple model binding

We introduced simple types earlier, which are essentially types that can be converted directly from strings. In addition, you can convert complex types to simple types through custom type converters. So for simple model binding, the core is actually to get the corresponding value and use the type converter to convert the value to the target type. Here is the implementation code for the simple model binding:

  

The core of the simple model binding is as follows: 1. If the model type is the same as the type obtained from the value provider, it can be returned directly without conversion. 2. The core of the type conversion method is DefaultModelBinder. ConvertProviderResult, actually is to obtain the type converter to complete the function, The final conversion code is as follows (ValueProviderResult’s ConvertSimpleType method, refer to the decompilated code for more details) :

  

3. The conversion of the simple model takes into account the conversion of array types as well as collection types.

Complex model binding

In general the basic types and some commonly used types are simple types, complex model and is actually made up of a series of simple types, complex model so the binding is to split it into simple type complete assembly again after the binding process, the main code is as follows (due to the complete code is more, this article only introduce the key point, For more information see decomcompiled source code for DefaultModelBinder) :

 1             internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 2         {
 3             object model = bindingContext.Model;
 4             Type modelType = bindingContext.ModelType;
 5             if (model == null && modelType.IsArray)
 6             {
 7                 Type elementType = modelType.GetElementType();
 8                 Type modelType2 = typeof(List<>).MakeGenericType(new Type[]
 9                 {
10                     elementType
11                 });
12                 object collection = this.CreateModel(controllerContext, bindingContext, modelType2);
13                 ModelBindingContext bindingContext2 = new ModelBindingContext
14                 {
15                     ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, modelType2),
16                     ModelName = bindingContext.ModelName,
17                     ModelState = bindingContext.ModelState,
18                     PropertyFilter = bindingContext.PropertyFilter,
19                     ValueProvider = bindingContext.ValueProvider
20                 };
21                 IList list = (IList)this.UpdateCollection(controllerContext, bindingContext2, elementType);
22                 if (list == null)
23                 {
24                     return null;
25                 }
26                 Array array = Array.CreateInstance(elementType, list.Count);
27                 list.CopyTo(array, 0);
28                 return array;
29             }
30             else
31             {
32                 if (model == null)
33                 {
34                     model = this.CreateModel(controllerContext, bindingContext, modelType);
35                 }
36                 Type type = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<, >));
37                 if (type != null)
38                 {
39                     Type[] genericArguments = type.GetGenericArguments();
40                     Type keyType = genericArguments[0];
41                     Type valueType = genericArguments[1];
42                     ModelBindingContext modelBindingContext = new ModelBindingContext();
43                     modelBindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType);
44                     modelBindingContext.ModelName = bindingContext.ModelName;
45                     modelBindingContext.ModelState = bindingContext.ModelState;
46                     modelBindingContext.PropertyFilter = bindingContext.PropertyFilter;
47                     modelBindingContext.ValueProvider = bindingContext.ValueProvider;
48                     ModelBindingContext bindingContext3 = modelBindingContext;
49                     return this.UpdateDictionary(controllerContext, bindingContext3, keyType, valueType);
50                 }
51                 Type type2 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
52                 if (type2 != null)
53                 {
54                     Type type3 = type2.GetGenericArguments()[0];
55                     Type type4 = typeof(ICollection<>).MakeGenericType(new Type[]
56                     {
57                         type3
58                     });
59                     if (type4.IsInstanceOfType(model))
60                     {
61                         ModelBindingContext modelBindingContext2 = new ModelBindingContext();
62                         modelBindingContext2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType);
63                         modelBindingContext2.ModelName = bindingContext.ModelName;
64                         modelBindingContext2.ModelState = bindingContext.ModelState;
65                         modelBindingContext2.PropertyFilter = bindingContext.PropertyFilter;
66                         modelBindingContext2.ValueProvider = bindingContext.ValueProvider;
67                         ModelBindingContext bindingContext4 = modelBindingContext2;
68                         return this.UpdateCollection(controllerContext, bindingContext4, type3);
69                     }
70                 }
71                 this.BindComplexElementalModel(controllerContext, bindingContext, model);
72                 return model;
73             }
74         }
Copy the code

View Code

1. Get the model object from the binding context (note: The BindComplexModel method may be called multiple times because there are recursive calls to binding methods in complex model bindings, and the model object of the binding context is the owning object used to hold the current recursion).

  

2. Create model if model does not exist, first bind complex object model is null:

  

3. Start binding the base model in the model, and you can see from the code that this is actually binding the properties in the model:

  

4. Attribute binding is to obtain all the attributes of the model, and then iterate and complete the binding of all the attributes. The code of attribute binding is as follows: a. Get the type of ModelBinder for the property:

    

B. Call ModelBinder’s BindModel method (execute simple model binding code if the property is simple model, otherwise recursively call complex model binding code)

    

C. Assign the obtained result to the Model object, and then get the property’s validator and verify the property. The error message is saved to the ModelState list:

    

Note: ModelValidatorProvider is a tool used to obtain model validators. There are three built-in validation providers in ASP.NET MVC, as shown in the following figure:

    

Including DataAnnotationsModelValidatorProvider used to obtain through the validator markup features model, namely the ASP.NET there is no magic – ASP.NET MVC model validation, the method of article And DataErrorInfoModelValidatorProvider is a kind of by implementing IDataErrorInfo interface to realize the function of data providers, more information can be reference: docs.microsoft.com/en-us/aspne… www.codeproject.com/articles/39… The final ClientDataTypeModelValidatorProvider for client authentication.

Note: as you can see from the code, the feature tag-based data validation in ASP.NET MVC only validates complex models, and using validation features on simple features is not effective.

    

Test123 is longer than 6, but the model state is still valid.

summary

This article mainly introduces the process of model binding in ASP.NET MVC based on the source code. In brief, the core process of model binding is as follows:

● Get ModelBinder based on parameter type. ● Get ValueProvider, or create a Json ValueProvider if it is a Json request. ● Get ModelMetadata based on feature tags. ● Data binding is based on model types (simple/complex). Simple types are mainly the conversion of strings to other simple types (including array/collection conversion), while complex types are the decomposition of the type itself into simple types, one by one binding and verification process.

Reference: www.codeproject.com/articles/60… www.dotnetcurry.com/aspnet-mvc/… Docs.microsoft.com/en-us/aspne… www.codeproject.com/articles/39… dirk.schuermans.me/? p=749

This paper links: www.cnblogs.com/selimsong/p…

ASP.NET has no magic — directories