Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize the ASP.NET MVC Controller Activation system

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "how to realize the ASP.NET MVC Controller activation system". Interested friends may wish to have a look. The method introduced in this paper is simple, fast and practical. Next, let the editor take you to learn "how to implement the ASP.NET MVC Controller activation system"!

1. Controller

We know that the IController interface is implemented directly or indirectly as a type of Controller. As the following code snippet shows, the IController interface contains only one Execute method with a parameter type of RequestContext. When a Controller object is activated, the core operation is to parse the target Action method according to the request context, extract the corresponding data mapping from the request context into the method parameters through the Model binding mechanism, and finally execute the Action method. All of these operations are performed by calling the Execute method.

Public interface IController {void Execute (RequestContext requestContext)

The Execute defined in the IController interface is executed synchronously. To support asynchronous processing of requests, the asynchronous version of the IController interface, System.Web.Mvc.IAsyncController, is defined. As shown in the following code snippet, the asynchronous Controller execution of the IAsyncController interface is accomplished through the combination of BeginExecute/EndExecute methods.

Public interface IAsyncController: IController {IAsyncResult BeginExecute (RequestContext requestContext, AsyncCallback callback, object state); void EndExecute (IAsyncResult asyncResult);}

The abstract class ControllerBase implements the IController interface, which has the following important properties. TemplateData, ViewBag, and ViewData are used to store data or variables passed from Controller to View. TemplateData and ViewData have dictionary-based data structures. Key and Value represent the name and value of variables respectively. The former is used to store variables based on the current HTTP context (the stored data will be reclaimed after the current request is completed). ViewBag and ViewData have the same function, and even correspond to the same data store. The difference between them is that the former is a dynamic object, and we can specify any attribute for it.

Public abstract class ControllerBase: IController {/ / other members public ControllerContext ControllerContext {get; set;} public TempDataDictionary TempData {get; set;} public object ViewBag {[return: Dynamic] get;} public ViewDataDictionary ViewData {get; set;}

We will come across a series of Context objects in ASP.NET MVC. We have introduced RequestContext (HttpContext + RouteData) that represents the context of the request in detail before, and now let's introduce another context type ControllerContext with the following definition.

Public class ControllerContext {/ / other members public ControllerContext (); public ControllerContext (RequestContext requestContext, ControllerBase controller); public ControllerContext (HttpContextBase httpContext, RouteData routeData, ControllerBase controller); public virtual ControllerBase Controller {get; set;} public RequestContext RequestContext {get; set;} public virtual HttpContextBase HttpContext {get; set;} public virtual RouteData RouteData {get; set;}

As the name implies, ControllerContext is based on the context of a Controller object. As shown in the following code, ControllerContext is actually an encapsulation of a Controller object and a RequestContext that correspond to properties of the same name defined in ControllerContext and can be initialized in the constructor. The HttpContextBase and RouteData objects returned through the properties HttpContext and RouteData are actually the core elements that make up RequestContext by default. These four properties of ControllerContext are all readable and writable, and we modify them arbitrarily. When ControllerBase's Execute method is executed, it creates a ControllerContext object based on the incoming ReuqestContext, and subsequent operations can be seen in that context.

When we are developing, the Controller type created by default through VS actually inherits from the abstract class Controller. Many helper methods and properties are defined in this type to make programming easier. As the following code snippet shows, in addition to directly inheriting from ControllerBase, the Controller type explicitly implements the IController and IAsyncController interfaces, as well as four interfaces that represent the four ASP.NET MVC filters (AuthorizationFilter, ActionFilter, ResultFilter, and ExceptionFilter).

Public abstract class Controller: ControllerBase, IController, IAsyncController, IActionFilter, IAuthorizationFilter, IExceptionFilter, IResultFilter, IDisposable,... {/ / omit member}

II. ControllerFactory

ASP.NET MVC defines the corresponding factory for the activation of Controller, which we collectively call ControllerFactory, and all ControllerFactory implement the interface IControllerFactory interface. As the following code snippet shows, the activation of the Controller object is ultimately done through the IControllerFactory's CreateController method, whose two parameters represent the current request context and the name of the Controller obtained from the routing information (originally derived from the request address).

Public interface IControllerFactory {IController CreateController (RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior (RequestContext requestContext, string controllerName); void ReleaseController (IController controller);} public enum SessionStateBehavior Default, Required, ReadOnly, Disabled}

Before the process is responsible for creating the Controller to process the request, the ControllerFactory also needs to implement the release and recycling of the Controller after the request processing is completed, which is implemented in the ReleaseController method. Another method of IControllerFactory, the GetControllerSessionBehavior method, returns an SessionStateBehavior enumeration. Readers familiar with ASP.NET should be familiar with SessionStateBehavior, which is used to represent the mode of session state support during request processing, and its four enumerated values have the following meanings:

Default: use the default ASP.NET logic to determine the session state behavior of the request.

Required: enables full read-write session state behavior for the request.

ReadOnly: enables read-only session state for requests.

Disabled: disables session state.

For the Default option, ASP.NET determines the specific session state control behavior by whether the mapped HttpHandler type implements the relevant interface. The IRequiresSessionState and IRequiresSessionState interfaces are defined under the System.Web.SessionState namespace, and as shown in the following code snippet, both are empty interfaces with no members (we generally call them tagged interfaces), and IReadOnlySessionState inherits from IRequiresSessionState. If HttpHandler implements interface IReadOnlySessionState, it means ReadOnly mode, and if only IRequiresSessionState is implemented, Required mode is adopted.

1: public interface IRequiresSessionState 2: {} 3: public interface IReadOnlySessionState: IRequiresSessionState 4: {}

Exactly what kind of session state behavior is adopted depends on the current HTTP context (HttpContext.Current). For previous versions, we could not dynamically modify the session state behavior mode of the current HTTP context. ASP.NET 4.0 defined the following SetSessionStateBehavior method for HttpContext so that we were free to choose the session state behavior mode. The same method is also defined in HttpContextBase, and its subclass HttpContextWrapper overrides this method and internally calls the encapsulated HttpContext method of the same name.

Public sealed class HttpContext: IServiceProvider, IPrincipalContainer {/ / other member public void SetSessionStateBehavior (SessionStateBehavior sessionStateBehavior);} public class HttpContextBase: IServiceProvider {/ / other member public void SetSessionStateBehavior (SessionStateBehavior sessionStateBehavior);}

III. ControllerBuilder

The ControllerFactory used to activate the Controller object is eventually registered with the ASP.NET MVC application through ControllerBuilder. As shown in the following code, ControllerBuilder defines a static read-only property Current that returns the current ControllerBuilder object, which is a global object applied for the entire Web. Two SetControllerFactory methods overload the type or instance used to register ControllerFactory, while the GetControllerFactory method returns a specific ControllerFactory object.

Public class ControllerBuilder public IControllerFactory GetControllerFactory (); public void SetControllerFactory (Type controllerFactoryType); public void SetControllerFactory (IControllerFactory controllerFactory); public HashSet DefaultNamespaces {get;} public static ControllerBuilder Current {get;}}

Specifically, if we are of the registered ControllerFactory type, then GetControllerFactory will create a specific ControllerFactory by reflecting the registered type (calling the static method CreateInstance of Activator) (the system will not cache the created Controller); if we are registering a specific ControllerFactory object, the object will be returned directly from the GetControllerFactory.

After being intercepted by the ASP.NET routing system, a RouteData object is generated to encapsulate the routing information, and the name of the target Controller is contained in the RouteValueDisctionary object represented by the Values attribute of the RouteData, and the corresponding Key is "controller". By default, this name as routing data can only help us resolve the type name of Controller. If we define multiple Controller classes with the same name in different namespaces, it will cause the activation system to be unable to determine the specific type of Controller and throw an exception.

To solve this problem, we must set different priorities for namespaces that define Controller types of the same name. Specifically, we have two ways to raise the priority of namespaces. The * way is to specify a list of namespaces when calling RouteCollection's extension method MapRoute. The list of namespaces specified in this way is saved in the RouteValueDictionary dictionary represented by the DataTokens attribute of the Route object, with the corresponding Key of "Namespaces".

Public static class RouteCollectionExtensions {/ / other members public static Route MapRoute (this RouteCollection routes, string name, string url, string [] namespaces); public static Route MapRoute (this RouteCollection routes, string name, string url, object defaults, string [] namespaces); public static Route MapRoute (this RouteCollection routes, string name, string url, object defaults, object constraints, string [] namespaces);}

Another way to increase the priority of namespaces is to add them to the default list of namespaces in the current ControllerBuilder. As you can see from the definition of ControllerBuilder given above, it has a read-only property of type HashSet, DefaultNamespaces, which represents such a default list of namespaces. For these two different ways of raising the priority of namespaces, the former (through route registration) specifies that namespaces have higher priority.

An example demonstrates how to raise the priority of a namespace

To give readers a deep impression of how to raise the priority of namespaces, let's do a simple example demonstration. We use the project template provided by Visual Studio to create an empty ASP.NET MVC application and register the code with the default route shown below.

Public class MvcApplication: System.Web.HttpApplication {public static void RegisterRoutes (RouteCollection routes) {routes.MapRoute (name: "Default", url: "{controller} / {action} / {id}", defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}) } protected void Application_Start () {/ / other operations RegisterRoutes (RouteTable.Routes) }} public class MvcApplication: System.Web.HttpApplication {public static void RegisterRoutes (RouteCollection routes) {routes.MapRoute (name: "Default", url: "{controller} / {action} / {id}", defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}) } protected void Application_Start () {/ / other operations RegisterRoutes (RouteTable.Routes);}}

