Learning ASP. NET MVC5 framework secrets note-How ASP. net mvc runs (4), mvc5-asp.net
Action execution
As the Controller's base class ControllerBase, its Execute method is mainly used to Execute the target Action method. If the target Action method returns an ActionResult object, it also needs to execute this object to respond to the current request. In the ASP. net mvc Framework, the execution of the two is done through an object called ActionInvoker.
1. ActionInvoker
We also define an excuse IActionInvoker for ActionInvoker. As shown below. This interface defines the unique method InvokeAction used to execute the Action method with the specified name. The first parameter of this method is a ControllerContext object that represents the current Controller context.
public interface IActionInvoker { void InvokeAction(ControllerContext controllerContext, string actionName); }
ControllerContext indicates the encapsulation of the current Controller and request context, which are expressed by the Controller and RequestContext attributes respectively.
public class ControllerContext { public ControllerBase Controller { get; set; } public RequestContext RequestContext { get; set; } }
ControllerBase indicates that the property with the same name of ActionInvoker is initialized in the constructor. In the Execute method, he creates a ControllerContext object by using the RequestContext object as the method parameter, and obtains the target Action object through RouteData contained in RequestContext, finally, you can call the InvokeAction method of ActionInvoker as a parameter in the definition of ControllerBase. In the constructor, The ActionInvoker created by default is an object of the ControllerActionInvoker type.
Public class ControllerActionInvoker: IActionInvoker {public IModelBinder ModelBinder {get; private set;} public ControllerActionInvoker () {this. modelBinder = new DefaultModelBinder ();} public void InvokeAction (ControllerContext controllerContext, string actionName) {// omitted Implementation }}
The purpose of the InvokeAction method is to implement the execution of the Action method. Because the Action method has the corresponding parameters, you must bind the parameters before executing the Action method. ASP. net mvc calls this mechanism Model binding, and this involves another object named ModelBinder.
2. ModelBinder
We provide a simple definition for the interface IModelBinder implemented by ModelBinder. This interface has a unique BindModel method. It obtains an object as a parameter based on the ControllerContext, Model name, and type.
public interface IModelBinder { object BindModel(ControllerContext controllerContext, string modelName,Type modelType); }
The previously defined ControllerActionInvoker shows that the ModelBinder created by default in the constructor is a defamodelmodelbinder object.
As shown in the following code, the data bound to a parameter has four sources: submitted forms, request query strings, RouteData Values, and DataTokens attributes. They are data sets in a dictionary structure. If the request parameter type is string or simple value type, we can directly match the parameter name and Key. For complex types, an empty object is created by reflection based on the provided data type, and corresponding data is provided based on the matching relationship between the property name and Key, and the attribute is assigned a value.
Public class DefaultModelBinder: IModelBinder {public object BindModel (ControllerContext controllerContext, string modelName, Type modelType) {if (modelType. isValueType | typeof (string) = modelType) {object instance; if (GetValueTypeInstance (controllerContext, modelName, modelType, out instance) {return instance;}; activreturn ator. createInstance (modelType);} object modelInstance = Activa Tor. CreateInstance (modelType); foreach (PropertyInfo property in modelType. GetProperties () {if (! Property. CanWrite | (! Property. PropertyType. IsValueType & property. PropertyType! = Typeof (string) {continue;} object propertyValue; if (GetValueTypeInstance (controllerContext, property. name, property. propertyType, out propertyValue) {property. setValue (modelInstance, propertyValue, null) ;}} return modelInstance;} private bool GetValueTypeInstance (ControllerContext controllerContext, string modelName, Type modelType, out object value) {Dictionary <string, object> dataSource = new Dictionary <string, object> (); // data source 1: HttpContext. current. request. form foreach (string key in HttpContext. current. request. form) {if (dataSource. containsKey (key. toLower () {continue;} dataSource. add (key. toLower (), HttpContext. current. request. form [key]);} // data source 2: HttpContext. current. request. queryString foreach (string key in HttpContext. current. request. queryString) {if (dataSource. containsKey (key. toLower () {continue;} dataSource. add (key. toLower (), HttpContext. current. request. queryString [key]);} // data source 3: ControllerContext. requestContext. routeData. values foreach (var item in controllerContext. requestContext. routeData. values) {if (dataSource. containsKey (item. key. toLower () {continue;} dataSource. add (item. key. toLower (), controllerContext. requestContext. routeData. values [item. key]);} // data source 4: ControllerContext. requestContext. routeData. dataTokens foreach (var item in controllerContext. requestContext. routeData. dataTokens) {if (dataSource. containsKey (item. key. toLower () {continue;} dataSource. add (item. key. toLower (), controllerContext. requestContext. routeData. dataTokens [item. key]);} if (dataSource. tryGetValue (modelName. toLower (), out value) {value = Convert. changeType (value, modelType); return true;} return false ;}}
3. ControllerActionInvoker
ControllerActionInvoker that implements the IActionInvoker interface is the default ActionInvoker. In the implemented InvokerAction method, we obtain the MethodInfo object used to describe the corresponding method based on the Action name, and then obtain the ParameterInfo list describing all parameters. For each ParameterInfo object, we use the Model binding method of the ModelBinder object to obtain the source data from the current request and generate the corresponding parameter object.
public class ControllerActionInvoker : IActionInvoker { public IModelBinder ModelBinder { get; private set; } public ControllerActionInvoker() { this.ModelBinder = new DefaultModelBinder(); } public void InvokeAction(ControllerContext controllerContext, string actionName) { MethodInfo methodInfo = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == 0); List<object> parameters = new List<object>(); foreach (ParameterInfo parameter in methodInfo.GetParameters()) { parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType)); } ActionExecutor executor = new ActionExecutor(methodInfo); ActionResult actionResult = (ActionResult)executor.Execute(controllerContext.Controller, parameters.ToArray()); actionResult.ExecuteResult(controllerContext); } }
Next, we create an object of the ActionExecutor type, and call the Execute method of the ActionExecutor object by activating the Controller object and using the parameter list generated by binding the Model as the input parameter, the target Action method is finally executed.
4. ActionExecutor
The execution of the target Action method is ultimately completed by ActionExecute. What method is used to execute the policy? Although ActionExecute is created based on MethodInfo that describes the target Action method, it can execute this method in reflection mode. But he did not do this to achieve higher performance. The execution of the target Action method is ultimately completed using the expression tree.
We can use the expression tree to represent a piece of code as a tree-like data structure, which can be compiled into executable code. Execute the target Action Method Based on the Expression Tree in the Execute method of ActionExecute. As shown below, we create an ActionExecute object based on the MethodInfo object that describes the Action method to be executed, in the static method CreateExecute, The MethodInfo object is used to construct the expression tree used to execute the target method and compile the tree to generate a Func <object, object [], object> type delegate object. The execution of the target Action method is ultimately completed by the delegate object.
internal class ActionExecutor { private static Dictionary<MethodInfo, Func<object, object[], object>> executors = new Dictionary<MethodInfo, Func<object, object[], object>>(); private static object syncHelper = new object(); public MethodInfo MethodInfo { get; private set; } public ActionExecutor(MethodInfo methodInfo) { this.MethodInfo = methodInfo; } public object Execute(object target, object[] arguments) { Func<object, object[], object> executor; if (!executors.TryGetValue(this.MethodInfo, out executor)) { lock (syncHelper) { if (!executors.TryGetValue(this.MethodInfo, out executor)) { executor = CreateExecutor(this.MethodInfo); executors[this.MethodInfo] = executor; } } } return executor(target, arguments); } private static Func<object, object[], object> CreateExecutor(MethodInfo methodInfo) { ParameterExpression target = Expression.Parameter(typeof(object), "target"); ParameterExpression arguments = Expression.Parameter(typeof(object[]), "arguments"); List<Expression> parameters = new List<Expression>(); ParameterInfo[] paramInfos = methodInfo.GetParameters(); for (int i = 0; i < paramInfos.Length; i++) { ParameterInfo paramInfo = paramInfos[i]; BinaryExpression getElementByIndex = Expression.ArrayIndex(arguments, Expression.Constant(i)); UnaryExpression convertToParameterType = Expression.Convert(getElementByIndex, paramInfo.ParameterType); parameters.Add(convertToParameterType); } UnaryExpression instanceCast = Expression.Convert(target, methodInfo.ReflectedType); MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters); UnaryExpression convertToObjectType = Expression.Convert(methodCall, typeof(object)); return Expression.Lambda<Func<object, object[], object>>(convertToObjectType, target, arguments).Compile(); } }
After the Execute method of the ActionExecute object is executed, the returned object represents the value returned by the method of the target Action method. Assume that the returned value is always an ActionResult object, we will directly convert it to the ActionResult type and call its ExecuteResult method to make the final response to the request.
5. ActionResult
We define an ActionResult abstract base class for a specific ActionResult. The abstract base class has an abstract method ExecuteResult whose parameter type is ControllerContext. We finally implement the request in this method.
public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
In the previous example, the Action method returns an object of the RawContentResult type. As the name suggests, RawContentResult is designed to present the content we write unhandled. As shown in the following code snippet, RawContentResult has an Action <TextWrite> type read-only attribute Callback, which we use to write the content to be rendered. In the implemented ExecuteResult method, we execute this Action <TextWrite> object, and as a parameter, It is the TextWrite object represented by the Output attribute of the current HttpResponse, undoubtedly, the content written by the Action <TextWrite> object will be returned to the client as a response.
public class RawContentResult : ActionResult { public Action<TextWriter> Callback { get; private set; } public RawContentResult(Action<TextWriter> action) { this.Callback = action; } public override void ExecuteResult(ControllerContext context) { this.Callback(context.RequestContext.HttpContext.Response.Output); } }