Preface: there are already a lot of good articles about the Asp.net running mode on the Internet. I don't need to do this anymore. However, from the author's own learning experience, if the learned knowledge does not correspond to the source code in the class library, the impression is always not profound enough, and there is a sense of itching. I had to write my previous article to make a small summary of this knowledge. All the content in this article is based on my understanding of many articles on the Internet and based on my development experience. It is inevitable that there will be errors. In addition, due to the limitations of the author's ability, many places have not been fully explained (actually corresponding to the Code), and hope that the high hand can give a supplement.
1. Before entering Asp.net Runtime
Although this article focuses on the analysis of managed code, for the integrity of the entire knowledge point, here we will briefly introduce some basic information about IIS processing requests. On an IIS server, you can set multiple application pools (each application pool can independently set parameters such as the maximum memory usage, CPU usage, and the time interval of the recycle worker process, in addition, only one version can be used in an application pool. and then deploy your web applications to these application pools respectively. Every application pool has a workflow w3wp.exe for maintenance (if the web garden function is activated, you can also set multiple working processes ). Each application (virtual directory) has its own application domain in the pool, which is in the process space of the Worker Process in the application pool.
IIS handles various types of applications through extensions of various isapis. After a request is submitted from the client, IIS maps the request to the specified ISAPI extension based on the requested page or service type. For example, if we want IIS to support server programs such as Perl (of course, this porting job has been done long ago ), we need to compile an ISAPI extension that specifically handles requests to Perl pages. According to the definition of ISAPI (the ISAPI extension that complies with this definition can interact with IIS normally), your extension can include ISAPI extension and ISAPI filter. ISAPI extension is a request processing program that completes input and output between the Web server and ISAPI filter is a callback interface, you can implement these interfaces to intervene in every step of request processing and control authentication and revolvecache. In addition, ISAPI itself runs in the working process, and Asp.net runs in the working process, so the interaction between the two is very efficient.
For the. ASPX page, this extension is aspnet_isapi.dll. Because these isapis are non-hosted Win32 applications, it is difficult to directly modify them. Therefore, to enhance the scalability of Asp.net runtime, aspnet_isapi.dll has very few functions. We can simply regard aspnet_isapi.dll as the request information router, which is responsible for transmitting requests from IIS to Asp.net runtime. The httphandle and httpmodule mentioned later assume the functions of ISAPI extension and ISAPI filter respectively. Fortunately, httphandle and httpmodule can be implemented by pure managed code.
2. From unmanaged code to managed code
Previously, aspnet_isapi.dllis a non-essential code, while asp.netis a temporary code at runtime, and all of them are running in the w3wp.exe workflow. Where does the call between the two occur? Before introducing the following content, you must first introduce a concept: ECB. The full name of ECB is extension control block. It is an unmanaged resource package that provides complete access to the ISAPI and contains all the underlying information related to an incoming request, for example, data in the submitted bidding form. Therefore, the managed code in Asp.net needs to use ECB to access the interface provided by aspnet_isapi.dll. To be more accurate, the managed code publishes an iunknown interface for aspnet_isapi.dll to call, while aspnet_isapi.dll transfers its ECB address when calling it.
Understand the concept of ECB. Next we will introduce an interface and an interface implementation class (located in system. web. hosting namespace), please note the author's comments in the Code (the main purpose of this article is to work with everyone to understand the entire Asp.net runtime from the perspective of code implementation, therefore, the comments in the Code are the key notes added by the author, and all the code segments in the future are like this ):
Iisapiruntime
1/** // * interfacetype (cominterfacetype. interfaceisiunknown)
Indicates that this interface will be made public to com as an iunknown derived interface, so that ISAPI. dll can call this interface in the com mode.
*/
2 [comimport, GUID ("08a2c56f-7c16-41c1-a8be-432917a1a2d1"), interfacetype (cominterfacetype. interfaceisiunknown)]
3 Public interface iisapiruntime
4 {
5 void startprocessing ();
6 void stopprocessing ();
7/*** // The processrequest method is the demarcation point of the hosted and unmanaged code in the entire processing process. It can be seen that the caller (ISAPI) is passed in with an intptr structure. DLL) ECB address */
8 [Return: financialas (unmanagedtype. I4)]
9 int processrequest ([in] intptr ECB, [in, financialas (unmanagedtype. I4)] int useprocessmodel );
10 void dogccollect ();
11}
12
13/*** // This class implements the iisapiruntime interface. Its instance object exists in each appdomain and serves as the entry for the entire Asp.net runtime. */
14 public sealed class isapiruntime: marshalbyrefobject, iisapiruntime, iregisteredobject
15 {
16 // Fields
17 Private Static int _ isthisappdomainremovedfromunmanagedtable;
18 Private Static string s_thisappdomainsisapiappid;
19
20 // Methods
21 [aspnethostingpermission (securityaction. Demand, level = aspnethostingpermissionlevel. Minimal), securitypermission (securityaction. Demand, unrestricted = true)]
22 public isapiruntime ();
23 public void dogccollect ();
24 public override object initializelifetimeservice ();
25/** // * method for processing the request entry point, which is called by ISAPI. dll in the form of COM */
26 Public int processrequest (intptr ECB, int iwrtype );
27 internal static void removethisappdomainfromunmanagedtable ();
28 Internal void setthisappdomainsisapiappid (string appid );
29 public void startprocessing ();
30 public void stopprocessing ();
31 void iregisteredobject. Stop (bool immediate );
32}
Therefore, everything starts when aspnet_isapi.dll calls the processrequest method of an isapiruntime object in the form of COM. It can be mentioned that this call is asynchronous, that is, aspnet_isapi.dll will return immediately after the call, but the ECB will keep it until the entire request is processed and then released.
Well, now we know that the isapiruntime object is the entry point of the managed code. When will this object be generated? In other words, w3wp is also a program written with unmanaged code. When does it load. Net runtime? (If you are more curious, you can also ask when a working process is generated and started to run and how it interacts with the application pool .) I have completely explained these problems beyond the scope of my current capabilities. I also hope that the experts can provide additional information. But currently. net code, it can be inferred that the isapiruntime object corresponds to the application domain ,.. Net creates an isapiruntime object when creating an application domain. See the following code for creating an application domain:
Create an application domain
1/** // * This is the create method of the system. Web. Hosting. appdomainfactory type. It calls the create method of the actual factory. */
2 [Return: financialas (unmanagedtype. Interface)]
3 Public object create (string module, string typename, string appid, string apppath, string strurlofapporigin, int izone)
4 {
5/** // * the actual factory is an object of the appmanagerappdomainfactory type. */
6 return this. _ realfactory. Create (appid, apppath );
7}
8
9/** // * The appmanagerappdomainfactory. Create method. For more information, see the comments in the code. */
10 [Return: financialas (unmanagedtype. Interface)]
11 public object create (string appid, string apppath)
12 {
13 object obj2;
14 try
15 {
16 if (apppath [0] = '.')
17 {
18 fileinfo info = new fileinfo (apppath );
19 apppath = info. fullname;
20}
21 if (! Stringutil. stringendswith (apppath ,'\\'))
22 {
23 apppath = apppath + @"\";
24}
25 isapiapplicationhost apphost = new isapiapplicationhost (appid, apppath, false );
26/** // The call chain inside the method is very complex. It creates an application domain and returns an isapiruntime object. You can use this method to create an appdomain object.
27. jetbrain is used to track its call stack. For more information, see ASP. NET internals-the bridge between ISAPI and application domains.
28. If you use jetbrain to debug the system assembly, you may not be able to view the complete debugging information because the corresponding PDB file is missing, decompile the code into an intermediate code,
29. The method for generating DLL and PDB files again in debug mode is as follows:
30 1) generate the Il file: ildasm/Tok/BYT system. Web. dll/out = system. Web. Il
31 2) regenerate PDB/dll: ilasm system. Web. il/debug/dll/output = system. Web. dll */
32 isapiruntime o = (isapiruntime) This. _ appmanager. createobjectinternal (appid, typeof (isapiruntime), apphost, false, null );
33 O. setthisappdomainsisapiappid (appid );
34 o. startprocessing ();
35 obj2 = new objecthandle (O );
36}
37 catch (exception)
38 {
39 throw;
40}
41 return obj2;
42}
3.asp.net runtime, our long-waiting pure managed code Environment
After a long time, we finally entered the field of hosting code. After the previous content, we know that the processrequest method of an isapiruntime object is first executed in the managed code. Let's take a look at what this method has done:
Isapiruntime. processrequest
1/** // The isapiruntime method, which is used to process the request entry. */
2 Public int processrequest (intptr ECB, int iwrtype)
3 {
4 try
5 {
6/** // * the ECB is passed in as a parameter. An httpworkerrequest object is returned as an encapsulation of the data of a request. However, httpworkerrequest
7 * It is just an abstract base class. As a factory method, createworkerrequest returns the actual type isapiworkerrequestinproc,
8 * isapiworkerrequestinprocforiis6 or isapiworkerrequestoutofproc. Most of the methods provided in these types are actually
9 * around how to obtain data from the ECB, many static methods of the system. Web. unsafenativemethods type are called.
10 **/
11 httpworkerrequest wR = isapiworkerrequest. createworkerrequest (ECB, iwrtype );
12 string apppathtranslated = Wr. getapppathtranslated ();
13 string appdomainapppathinternal = httpruntime. appdomainapppathinternal;
14 if (appdomainapppathinternal = NULL) | stringutil. inclusignorecase (apppathtranslated, appdomainapppathinternal ))
15 {
16/** // * from here, the request processing process is handed over to httpruntime. It should be noted that ISAPI is multi-threaded and processrequest calls are asynchronous,
17 * This requires that the httpruntime. processrequest method be thread-safe. Let's take a look at the code in httpruntime. processrequestnodemand,
18 * all requests are arranged in a queue for sequential execution, ensuring the concurrency security.
19 * Finally, the httpruntime. processrequestinternal method will be called. Let's take a look at the method.
20 **/
21 httpruntime. processrequestnodemand (WR );
22 return 0;
23}
24 httpruntime. shutdownappdomain (applicationshutdownreason. physicalapplicationpathchanged, Sr. getstring ("hosting_phys_path_changed", new object [] {appdomainapppathinternal, apppathtranslated }));
25}
26 catch (exception)
27 {
28 Misc. reportunhandledexception (exception, new string [] {Sr. getstring ("failed_to_process_request ")});
29 throw;
30}
31 return 1;
32}
The main function of the above Code segment is to call the httprumtime. processrequestinternal method. Let's take a look at the implementation of this method below:
Httpruntime. processrequestinternal
1/** // * in the httpruntime. processrequestinternal () method, the following important objects are created:
2 * (1) httpcontext (including httprequest and httpresponse)
3 * (2) httpapplication
4 * at the same time, the processrequest method of the httpapplication object will be executed,
5 */
6 private void processrequestinternal (httpworkerrequest wr)
7 {
8/** // * The httpcontext object is created here. Httpworkerrequest is used as the construction parameter, while httpworkerrequest itself
9 * a group of high-level methods are built around the ECB's processing. Its instances are passed to httprequest and httpresponese by httpcontext.
10 * as their construction parameters. Therefore, the essence of httpworkerrequest as the managed environment package of ECB can be seen clearly here.
11 * In addition, it can be clearly reflected that each request has its own httpcontext object (and each httpcontext object manages
12 * an httpsession object-see the session attribute of httpcontext, which ensures that each visitor has its own session object .), You can
13 * use httpcontext. Current to access this object.
14 */
15 httpcontext extradata = new httpcontext (WR, false );
16 Wr. setendofsendnotification (this. _ asyncendofsendcallback, extradata );
17 interlocked. increment (ref this. _ activerequestcount );
18 hostingenvironment. incrementbusycount ();
19 try
20 {
21 try
22 {
23 This. ensurefirstrequestinit (extradata );
24}
25 catch
26 {
27 if (! Extradata. Request. isdebuggingrequest)
28 {
29 throw;
30}
31}
32 extradata. response. initresponsewriter ();
33/** // * use the application factory to return an httpapplication object.
34 * similar to the thread pool's management of threads, an httpapplication list is maintained by the stack in httpapplicationfactory (see httpapplicationfactory
35 *'s _ freelist variable ). At the end of the method call, _ theapplicationfactory. getnormalapplicationinstance (context) is called ),
36 * is a constructed httpapplication instance pop from the top of the _ freelist stack.
37 * therefore, httpcontext is used as the context for each request, and an httpapplication object is used to control the pipelines processed by the entire application.
38 * The processing process is completed in a thread pool managed by the worker process.
39 * In addition, since multiple requests can be processed simultaneously in an application domain, there are multiple httpapplication instances and multiple active threads (you can use the SOs of windbg
40 * extension to observe the relationship between them. This article will not go into depth ).
41 * In addition, all httpmodules are loaded during the creation of the httpapplication object (including the authentication modules provided by the system and
42 * Custom module ). We can declare our custom modules in Web. config. These modules are used to process related event points of the entire httpapplication pipeline,
43 * attach your own processing. Note the declaration of the init () method of the ihttpmodule interface. The input parameter of this method is the httpapplication object to be created.
44 * If your module wants to add some custom operations to the cache read, you only need to perform the following processing:
45 public class yourcustommodule: ihttpmodule
46 {
47 Public void Init (httpapplication Application)
48 {
49 application. resolverequestcache + = new eventhandler (this. yourcustomresolverequestcache );
50}
51}
52 * In addition, by reading the internal implementation of the httpapplicationfactory. getapplicationinstance method, you will find that each httpapplication object is created
53 * later, the initinternal method of this object will be called immediately, and many important initialization operations are done in this method. The content is large and we will introduce it separately below.
54 *
55 */
56 ihttphandler applicationinstance = httpapplicationfactory. getapplicationinstance (extradata );
57 if (applicationinstance = NULL)
58 {
59 throw new httpexception (Sr. getstring ("unable_create_app_object "));
60}
61 If (etwtrace. istraceenabled (5, 1 ))
62 {
63 etwtrace. Trace (etwtracetype. etw_type_start_handler, extradata. workerrequest, applicationinstance. GetType (). fullname, "Start ");
64}
65/** // * Check the type declaration of system. Web. httpapplication.
66 * Public class httpapplication: ihttpasynchandler, ihttphandler, icomponent, and idisposable
67 * You will find that it implements both synchronous and asynchronous ihandler, so by default, Asp.net processes Requests asynchronously.
68 */
69 If (applicationinstance is ihttpasynchandler)
70 {
71 ihttpasynchandler handler2 = (ihttpasynchandler) applicationinstance;
72 extradata. asyncapphandler = handler2;
73/** // * beginprocessrequest calls the resumesteps () method of httpapplication and completes all operations throughout the application cycle in resumesteps,
74 * triggers and executes all events and calls handler. This section will be introduced later. */
75 handler2.beginprocessrequest (extradata, this. _ handlercompletioncallback, extradata );
76}
77 else
78 {
79 applicationinstance. processrequest (extradata );
80 this. finishrequest (extradata. workerrequest, extradata, null );
81}
82}
83 catch (exception)
84 {
85 extradata. response. initresponsewriter ();
86 This. finishrequest (WR, extradata, exception );
87}
88}
The code above shows that the httpapplication object is created through httpapplicationfactory. getapplicationinstance (and finally calls httpruntime. createnonpublicinstance.
The httpapplicationfactory object first interprets the global. then load the application assembly (Global. DLL), and then combine the two to create a ghost application class. Finally, compile this class and obtain the object instance and return it to the httpruntime object. This object instance is the httpapplication object. The action of interpreting and compiling the. asax file only occurs when the virtual directory processes user requirements for the first time, or global. asax and Global. dll are modified after the previous execution. As for httpruntime. the createnonpublicinstance method calls parser and compiler according to the machine. config and web. config to generate an httpapplication object. I failed to track it through my own efforts.
In short, through the machine. config and web. description of the Page page = buildmanager. createinstancefromvirtualpath (virtualpath, typeof (PAGE), context, true, true) as page;
The above code is indirectly called by the gethandle method of pagehandlefactory (you can refer to httpapplication. maphttphandler method). Calling the returned page object is a very critical instance (the calling of pageparser and pagebuilder should be included in the specific method call process. We hope you can add this parameter ), because it is the ihttphandler role in our general ASPX page processing process! For this reason, we are at httpruntime. applicationinstance seen in processrequestinternal () method. processrequest (extradata) is actually a system called. web. UI. the processrequest method of the page-type instance. Therefore, the whole execution flow enters the page. processrequestmain. We usually refer to several page lifecycle events. You only need to take a good look at the implementation of this method and you will understand it. Since this method is usually viewed by many people and is familiar with it, I will not explain it here.
Iv. event mechanism of httpapplication
As of the content described above, the entire process is basically finished. However, if you only want to introduce it here, I am afraid you are still not clear about the relationship among httpapplication, ihttpmodule, and ihttphandler. How are all events in a request process (such as beginrequest and authenticaterequest) triggered? How can I handle these events through my own custom module? Where is handler's processing? In fact, all these are centered on the built-in event mechanism of httpapplication. Let's reveal its implementation method step by step (right. if you are not familiar with the event mechanism in. net, refer to the author's article: Part I of events in ASP. net: Events in.. net ). There are many events involved here. We will take the beginrequest event as an example to describe it:
(1) first, define the event itself.
Public event eventhandler beginrequest
(2) because there are more than one event, a unique key is defined for each event to facilitate the management of all events and is used as a marker for finding the specified event in the event container.
Private Static readonly object eventbeginrequest;
(3) define event triggers as "execution steps". The following is the definition of the Interface "execution steps ".
Internal interface iexecutionstep
{
// Execute the command for each "execution step"
Void execute ();
// Properties
Bool completedsynchronously {Get ;}
Bool iscancellable {Get ;}
}
Here, why does it seem necessary to encapsulate the event trigger in another layer and encapsulate it as a so-called "execution step", which is described later.
(4) use an array to save all execution steps. As you can imagine, when it comes to execution, extract each iexecutionstep from the array and then execute its execute () method. In execute, it must be a call to the event Delegate chain.
Private iexecutionstep [] _ execsteps;
(5) httpmodule registers the event during its initialization. When creating an httpapplication object, the initinternal method of the object is called. initmodules () is called internally to initialize all httpmodules related to the application, in this method, the most important thing is to call the init () method of each module. If we have a custom httpmodule and want this module to respond to the beginrequest event, we should define our module's Init () method as follows:
Public void Init (httpapplication Application)
{
Application. beginrequest + = new eventhandler (this. yourcustommethodforbeginrequestevent );
}
This completes the registration. Of course, at this time, the event has not been executed, and you have not seen the relationship between the event and the execution step, and httphandler has not yet appeared.
(6) As mentioned above, let's take a look at the two iexecutionstep defined in httpapplication, because the two steps are completed, one is to parse and instantiate httphandler, and the other is to call the processrequest method of httphandler. They are maphandlerexecutionstep and callhandlerexecutionstep. You can read the code of the execute () methods of these two classes by yourself. the handler is displayed in execute. processrequest (context) and handler2.beginprocessrequest (context, this. _ completioncallback, null) CALL statement, from maphandlerexecutionstep. execute. handler = This. _ application. maphttphandler (context, request. requesttype, request. filepathobject, request. physicalpathinternal, false); such a call statement will certainly have the pleasure of "oh, you are here.
(7) The initialization operation on the "execute step array" is put in the httpapplication. initinternal () method, specifically the following statement:
Initialization of execution steps
1this. createeventexecutionsteps (eventbeginrequest, steps );
2this. createeventexecutionsteps (eventauthenticaterequest, steps );
3this. createeventexecutionsteps (eventdefaauthauthentication, steps );
4this. createeventexecutionsteps (eventpostauthenticaterequest, steps );
5this. createeventexecutionsteps (eventauthorizerequest, steps );
6this. createeventexecutionsteps (eventpostauthorizerequest, steps );
7this. createeventexecutionsteps (eventresolverequestcache, steps );
8this. createeventexecutionsteps (eventpostresolverequestcache, steps );
9steps. Add (New maphandlerexecutionstep (this ));
10this. createeventexecutionsteps (eventpostmaprequesthandler, steps );
11this. createeventexecutionsteps (eventacquirerequeststate, steps );
12this. createeventexecutionsteps (eventpostacquirerequeststate, steps );
13this. createeventexecutionsteps (eventprerequesthandlerexecute, steps );
14steps. Add (New callhandlerexecutionstep (this); // from here, you can easily see where handler handles page parsing in the whole request.
15 // which application events are before and after the application events
16this. createeventexecutionsteps (eventpostrequesthandlerexecute, steps );
17this. createeventexecutionsteps (eventreleaserequeststate, steps );
18this. createeventexecutionsteps (eventpostreleaserequeststate, steps );
19steps. Add (New callfilterexecutionstep (this ));
20this. createeventexecutionsteps (eventupdaterequestcache, steps );
21this. createeventexecutionsteps (eventpostupdaterequestcache, steps );
22this. _ endrequeststepindex = steps. count;
23this. createeventexecutionsteps (eventendrequest, steps );
24steps. Add (New noopexecutionstep ());
25this. _ execsteps = new iexecutionstep [steps. Count];
26steps. copyto (this. _ execsteps); // copy the entire array to the private Variable _ execsteps of httpapplication. There are two ways to initialize the execution step array:
Steps. Add (New maphandlerexecutionstep (this ));
This execution step is irrelevant to the event. It only executes some specific operations (such as instantiating handler) at a specific location in the event stream ).
The other is to add the list of related event handling methods to the step. Each step is actually processing an event:
This. createeventexecutionsteps (eventbeginrequest, steps );
(8) execute the step array.
As mentioned earlier, the execution of the entire execution step array is called in httpapplication. resumesteps. I'm afraid even if you don't look at the code of this method, you can imagine that it is to traverse the entire execution step array and then call each of the execute methods. Here we will probably understand why there is a concept of steps. In my opinion, it is well understood in terms of concept and fully fits the pipeline model pipeline of the entire application processing; second, it shields the difference between the application execution steps that cause the event and the general built-in execution steps. Third, it is easier to improve and expand the entire process in the future.