Then we add a .cs file in the Controllers directory and define two Controller classes with the same name in that file. As the following code snippet shows, these two HomeCotroller classes are defined in the namespace Artech.MvcApp and Artech.MvcApp.Controllers, respectively, while the Index operation returns a ContentResult object with the full name of the Controller type as content.

Namespace Artech.MvcApp.Controllers {public class HomeController: Controller {public ActionResult Index () {return this.Content (this.GetType () .FullName);}} namespace Artech.MvcApp {public class HomeController: Controller {public ActionResult Index () {return this.Content (this.GetType (). FullName) }}

Now let's run the Web application directly. Because multiple Controller matches the registered routing rules, ASP.NET MVC's Controller activation system cannot determine which type of Controller to target should be selected, so the error shown in the following figure occurs.

The two namespaces that currently define HomeController have the same priority, so let's now define one of them in the default namespace list of the current ControllerBuilder to raise the matching priority. As shown in the following code snippet, in the Application_Start method of Global.asax, we add the namespace "Artech.MvcApp.Controllers" to the list of namespaces shown by the DefaultNamespaces attribute of the current ControllerBuilder.

1: public class MvcApplication: System.Web.HttpApplication 2: {3: protected void Application_Start () 4: {5: / / other operations 6: ControllerBuilder.Current.DefaultNamespaces.Add ("Artech.MvcApp.Controllers"); 7:} 8:}

For two HomeController that match registered routing rules at the same time, because the "Artech.MvcApp.Controllers" namespace has a higher matching priority, all defined HomeController will be selected, as can be seen from the running results shown in the following figure.

To verify which namespace is specified at the time of route registration and which is the current ControllerBuilder namespace has higher matching priority, we modify the route registration code defined in Global.asax. As the following code snippet shows, we specify the namespace ("Artech.MvcApp") when we call the MapRoute method of the static property Routes of RouteTable to register the route.

Public class MvcApplication: System.Web.HttpApplication {public static void RegisterRoutes (RouteCollection routes) {routes.MapRoute (name: "Default", url: "{controller} / {action} / {id}", defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}) Namespaces:new string [] {"Artech.MvcApp"}) } protected void Application_Start () {/ / other operations RegisterRoutes (RouteTable.Routes); ControllerBuilder.Current.DefaultNamespaces.Add ("Artech.MvcApp.Controllers");}

Running our program again will get the results shown in figure 3-3 in the browser, from which you can see that the HomeController defined in the namespace "Artech.MvcApp" is finally selected. It can be seen that the namespace executed in the route registration process has a higher matching priority than the default namespace of the current ControllerBuilder, and the former can be regarded as a backup of the latter.

The namespace specified when the route is registered has a higher matching priority than the default namespace of the current ControllerBuilder, but has the same matching priority for all namespaces in both sets. In other words, the new namespace used to assist in parsing the Controller class is divided into three echelons, namely, routing namespace, ConrollerBuilder namespace and Controller type namespace. If the previous echelon cannot correctly resolve the type of the target Controller, the namespace of the latter echelon is used as a backup; on the contrary, if multiple matching Controller types are obtained by parsing according to the namespace of an echelon, an exception will be thrown directly.

Now we have made the following changes to the routing registration code in this example, specifying two namespaces for the registered routing object (one is the namespace where the two HomeContrller are located), and running our program will still get the error shown in the figure.

Public class MvcApplication: System.Web.HttpApplication {public static void RegisterRoutes (RouteCollection routes) {routes.MapRoute (name: "Default", url: "{controller} / {action} / {id}", defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}, namespaces: new string [] {"Artech.MvcApp") "Artech.MvcApp.Controllers"}) } protected void Application_Start () {/ / other operations RegisterRoutes (RouteTable.Routes);}}

The namespace of the routing object for Area

The route map for an Area is registered through the corresponding AreaRegistration, specifically by calling the MapRoute method of the AreaRegistrationContext object in the RegisterArea method of the AreaRegistration. If a string representing the namespace is specified in the call to the MapRoute method, it is automatically used as the namespace of the registered routing object, otherwise the string representing the namespace of the AreaRegistration is appended with the ". *" suffix as the namespace of the routing object. The "namespace of the routing object" here refers to an array of strings in the RouteValueDictionary object whose Key is "Namespaces" represented by the DataTokens property of the Route object, which is eventually transferred to the DataTokens of the generated RouteData.

In addition, when calling the MapRoute method of AreaRegistrationContext, an entry with the Key of "UseNamespaceFallback" is added to the registered Route object DataTokens to indicate whether to use a fallback namespace to resolve the Controller type. If the registered object has a namespace (the namespace is specified when the MapRoute method is called or the corresponding AreaRegistration type is defined in a namespace), the value of the entry is False; otherwise True. This entry is also reflected in the DataTokens property of the RouteData object generated through the Route object. [about ASP.NET MVC routing, there is a detailed introduction in my article "ASP.NET MVC routing extension: route Map"]

In the process of parsing the Controller real type, the Controller type is parsed first through the namespace contained in the RouteData. If Controller type resolution fails, it is determined whether to use the "backup" namespace to parse by the UseNamespaceFallback value contained in the RouteValueDictionary object represented by the DataTokens attribute of RouteData. Specifically, if the value is True or does not exist, it is first resolved through the namespace of the current ControllerBuilder, and if it fails, the namespace is ignored and the type name is directly matched; otherwise, an exception is thrown directly because the matching Controller cannot be found.

We use specific examples to illustrate this problem. In an empty Web application created through Visual Studio's ASP.NET MVC project, we add an Area named Admin, and IDE defaults to adding the following AdminAreaRegistration type.

1: namespace Artech.MvcApp.Areas.Admin 2: {3: public class AdminAreaRegistration: AreaRegistration 4: {5: public override string AreaName 6: {7: get {return "Admin" } 8:} 9: public override void RegisterArea (AreaRegistrationContext context) 10: {11: context.MapRoute ("Admin_default", "Admin/ {controller} / {action} / {id}", 12: new {action = "Index", id = UrlParameter.Optional} 13:); 14:} 15:} 16:}

The AdminAreaRegistration type is defined in the namespace Artech.MvcApp.Areas.Admin. Now we add a Controller class to the Area, which is called HomeController. By default, the Controller type we added has the same namespace as AdminAreaRegistration, but now we deliberately change the namespace to Artech.MvcApp.Areas.

Namespace Artech.MvcApp.Areas {public class HomeController: Controller {public ActionResult Index () {return Content ("...");}}

Now when we access the Index operation of HomeController with Area Admin through the matching URL (/ Admin/Home/Index) in the browser, we get an error with a HTTP status of 404 as shown in the following figure. This is because the parsing of Controller types is done strictly according to the namespace of the corresponding AreaRegistration, and it is obviously impossible to find the corresponding Controller type in this scope.

IV. Activation of Controller and URL routing

The ASP.NET routing system is a barrier for HTTP requests to arrive at the server. It matches the intercepted requests according to the registered routing rules and parses the routing information containing the target Controller and Action names. While ControllerBuilder currently has a ControllerFactory for activating Controller objects, let's see how the two combine.

Through the introduction of "the implementation principle of ASP.NET routing system: the dynamic Mapping of HttpHandler", we know that the core of ASP.NET routing system is a custom HttpModule called UrlRoutingModule. The implementation of routing is realized by registering the dynamic mapping of PostResolveRequestCache events of HttpApplication representing the current Web application to HttpHandler. Specifically, it matches the request and obtains a RouteData object through the global routing table represented by the static attribute Routes of RouteTable. RouteData has an attribute RouteHandler that implements the interface IRouteHandler, and the HttpHandler that is ultimately mapped to the current request is obtained through the GetHttpHandler method of this attribute.

For ASP.NET MVC applications, the RouteHandler attribute type of RouteData is MvcRouteHandler, which is reflected in the fact that the provision mechanism of HttpHandler on the MvcRouteHandler type is basically (not exactly equivalent) can be represented by the following code. MvcRouteHandler maintains a ControllerFactory object that can be specified in the constructor or obtained directly by calling the GetControllerFactory method of the current ControllerBuilder if the assignment is not displayed.

Public class MvcRouteHandler: IRouteHandler {private IControllerFactory _ controllerFactory; public MvcRouteHandler (): this (ControllerBuilder.Current.GetControllerFactory ()) {} public MvcRouteHandler (IControllerFactory controllerFactory) {_ controllerFactory = controllerFactory;} IHttpHandler IRouteHandler.GetHttpHandler (RequestContext requestContext) {string controllerName = (string) requestContext.RouteData.GetRequiredString ("controller"); SessionStateBehavior sessionStateBehavior = _ controllerFactory.GetControllerSessionBehavior (requestContext, controllerName) RequestContext.HttpContext.SetSessionStateBehavior (sessionStateBehavior); return new MvcHandler (requestContext);}}

In the GetHttpHandler method used to provide HttpHandler, in addition to returning a MvcHandler object that implements the IHttpHandler interface, you also need to set the session-state behavior mode of the current HTTP context. Specifically, you first get the name of the Controller from the RouteData object contained in the incoming RequestContext, which is passed into the ControllerFactory's GetControllerSessionBehavior method along with the RequestContext object to get an enumeration of type SessionStateBehavior. * get the HttpContextBase object (actually a HttpContextWrapper object) that represents the current HTTP context through RequestContext and call its SetSessionStateBehavior method.

We know that the RouteHandler attribute in RouteData is originally derived from the corresponding Route object's property of the same name, but when we call the RouteCollection extension method MapRoute method, a Route object is created and added directly inside. Because ControllerFactory is not explicitly specified when the Route object is created, the ControllerFactory obtained through the GetControllerFactory method of the current ControllerBuilder is used by default.

The ControllerFactory obtained through the GetControllerFactory method of the current ControllerBuilder is only used to obtain the session-state behavior pattern, while MvcHandler actually uses it to create the Controller. The logic for handling requests in MvcHandler can basically be reflected in the following code snippet. As the following code snippet shows, MvcHandler has a RequestContext property that represents the context of the current request, which is initialized in the constructor.

Public class MvcHandler: IHttpHandler {public RequestContext RequestContext {get; private set;} public bool IsReusable {get {return false;}} public MvcHandler (RequestContext requestContext) {this.RequestContext = requestContext;} public void ProcessRequest (HttpContext context) {IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory (); string controllerName = this.RequestContext.RouteData.GetRequiredString ("controller") IController controller = controllerFactory.CreateController (this.RequestContext, controllerName); try {controller.Execute (this.RequestContext);} finally {controllerFactory.ReleaseController (controller);}}:}

In the ProcessRequest method, you get the name of the target Controller through the RequestContext object and activate the Controller object using the ControllerFactory created by the current ControllerBuilder. After the Execute method of the activated Controller object is executed, the ReleaseController of ControllerFactory is called to clean it up.

At this point, I believe you have a deeper understanding of "how to achieve the ASP.NET MVC Controller activation system". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report