Tag Archives: .NET

HTTP Request flow in IIS (Image)

Overview of an HTTP Request :

iis-1

The following list describes the request-processing flow :

  1. When a client browser initiates an HTTP request for a resource on the Web server, HTTP.sys intercepts the request.
  2. HTTP.sys contacts WAS to obtain information from the configuration store.
  3. WAS requests configuration information from the configuration store, applicationHost.config.
  4. The WWW Service receives configuration information, such as application pool and site configuration.
  5. The WWW Service uses the configuration information to configure HTTP.sys.
  6. WAS starts a worker process for the application pool to which the request was made.
  7. The worker process processes the request and returns a response to HTTP.sys.
  8. The client receives a response.

Detail of a HTTP request inside the Worker Process

iis-2

ASP.NET Request Flow:

iis-3

  • IIS gets the request
  • Looks up a script map extension and maps to aspnet_isapi.dll
  • Code hits the worker process (aspnet_wp.exe in IIS5 or w3wp.exe in IIS6)
  • .NET runtime is loaded
  • IsapiRuntime.ProcessRequest() called by non-managed code
  • IsapiWorkerRequest created once per request
  • HttpRuntime.ProcessRequest() called with Worker Request
  • HttpContext Object created by passing Worker Request as input
  • HttpApplication.GetApplicationInstance() called with Context to retrieve instance from pool
  • HttpApplication.Init() called to start pipeline event sequence and hook up modules and handlers
  • HttpApplicaton.ProcessRequest called to start processing
  • Pipeline events fire
  • Handlers are called and ProcessRequest method are fired
  • Control returns to pipeline and post request events fire

refer:

A low-level Look at the ASP.NET Architecture

IIS Architecture

Top 50 .Net Framework Interview and General FAQs

Q 1 What Is CLR? 
CLR is a runtime environment. Code that we develop with language compiler that targets the runtime is called managed code. Programmers write code in any .Net language, compile their programs into IL in a portable executable file that can then be managed and executed by CLR. The cross-language integration is possible because language compilers and tools that target the runtime use a common type system defined by the runtime, and they follow the runtime’s rules for defining new types, as well as for creating, using, persisting, and binding to types.
Q 2 What is CLR HOST? 
Basically, the CLR acts as a library that can be loaded and “hosted” by a process. You can develop an app that loads and hosts the CLR if you wish; that would allow your app to contain a whole CLR virtual machine, load assemblies and run .NET managed code all within it.
SQL Server 2008, for example, can do this. You can write .NET code that is stored in a SQL Server database and run from within the SQL Server database engine. SQL Server is hosting the CLR to achieve that.
Q 3 What is CTS? 
The common type system defines how types are declared, used, and managed in the common language runtime, and is also an important part of the runtime’s support for cross-language integration. The common type system performs the following functions:
Establishes a framework that helps enable cross-language integration, type safety, and high-performance code execution.
Provides an object-oriented model that supports the complete implementation of many programming languages.
Defines rules that languages must follow, which helps ensure that objects written in different languages can interact with each other.
Provides a library that contains the primitive data types (such as Boolean, Byte, Char, Int32, and UInt64) used in application development. All types in the .NET Framework are either value types or reference types.
Value types are data types whose objects are represented by the object’s actual value. If an instance of a value type is assigned to a variable, that variable is given a fresh copy of the value.
Reference types are data types whose objects are represented by a reference (similar to a pointer) to the object’s actual value. If a reference type is assigned to a variable, that variable references (points to) the original value. No copy is made.Classes,Structures,Enumerations,Interfaces,Delegates .
Q 4 What is CLS? 
.Net framework uses CLS to fully interact with others objects written in other languages. It is set of guidelines for languages to follow so that it can communicate with other .NET languages in seamless manner.
This is a subset of CTS which all .Net languages are expected to support.
Q 5 What is an Intermediate Language? 
MSIL is the CPU independent set of instructions which contains instructions of loading, storing, initializing and calling functions. By using Metadata and CTS, MSIL allows true cross language integration. CIL is an object-oriented assembly language, and is entirely stack-based. Its byte code is translated into native code or — most commonly — executed by a virtual machine.
Q 6 What is Just In Time Compiler? 
JIT compiler converts MSIL to native code on demand at application runtime, When the contents of an assembly are loaded and executed. Because CLR supplies a JIT compiler for each supported CPU architecture, Developers can build a set of MSIL assemblies that can be JIT compiled and run on different machine architecture.
Q 7 What is Portable executable (PE)? 
Metadata is stored in one section of a .NET Framework portable executable (PE) file, while Microsoft intermediate language (MSIL) is stored in another section of the PE file. The metadata portion of the file contains a series of table and heap data structures. The MSIL portion contains MSIL and metadata tokens that reference the metadata portion of the PE file.
Q 8 What is Managed Code? 
In .NET Framework Managed Code runs within the .Net Framework’s CLR and benefits from the services provided by the CLR. When we compile the managed code, the code gets compiled to an intermediate language (MSIL) and an executable is created. When a user runs the executable the Just In Time Compiler of CLR compiles the intermediate language into native code specific to the underlying architecture. Since this translation happens by the managed execution environment (CLR), the managed execution environment can make guarantees about what the code is going to do, because it can actually reason about it. It can insert traps and sort of protection around, if it’s running in a sandboxed environment, it can insert all the appropriate garbage collection hooks, exception handling, type safety, array bounce, index checking and so forth.
Q 9 What is Unmanaged Code? 
“Code that is directly executed by the Operating System is known as un-managed code. Typically applications written in VB 6.0, C++, C, etc are all examples of unmanaged code. Unmanaged code typically targets the processor architecture and is always dependent on the computer architecture. Unmanaged code is always compiled to target a specific architecture and will only run on the intended platform. This means that if you want to run the same code on different architecture then you will have to recompile the code using that particular architecture. Unmanaged code is always compiled to the native code which is architecture specific. When we compile unmanaged code it gets compiled into a binary X86 image. And this image always depends on the platform on which the code was compiled and cannot be executed on the other platforms that are different that the one on which the code was compiled. Unmanaged code does not get any services from the managed execution environment.
In unmanaged code the memory allocation, type safety, security, etc needs to be taken care of by the developer. This makes unmanaged code prone to memory leaks like buffer overruns and pointer overrides and so forth.
Unmanaged executable files are basically a binary image, x86 code, loaded into memory. The program counter gets put there and that’s the last the Operating System knows. There are protections in place around memory management and port I/O and so forth, but the system doesn’t actually know what the application is doing.”
Unmanaged Code example like C++, Win32, and COM which compiled into native so they not managed by .net runtime.but as you spend alot of time and money to build them .Net provide Unmanaged Interoperability.
so you can use them into your application and here the word Managed are came into the picture ie the runtime allow to use them but it said i will not managed them its your job to manage them one service that runtime provide to managed code is Garbage Collector its know how to control the lifetime of .net object and memory but it did not know how to clear resource allocated by unmanaged code. so this called unmanaged code and this your responsibility to clear the resource allocated by unmanaged code via implementing IDisposable interface for example.
Q 10 What is Garbage Collector? 
The garbage collector manages the allocation and release of memory for your application.
Each time you create a object, the CLR allocates memory for the object from the managed heap.
The garbage collector’s optimizing engine determines the best time to perform a collection, based upon the allocations being made.. When the garbage collector performs a collection, it checks for objects in the managed heap that are no longer being used by the application and performs the necessary operations to reclaim their memory.
Q 11 What is a Strong Name? 
A strong name consists of the assembly’s identity — its simple text name, version number, and culture information (if provided) — plus a public key and a digital signature.
You can use strong naming to ensure that when you load a DLL you get exactly the DLL you were expecting and not some other DLL that happens to have the same name.
Strong names guarantee name uniqueness by relying on unique key pairs. No one can generate the same assembly name that you can, because an assembly generated with one private key has a different name than an assembly generated with another private key.
Q 12 What are the steps to create Strong Name? 
Open .net command prompt.
Go to the folder containing DLL.
Type sn -k test.snk, you can use any file name instead of test.
This will create test .snk file in that folder.
Open the assemblyinfo.cs file of project.
Type file path in this tag [assembly:AssemblyKeyFile@”C:\Test\bin\Debug\test.snk”)]
Build application, finally your strong name created for your DLL.
Q 13 What are the Problems faced using Strong Name? 
Requires Exact Match of the strong name key used
Cannot Lose Private Key – if lost we need to create the whole process again
Q 14 What is Program Database? 
PDB files commonly have .pdb extention.When you create a class library project then after compilation,a .dll and .pdb file is created in ProjectRootFolder\bin\Debug.The created pdb file is program database for this project.
Q 16 What is an Assembly? 
A chunk of (precompiled) code that can be executed by the .NET runtime environment. A .NET program consists of one or more assemblies. An assembly is a collection of types and resources that forms a logical unit of functionality.
When you compile an application, the MSIL code created is stored in an assembly .
Assemblies include both executable application files that you can run directly from Windows without the need for any other programs (these have a .exe file extension), and libraries (which have a .dll extension) for use by other applications.
There are two kind of assemblies in .NET;
private shared
Private assemblies are simple and copied with each calling assemblies in the calling assemblies folder.
Shared assemblies (also called strong named assemblies) are copied to a single location (usually the Global assembly cache). For all calling assemblies within the same application, the same copy of the shared assembly is used from its original location. Hence, shared assemblies are not copied in the private folders of each calling assembly. Each shared assembly has a four part name including its face name, version, public key token and culture information. The public key token and version information makes it almost impossible for two different assemblies with the same name or for two similar assemblies with different version to mix with each other.
Q 17 What are the Contents of an Assembly? 
The assembly manifest, which contains assembly metadata.Type metadata,MSIL code that implements the types,A set of resources.
Q 18 What are Types of an Assemblies? 
Private Assembly: – An assembly is used only for a particular application. It is stored in the application’s directory otherwise in the application’s sub directory. There is no version constraint in private assembly.
Public/shared Assembly:- It has version constraint. This public assembly is stored inside the global assembly cache or GAC.GAC contains a collection of shared assemblies.
A .NET Framework assembly containing resources specific to a given language. Using satellite assemblies, you can place the resources for different languages in different assemblies, and the correct assembly is loaded into memory only if the user elects to view the application in that language.”
Q 19 What is a Satellite assembly? 
A satellite assembly is a .NET Framework assembly containing resources specific to a given language. Using satellite assemblies, you can place resources for different languages in different assemblies, and the correct assembly is loaded into memory only if the user selects to view the application in that language.
Q 20 What are Steps to Create Satellite Assembly? 
Create a folder with a specific culture name (for example, en-US) in the application’s bin\debug folder.
Create a .resx file in that folder. Place all translated strings into it.
Create a .resources file by using the following command from the .NET command prompt. (localizationsample is the name of the application namespace. If your application uses a nested namespace structure like MyApp.YourApp.MyName.YourName as the type of namespace, just use the uppermost namespace for creating resources files—MyApp.)
resgen Strings.en-US.resx LocalizationSample.
Strings.en-US.resources
al /embed:LocalizationSample.Strings.en-US.resources
/out:LocalizationSample.resources.dll /c:en-US
The above step will create two files, LocalizationSample.Strings.en-US.resources and LocalizationSample.resources.dll. Here, LocalizationSample is the name space of the application.
In the code, find the user’s language; for example, en-US. This is culture specific.
Give the assembly name as the name of .resx file. In this case, it is Strings.
Q 21 What is an Assembly Loader? 
The first thing the .NET assembly loader checks is whether the assembly is strongly signed. If it is, it will start its search in the Global Assembly Cache .
The loader will search for a policy file named in the format of:
policy.AssemblyMajorVersion.AssembyMinorVersion.AssemblyName
For example:
Policy.1.2.MyAssembly
If such a file exists it will look inside of it if the version of the assembly that we are trying to load matches the version/versions range written in the policy file. If it does, it will try to load the assembly with the version specified there. If no such policy file exists, it will try to load the assembly from the GAC.
If it will fail to find it in the GAC, it will start to search in the system’s search path.
In web applications it will also include the application’s Bin directory in the search path.
You can manually add folders to an AppDomain’s search path by using the “AppendPrivatePath” method.
Q 22 What is Multi Module Assembly or Assembly Linker? 
An assembly is called Multi Module assembly if it refers to multiple files. It can be combinations of modules written in different languages. When we link different modules into final assembly, the hash of each module is recorded in the manifest file.
link.exe is used to link multiple modules
as
c:\>more a1.vb
c:\>more b1.cs
c:\>vbc /t:module a1.vb
c:\>csc /addmodule:a1.netmodule /t:module b1.cs
c:\>link /entry:MainClinetApp.Main /out:main.exe b1.netmodule a1.module
Q 23 What is an Assembly Manifest? 
Assembly Manifest contains
Assembly Name,
Version number,
Culture,
Strong Name Information,
List of all files in the assembly,
Type Reference information,
Information on referenced assemblies.
Q 24 What is a Metadata? 
COM provided a step towards solving this problem. The .NET Framework makes component interoperation even easier by allowing compilers to emit additional declarative information into all modules and assemblies. This information, called metadata, helps components to interact seamlessly.
Metadata is binary information describing your program that is stored either in a common language runtime portable executable (PE) file or in memory. When you compile your code into a PE file, metadata is inserted into one portion of the file, and your code is converted to Microsoft intermediate language (MSIL) and inserted into another portion of the file. Every type and member that is defined and referenced in a module or assembly is described within metadata. When code is executed, the runtime loads metadata into memory and references it to discover information about your code’s classes, members, inheritance, and so on.
Q 25 What is a Base class in .Net? 
A base class, in the context of C#, is a class that is used to create, or derive, other classes. Classes derived from a base class are called child classes, subclasses or derived classes. A base class does not inherit from any other class and is considered parent of a derived class. Base class members (constructor, an instance method or instance property accessor) are accessed in derived class using the “base” keyword.
Base classes are automatically instantiated before derived classes.
Derived class can communicate to the base class during instantiation by calling the base class constructor with a matching parameter list.
Base class members can be accessed from the derived class through an explicit cast.
Since a base class itself can be a derived class, a class may have many base classes.
Members of a derived class can access the public, protected, internal and protected internal members of a base class.
Due to the transitive nature of inheritance, although a derived class has only one base class, it inherits the members declared in the parent of the base class.
By declaring a method in base class as virtual, the derived class can override that method with its own implementation. Both the overridden and overriding method and property must have the same access-level modifiers such as virtual, abstract or override.
When the keyword “abstract” is used for a method, it should be overridden in any nonabstract class that directly inherits from that class.
Abstract base classes are created using the “abstract” keyword in its declaration and are used to prevent direct initiation using the “new” keyword. They can only be used through derived classes that implement abstract methods.
A base class can prevent other classes from inheriting from it by declaring all the members as “sealed.”
Base class members can be hidden in a derived class by using the keyword “new” to indicate that the member is not intended to be an override of the base member. If “new” is not used, the compiler generates a warning.
Q 26 What is Full Assembly Reference? 
Full Assembly reference: A full assembly reference includes the assembly’s text name, version, culture, and public key token (if the assembly has a strong name). A full assembly reference is required if you reference any assembly that is part of the common language runtime or any assembly located in the global assembly cache.
Partial Assembly reference: We can dynamically reference an assembly by providing only partial information, such as specifying only the assembly name. When you specify a partial assembly reference, the runtime looks for the assembly only in the application directory. We can make partial references to an assembly in your code one of the following ways:
Use a method such as System.Reflection.Assembly.Load and specify only a partial reference. The runtime checks for the assembly in the application directory.
Use the System.Reflection.Assembly.LoadWithPartialName method and specify only a partial reference. The run time checks for the assembly in the application directory and in the global assembly cache.
Q 28 What is an Assembly Qualified Name? 
Type objType = typeof(System.Array);
// Print the full assembly name.
Console.WriteLine (“Full assembly name:\n {0}.”,
objType.Assembly.FullName.ToString());
// Print the qualified assembly name.
Console.WriteLine (“Qualified assembly name:\n {0}.”,
objType.AssemblyQualifiedName.ToString());
Q 29 What is ILDASM (Intermediate Language Disassembler)? 
The Ildasm.exe parses any .NET Framework .exe or .dll assembly, and shows the information in human-readable format. Ildasm.exe shows more than just the Microsoft intermediate language (MSIL) code — it also displays namespaces and types, including their interfaces. You can use Ildasm.exe to examine native .NET Framework assemblies, such as Mscorlib.dll, as well as .NET Framework assemblies provided by others or created yourself. Most .NET Framework developers will find Ildasm.exe indispensable.http://msdn.microsoft.com/en-us/library/aa309387(v=vs.71).aspx
Q 30 What is Global Assembly Cache? 
The global assembly cache stores assemblies specifically designated to be shared by several applications on the computer.
Q 31 What is an Attribute? 
Attributes provide a powerful method of associating declarative information with C# code (types, methods, properties, and so forth). Once associated with a program entity, the attribute can be queried at run time and used in any number of ways.
Q 32 What is Serialization & Deserialization? 
Serialization is the process of converting the state of an object into a form that can be persisted in a storage medium or transported across the processes/machines. The opposite of serialization is deserialization which is a process that converts the outcome of serialization into the original object.
Q 33 Where Serialization is used? 
Communication: If you have two machines that are running the same code, and they need to communicate, an easy way is for one machine to build an object with information that it would like to transmit, and then serialize that object to the other machine. It’s not the best method for communication, but it gets the job done.
Persistence: If you want to store the state of a particular operation in a database, it can be easily serialized to a byte array, and stored in the database for later retrieval.
Deep Copy: If you need an exact replica of an Object, and don’t want to go to the trouble of writing your own specialized clone() class, simply serializing the object to a byte array, and then de-serializing it to another object achieves this goal.
Caching: Really just an application of the above, but sometimes an object takes 10 minutes to build, but would only take 10 seconds to de-serialize. So, rather than hold onto the giant object in memory, just cache it out to a file via serialization, and read it in later when it’s needed.
Serialization is useful any time you want to move a representation of your data into or out of your process boundary.
Saving an object to disk is a trivial example you’ll see in many tutorials.
More commonly, serialization is used to transfer data to and from a web service, or to persist data to or from a database.
Q 34 What are the types of Serialization available in .net? 
Serialization can be Binary,SOAP or XML.
Q 35 What is Binary Serialization? 
Binary serialization is the process where you convert your .NET objects into byte stream. In binary serialization all the public, private, even those which are read only, members are serialized and converted into bytes. So when you want a complete conversion of your objects to bytes then one can make use of binary serialization.In XML serialization only the public properties and fields of the objects are converted into XML. The private members are not taken into consideration in XML serialization.
Similar to XML serialization. When you serialize object to SOAP format it conforms to the SOAP specification.Binary Serialization: Light and compact used in Remoting
SOAP Serialization : Interoperable use SOAP and used in web Services
XML Serialization : Custom Serialization .
Q 36 What are the Advantages & Disadvantages of Binary Serialization? 
Advantages of Binary Serialization
Object can be de-serialized from the same data you serialized it to.
Enhanced performance as it is faster and even more powerful in the sense that it provides support for complex objects, read only properties and even circular references.
Disadvantage of Binary Serialization: It is not easily portable to another platform.
Q 37 What is SOAP Serialization? 
To support SOAP serialization, the .NET Framework provides the SoapFormatter class. This class is defined in the System.Runtime.Serialization.Formatters.Soap namespace that is part of the System.Runtime.Serialization.Formatters.Soap.dll assembly. In order to use The SoapFormatter class, you must reference this assembly. Then, you can create an object and initialize it as you see fit. Before saving it, as always, create a Stream-based object that would indicate the name (and location) of the file and the type of action to perform. Then, declare a SoapFormatter variable using its default constructor. To actually save the object, call the Serialize() method of this class. This method uses the same syntax as that of the BinaryFormatter class: it takes two arguments. The first is a Stream-based object. The second is the object that needs to be serialized.Typically the serialization process consists of creation of the serializer, opening of the stream and invocation of the serializer.
Q 38 What is Advantages of SOAP Serialization? 
If you want full Type fidelity, and the “stability” that you are talking about you should use Soap Serialization. Soap Serialization preserves the
full type information.
XML Serialization is intended more for interoperability with other Operating Systems, and does not preserve all type information.
Q 39 What is a XML Serialization? 
XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information. For example, if you have a Book object that exists in the Library namespace, there is no guarantee that it is deserialized into an object of the same type.XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object’s fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.
Q 40 What are the Advantages of XML Serialization? 
The advantages of XML Serialization are as follows:
· XML based
· Support for cross platforms
· Easily readable and editable
Q 41 >What is Custom Serialization? 
Custom serialization is the process of controlling the serialization and deserialization of a type. By controlling serialization, it is possible to ensure serialization compatibility, which is the ability to serialize and deserialize between versions of a type without breaking the core functionality of the type. For example, in the first version of a type, there may be only two fields. In the next version of a type, several more fields are added. Yet the second version of an application must be able to serialize and deserialize both types. The following sections describe how to control serialization.
Q 42 What is a Namespace? 
The namespace keyword is used to declare a scope that contains a set of related objects. You can use a namespace to organize code elements and to create globally unique types.
Q 43 What is GUID? 
A Globally Unique Identifier is a unique reference number used as an identifier.
The term GUID typically refers to various implementations of the universally unique identifier (UUID) standard.GUIDs are usually stored as 128-bit values, and are commonly displayed as 32 hexadecimal digits with groups separated by hyphens, such as {21EC2020-3AEA-4069-A2DD-08002B30309D}.the total number of unique such GUIDs is 2122 or 5.3×1036.
Q 44 What is a Formatter? 
A formatter is an object that is responsible for encoding and serializing data into messages on one end, and deserializing and decoding messages into data on the other end.
Q 45 What is a Binary Formatter?
Serializes and deserializes an object, or an entire graph of connected objects, in binary format.
[ComVisibleAttribute(true)]
public sealed class BinaryFormatter : IRemotingFormatter, Iformatter
Q 46 What is a SOAP Formatter? 
Serializes and deserializes an object, or an entire graph of connected objects, in SOAP format.
Q 47 What is Reflection? 
Reflection provides objects (of type Type) that describe assemblies, modules and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. If you are using attributes in your code, reflection enables you to access them.
Reflection is useful in the following situations:
When you have to access attributes in your program’s metadata.
Retrieving Information Stored in Attributes.
For examining and instantiating types in an assembly.
For building new types at runtime. Use classes in System.Reflection.Emit.
For performing late binding, accessing methods on types created at run time.
Q 48 What is Thread and Process? 
A process, in the simplest terms, is an executing program. One or more threads run in the context of the process. A thread is the basic unit to which the operating system allocates processor time. A thread can execute any part of the process code, including parts currently being executed by another thread.
Q 49 What are the difference between a Dll and an Exe? 
EXE:
It’s a executable file
When loading an executable, no export is called, but only the module entry point.
When a system launches new executable, a new process is created
The entry thread is called in context of main thread of that process.
DLL:
It’s a Dynamic Link Library
There are multiple exported symbols.
The system loads a DLL into the context of an existing process.
Q 50 What are Globalization and Localization? 
Globalization is the process of designing and developing applications that function for multiple cultures. Localization is the process of customizing your application for a given culture and locale.
References:

from:http://www.sqlservercentral.com/blogs/querying-microsoft-sql-server/2014/04/16/top-50-net-framework-interview-and-general-faqs/

.Net平台下的B/S开发框架

一、前言

本文主要是对.Net平台下的几种B/S开发框架进行比较。只对比前端展现和界面业务逻辑的部分,对于后台的数据层、业务层、持久层等则不作讨论,因为这些部分是完全可以共用的。   主要从如下几个维度比较:

  • 技术差异、成熟度
  • 难易程度、学习成本
  • 适应的范围

.Net平台下的B/S开发框架分类

总体来说,目前.Net平台下的B/S开发框架基本可以分为三大类:

  1. 基于控件和页面事件驱动思想的Web Forms
  2. 基于模型、视图、控制器的MVC模式
  3. 综合了Web Forms和MVC的一些特点而产生的框架(不是本文的介绍重点)

到目前为止,ASP.NET Web Forms和ASP.NET MVC都有着各自的追捧者,双方都认为各自所使用的技术才是最好的,我个人很反对这种观点,马克思等革命先烈告诉我们,看待事物要用辩证、唯物的思想,存在即合理。作为开发人员的我们,眼光不能太狭隘,多掌握一门技术总是好的事情。而本文也尽量从客观、平等的角度出发,做一个相对公正全面的对比,而不是某种技术框架的推崇。

二、知识准备

在进行具体的比较之前,我们先回过头来想一想,什么是B/S结构?而本文介绍的框架都是基于微软.Net Framework,那么什么又是.Net Framework?

What is B/S?

毫无疑义、理所当然,B/S指的就是B:Browser,S:Server,即我们的B/S程序的客户端就是浏览器(各种各样的浏览器,不管你是IE还是Firefox、Chrome等等),而服务端又是什么呢?服务端是指我们利用.Net平台(当然也可以是PHP、Java、Ruby、Python等)开发出来的应用程序,这些程序运行在各种Web Server上(例如:IIS、Apache、Tomcat等)。

而联系B和S的就是HTTP协议,由于HTTP无状态的特性,造成了B/S应用所有的请求只能从浏览器(客户端)开始,也只能采用拉的模式,即服务端无法推送消息到客户端,而这点是和C/S模式的Windows程序有着很大区别的。

关于HTTP协议,属于另外一个话题,这里就不详细介绍了。具体可参考:http://baike.baidu.com/view/70545.htm,当然,要想做一个好的B/S应用,是非常有必要对HTTP协议做一些深入的了解的。

每一次的HTTP请求通过统一资源定位符(Url)开始,服务端在接收到一次Http Request之后,会由Web Server接管请求,然后交给具体的服务端程序进行逻辑处理(中间的这个处理过程会因为Web Server的不同而有所区别,总之是一个比较复杂的生命周期过程,以ASP.NET为例,详情可参考:http://msdn.microsoft.com/zh-cn/library/ms178473(VS.80).aspx),处理完成之后,最终将生成的结果发回给客户端。这个生成的结果一般是一段HTML文本、或者是一段二进制字节流。而客户端在接收到返回到信息之后,将这些信息解析出来,就形成了我们在浏览器上看到的实实在在的页面,至此就形成了一个完整的请求过程。

好吧,上面这些介绍可能和本文的这个议题没有太直接的关系,可能也有人为认为这些是一个很简单的问题,可是,你真的理解HTTP协议了吗?真的理解应用程序生命周期和页面生命周期了吗?你真的理解了我们经常用的Response.Redirect(“url”)对应的HTTP状态是301还是302吗?之所以介绍这么多,还是因为个人认为:要想较好的设计B/S系统结构,或者说写出高效、优雅的B/S代码,这些都是不可或缺的知识。

What is .Net Framework

先看一段解释: NET Framework又称 .Net框架。是由微软开发,一个致力于敏捷软件开发(Agile software development)、快速应用开发(Rapid application development)、平台无关性和网络透明化的软件开发平台。.NET是微软为下一个十年对服务器和桌面型软件工程迈出的第一步。.NET包含许多有助于互联网和内部网应用迅捷开发的技术。   .NET框架是微软公司继Windows DNA之后的新开发平台。.NET框架是以一种采用系统虚拟机运行的编程平台,以通用语言运行库(Common Language Runtime)为基础,支持多种语言(C#、VB、C++、Python等)的开发。 .NET也为应用程序接口(API)提供了新功能和开发工具。这些革新使得程序设计员可以同时进行Windows应用软件和网络应用软件以及组件和服务(web服务)的开发。.NET提供了一个新的反射性的且面向对象程序设计编程接口。.NET设计得足够通用化从而使许多不同高级语言都得以被汇集。

.Net Framework作为微软面向企业级应用的重要战略之一,有着十分重要的意义。.Net Framework是运行于.Net平台上所有应用程序的基础。而每一次版本的发布,都会带来一些革命性的变化。如下图就展示了不同Framework版本之间的关系,当然,还有很多更细节、更具体的不同之处,请参考微软官方站点,这里就不详细介绍了,只是作为理解本文的一个知识扩展。

三、技术比较

ASP.NET Web Forms官方定义:

ASP.NET Web Forms lets you build dynamic websites using a familiar drag-and-drop, event-driven model. A design surface and hundreds of controls and components let you rapidly build sophisticated, powerful UI-driven sites with data access.

总结为如下几点:

  1. 拖拽式的编程模式。
  2. 事件驱动模型。
  3. 提供大量的控件。

ASP.NET MVC官方定义:

ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development. ASP.NET MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.

总结为如下几点:

  1. 基于历史悠久的MVC模式。
  2. 更加清晰的界面代码分离。
  3. 对HTML/CSS/JS更加完全的控制权。
  4. 体现了敏捷、测试驱动开发等思想。

关系图

在这里,有必要解释一下.Net、ASP.NET、ASP.NET Web Forms、ASP.NET MVC之间的关系,其层次关系可用如下的图来表示:

其中.Net Framework是所有框架的基础,ASP.NET在.Net Framework基础上提供了Web开发框架的基础,而ASP.NET Web Forms和ASP.NET MVC是由微软提供的两种目前最主流的Web开发框架。

ASP.NET Web Forms与ASP.NET MVC详细对比

通过从优点、缺点、可能存在的风险、可能存在的机会这四个方面,进行一个详细的比较,具体如下表:

目前的发展情况

先看下微软.Net Framework各个版本的发布时间、IDE支持、Windows默认安装的版本,来做一个比较:

可以看出,.Net Framework 1.0的发布时间为2002年,ASP.NET Web Forms作为当时ASP的替代也同时推出。经历了将近10年的发展,在企业级B/S系统开发上,扮演了重要的角色,目前各种框架和第三方控件支持,也让ASP.NET Web Forms越来越成熟,但同时Web Forms界面和代码的高度耦合、重量级的页面尺寸及复杂的页面生命周期等,也越来越被开发人员所诟病。

而Web Forms在互联网开发方面的不足,导致了广大的开发人员在开发互联网应用时,首选PHP、Python、Ruby等轻量级快速开发平台。为了改善这一现状,微软在2009年4月,在.Net Framework 3.5的基础上,推出了ASP.NET MVC 1.0版,ASP.NET MVC的推出,让广大的Web开发人员耳目一新,抛弃了大量的服务器端控件、各种各样的回发事件,让ASP.NET MVC的页面看上去是那么的清爽,而MVC模式也更利于代码层次的组织,充分体现了Web开发简单、高效的本质。到目前为止,ASP.NET MVC已经发展到3.0的版本,视图引擎方面也新增了简单、清晰的Razor。

四、难易程度及学习成本

于这个方面,是很难比较的,因为总体来说,不管是ASP.NET Web Forms还是ASP.NET MVC,其底层实现都是基于.Net Framework的。在具体的Coding层面,他们是完全一样的。而他们的学习难易程度,可能取决于你之前的技术积累和设计思想,我想一个Windows开发人员可能学习ASP.NET Web Forms更加容易一些,而一个ASP程序员或者PHP程序员,可能接受MVC思想更加容易一些。

学习成本对比表

而下表,则尽量从多个维度进行一个学习曲线的综合比较:

五、适用的范围

如下图所示,展示了ASP.NET Web Forms和ASP.NET MVC各自适用的场景。

适用场景总结

但事情无绝对,对于各自的适用场景,可能还有很多其他因素的影响,总结为如下几点:

  1. 如果是快速开发后台管理系统,需要呈现大量的数据、表格等,建议采用Web Forms。
  2. 如果是对页面性能有着更高的要求,建议采用MVC。
  3. 如果是做互联网应用,对UI有着很高的要求,建议采用MVC。
  4. 如果要采用TDD开发模式,建议采用MVC。
  5. 具有很复杂的页面逻辑,建议采用Web Forms。
  6. 团队人员的掌握情况也是需要重点考虑的因素之一。
  7. 如果是做系统升级,尽量采用和老系统一致的框架。

六、其他框架介绍

Monorail

Monorail作为早于微软官方出现的MVC框架,可以算作第一款基于.Net实现的MVC框架,属于开源项目Castle的子项目。目前最新的版本为2.1,作者深厚的设计功底,让大家充分领略到了MVC的魅力,以至于后来微软的ASP.NET MVC里的很多实现,都能在monorail里看到影子。

官方站点:http://www.castleproject.org/monorail/index.html

参考介绍:http://baike.baidu.com/view/1344802.htm

MonoRail实现的模板引擎有3个:

AspNetViewEngine   用传统的.aspx文件做模板, 可以照常使用aspx语法和服务器控件, 但是由于Webform的生命周期和MonoRail完全不同, 有时候会让人觉得别扭, 有部分特性也受到了限制.

NVelocityViewEngine   用NVelocity做模板引擎, 需要学习VTL语法, 但是使用很简单, 特别是很多java程序员已经熟悉velocity. 简单的语法也强迫程序员把逻辑和界面很好的分离开来, 方便跟美工配合.

BrailViewEngine 基于Boo的模板引擎, Boo是一种语法类似python的.NET语言, 据MonoRail的参考说, Brail引擎是功能最强, 性能最好的选择, 但Boo是一种陌生的语言, 这成了Brail引擎应用的最大障碍.

总的来说,Monorail与ASP.NET MVC是如此的相似,如果掌握了其中一个的应用,那么切换到另外一种框架是很容易的事情。 唯一的区别可能在于模板引擎的选择上,monorail官方推荐的是NVelocity,而ASP.NET MVC官方推荐的是Razor,显然,对于一个.Net(C#)程序员来讲,学习Razor比NVelocity还是要简单一些,尽管NVelocity也是一门非常简单的模板语言。

我之前的东家,也一直在使用Monorail作为开发框架,自己也使用过很长的一段时间,觉得各方面还是非常不错的。

Web Forms与MVC结合的框架

此类框架是一个泛指,前人已经有过不少的实践。总结来看的话,主要是基于以下目的:

  1. 解耦页面和页面逻辑代码。
  2. 实现可替换的页面。
  3. 减少微软对HTML的过度封装。
  4. 继续沿用Web Forms的页面生命周期思想和控件思想。
  5. 提供更好的性能。

这类框架不管是第三方还是个人,实现的都不少,举两个例子:

Discuz!NT

由康盛创想公司开发,到目前为止,已经经历了10多个版本的发展,到现在已经相对成熟,如果想搭建基于.Net的BBS,那Discuz是比较不错的选择。

详情请参考:http://nt.discuz.net/

优点:

  1. 强大的BBS功能,你能想到的基本上都想到了。
  2. 官方支持,版本持续更新中。
  3. 快速搭建BBS应用,几乎不用开发。

缺点:

    1. 定制化开发麻烦,除非花钱找官方定制。
    2. 不能无缝和现有系统整合。

A low-level Look at the ASP.NET Architecture

Getting Low Level

This article looks at how Web requests flow through the ASP.NET framework from a very low level perspective, from Web Server, through ISAPI all the way up the request handler and your code. See what happens behind the scenes and stop thinking of ASP.NET as a black box.

By Rick Strahl

 

ASP.NET is a powerful platform for building Web applications, that provides a tremendous amount of flexibility and power for building just about any kind of Web application. Most people are familiar only with the high level frameworks like WebForms and WebServices which sit at the very top level of the ASP.NET hierarchy. In this article I’ll describe the lower level aspects of ASP.NET and explain how requests move from Web Server to the ASP.NET runtime and then through the ASP.NET Http Pipeline to process requests.

 

To me understanding the innards of a platform always provides certain satisfaction and level of comfort, as well as insight that helps to write better applications. Knowing what tools are available and how they fit together as part of the whole complex framework makes it easier to find the best solution to a problem and more importantly helps in troubleshooting and debugging of problems when they occur. The goal of this article is to look at ASP.NET from the System level and help understand how requests flow into the ASP.NET processing pipeline. As such we’ll look at the core engine and how Web requests end up there. Much of this information is not something that you need to know in your daily work, but it’s good to understand how the ASP.NET architecture routes request into your application code that usually sits at a much higher level.

 

Most people using ASP.NET are familiar with WebForms and WebServices. These high level implementations are abstractions that make it easy to build Web based application logic and ASP.NET is the driving engine that provides the underlying interface to the Web Server and routing mechanics to provide the base for these high level front end services typically used for your applications. WebForms and WebServices are merely two very sophisticated implementations of HTTP Handlers built on top of the core ASP.NET framework.

 

However, ASP.NET provides much more flexibility from a lower level. The HTTP Runtime and the request pipeline provide all the same power that went into building the WebForms and WebService implementations – these implementations were actually built with .NET managed code. And all of that same functionality is available to you, should you decide you need to build a custom platform that sits at a level a little lower than WebForms.

 

WebForms are definitely the easiest way to build most Web interfaces, but if you’re building custom content handlers, or have special needs for processing the incoming or outgoing content, or you need to build a custom application server interface to another application, using these lower level handlers or modules can provide better performance and more control over the actual request process. With all the power that the high level implementations of WebForms and WebServices provide they also add quite a bit of overhead to requests that you can bypass by working at a lower level.

What is ASP.NET

Let’s start with a simple definition: What is ASP.NET? I like to define ASP.NET as follows:

 

ASP.NET is a sophisticated engine using Managed Code for front to back processing of Web Requests.

 

It’s much more than just WebForms and Web Services…

 

ASP.NET is a request processing engine. It takes an incoming request and passes it through its internal pipeline to an end point where you as a developer can attach code to process that request. This engine is actually completely separated from HTTP or the Web Server. In fact, the HTTP Runtime is a component that you can host in your own applications outside of IIS or any server side application altogether. For example, you can host the ASP.NET runtime in a Windows form (check out  http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp for more detailed information on runtime hosting in Windows Forms apps).

 

The runtime provides a complex yet very elegant mechanism for routing requests through this pipeline. There are a number of interrelated objects, most of which are extensible either via subclassing or through event interfaces at almost every level of the process, so the framework is highly extensible. Through this mechanism it’s possible to hook into very low level interfaces such as the caching, authentication and authorization. You can even filter content by pre or post processing requests or simply route incoming requests that match a specific signature directly to your code or another URL. There are a lot of different ways to accomplish the same thing, but all of the approaches are straightforward to implement, yet provide flexibility in finding the best match for performance and ease of development.

 

The entire ASP.NET engine was completely built in managed code and all extensibility is provided via managed code extensions.

 

The entire ASP.NET engine was completely built in managed code and all of the extensibility functionality is provided via managed code extensions. This is a testament to the power of the .NET framework in its ability to build sophisticated and very performance oriented architectures. Above all though, the most impressive part of ASP.NET is the thoughtful design that makes the architecture easy to work with, yet provides hooks into just about any part of the request processing.

 

With ASP.NET you can perform tasks that previously were the domain of ISAPI extensions and filters on IIS – with some limitations, but it’s a lot closer than say ASP was. ISAPI is a low level Win32 style API that had a very meager interface and was very difficult to work for sophisticated applications. Since ISAPI is very low level it also is very fast, but fairly unmanageable for application level development. So, ISAPI has been mainly relegated for some time to providing bridge interfaces to other application or platforms. But ISAPI isn’t dead by any means. In fact, ASP.NET on Microsoft platforms interfaces with IIS through an ISAPI extension that hosts .NET and through it the ASP.NET runtime. ISAPI provides the core interface from the Web Server and ASP.NET uses the unmanaged ISAPI code to retrieve input and send output back to the client. The content that ISAPI provides is available via common objects like HttpRequest and HttpResponse that expose the unmanaged data as managed objects with a nice and accessible interface.

From Browser to ASP.NET

Let’s start at the beginning of the lifetime of a typical ASP.NET Web Request. A request starts on the browser where the user types in a URL, clicks on a hyperlink or submits an HTML form (a POST request). Or a client application might make call against an ASP.NET based Web Service, which is also serviced by ASP.NET. On the server side the Web Server – Internet Information Server 5 or 6 – picks up the request. At the lowest level ASP.NET interfaces with IIS through an ISAPI extension. With ASP.NET this request usually is routed to a page with an .aspx extension, but how the process works depends entirely on the implementation of the HTTP Handler that is set up to handle the specified extension. In IIS .aspx is mapped through an ‘Application Extension’ (aka. as a script map) that is mapped to the ASP.NET ISAPI dll – aspnet_isapi.dll. Every request that fires ASP.NET must go through an extension that is registered and points at aspnet_isapi.dll.

 

Depending on the extension ASP.NET routes the request to an appropriate handler that is responsible for picking up requests. For example, the .asmx extension for Web Services routes requests not to a page on disk but a specially attributed class that identifies it as a Web Service implementation. Many other handlers are installed with ASP.NET and you can also define your own. All of these HttpHandlers are mapped to point at the ASP.NET ISAPI extension in IIS, and configured in web.config to get routed to a specific HTTP Handler implementation. Each handler, is a .NET class that handles a specific extension which can range from simple Hello World behavior with a couple of lines of code, to very complex handlers like the ASP.NET Page or Web Service implementations. For now, just understand that an extension is the basic mapping mechanism that ASP.NET uses to receive a request from ISAPI and then route it to a specific handler that processes the request.

 

ISAPI is the first and highest performance entry point into IIS for custom Web Request handling.

The ISAPI Connection

ISAPI is a low level unmanged Win32 API. The interfaces defined by the ISAPI spec are very simplistic and optimized for performance. They are very low level – dealing with raw pointers and function pointer tables for callbacks – but they provide he lowest and most performance oriented interface that developers and tool vendors can use to hook into IIS. Because ISAPI is very low level it’s not well suited for building application level code, and ISAPI tends to be used primarily as a bridge interface to provide Application Server type functionality to higher level tools. For example, ASP and ASP.NET both are layered on top of ISAPI as is Cold Fusion, most Perl, PHP and JSP implementations running on IIS as well as many third party solutions such as my own Web Connection framework for Visual FoxPro. ISAPI is an excellent tool to provide the high performance plumbing interface to higher level applications, which can then abstract the information that ISAPI provides. In ASP and ASP.NET, the engines abstract the information provided by the ISAPI interface in the form of objects like Request and Response that read their content out of the ISAPI request information. Think of ISAPI as the plumbing. For ASP.NET the ISAPI dll is very lean and acts merely as a routing mechanism to pipe the inbound request into the ASP.NET runtime. All the heavy lifting and processing, and even the request thread management happens inside of the ASP.NET engine and your code.

 

As a protocol ISAPI supports both ISAPI extensions and ISAPI Filters. Extensions are a request handling interface and provide the logic to handle input and output with the Web Server – it’s essentially a transaction interface. ASP and ASP.NET are implemented as ISAPI extensions. ISAPI filters are hook interfaces that allow the ability to look at EVERY request that comes into IIS and to modify the content or change the behavior of functionalities like Authentication. Incidentally ASP.NET maps ISAPI-like functionality via two concepts: Http Handlers (extensions) and Http Modules (filters). We’ll look at these later in more detail.

 

ISAPI is the initial code point that marks the beginning of an ASP.NET request. ASP.NET maps various extensions to its ISAPI extension which lives in the .NET Framework directory:

 

<.NET FrameworkDir>\aspnet_isapi.dll

 

You can interactively see these mapping in the IIS Service manager as shown in Figure 1. Look at the root of the Web Site and the Home Directory tab, then Configuration | Mappings.

 

 

Figure 1: IIS maps various extensions like .ASPX to the ASP.NET ISAPI extension. Through this mechanism requests are routed into ASP.NET’s processing pipeline at the Web Server level.

 

You shouldn’t set these extensions manually as .NET requires a number of them. Instead use the aspnet_regiis.exe utility to make sure that all the various scriptmaps get registered properly:

 

cd <.NetFrameworkDirectory>

aspnet_regiis – i

 

This will register the particular version of the ASP.NET runtime for the entire Web site by registering the scriptmaps and setting up the client side scripting libraries used by the various controls for uplevel browsers. Note that it registers the particular version of the CLR that is installed in the above directory. Options on aspnet_regiis let you configure virtual directories individually. Each version of the .NET framework has its own version of aspnet_regiis and you need to run the appropriate one to register a site or virtual directory for a specific version of the .NET framework. Starting with ASP.NET 2.0, an IIS ASP.NET configuration page lets you pick the .NET version interactively in the IIS management console.

IIS 5 and 6 work differently

When a request comes in, IIS checks for the script map and routes the request to the aspnet_isapi.dll. The operation of the DLL and how it gets to the ASP.NET runtime varies significantly between IIS 5 and 6. Figure 2 shows a rough overview of the flow.

 

In IIS 5 hosts aspnet_isapi.dll directly in the inetinfo.exe process or one of its isolated worker processes if you have isolation set to medium or high for the Web or virtual directory. When the first ASP.NET request comes in the DLL will spawn a new process in another EXE – aspnet_wp.exe – and route processing to this spawned process. This process in turn loads and hosts the .NET runtime. Every request that comes into the ISAPI DLL then routes to this worker process via Named Pipe calls.

 

 

Figure 2 – Request flow from IIS to the ASP.NET Runtime and through the request processing pipeline from a high level. IIS 5 and IIS 6 interface with ASP.NET in different ways but the overall process once it reaches the ASP.NET Pipeline is the same.

 

IIS6, unlike previous servers, is fully optimized for ASP.NET

 

IIS 6 – Viva the Application Pool

IIS 6 changes the processing model significantly in that IIS no longer hosts any foreign executable code like ISAPI extensions directly. Instead IIS 6 always creates a separate worker process – an Application Pool – and all processing occurs inside of this process, including execution of the ISAPI dll. Application Pools are a big improvement for IIS 6, as they allow very granular control over what executes in a given process. Application Pools can be configured for every virtual directory or the entire Web site, so you can isolate every Web application easily into its own process that will be completely isolated from any other Web application running on the same machine. If one process dies it will not affect any others at least from the Web processing perspective.

 

In addition, Application Pools are highly configurable. You can configure their execution security environment by setting an execution impersonation level for the pool which allows you to customize the rights given to a Web application in that same granular fashion. One big improvement for ASP.NET is that the Application Pool replaces most of the ProcessModel entry in machine.config. This entry was difficult to manage in IIS 5, because the settings were global and could not be overridden in an application specific web.config file. When running IIS 6, the ProcessModel setting is mostly ignored and settings are instead read from the Application Pool. I say mostly – some settings, like the size of the ThreadPool and IO threads still are configured through this key since they have no equivalent in the Application Pool settings of the server.

 

Because Application Pools are external executables these executables can also be easily monitored and managed. IIS 6 provides a number of health checking, restarting and timeout options that can detect and in many cases correct problems with an application. Finally IIS 6’s Application Pools don’t rely on COM+ as IIS 5 isolation processes did which has improved performance and stability especially for applications that need to use COM objects internally.

 

Although IIS 6 application pools are separate EXEs, they are highly optimized for HTTP operations by directly communicating with a kernel mode HTTP.SYS driver. Incoming requests are directly routed to the appropriate application pool. InetInfo acts merely as an Administration and configuration service – most interaction actually occurs directly between HTTP.SYS and the Application Pools, all of which translates into a more stable and higher performance environment over IIS 5. This is especially true for static content and ASP.NET applications.

 

An IIS 6 application pool also has intrinsic knowledge of ASP.NET and ASP.NET can communicate with new low level APIs that allow direct access to the HTTP Cache APIs which can offload caching from the ASP.NET level directly into the Web Server’s cache.

 

In IIS 6, ISAPI extensions run in the Application Pool worker process. The .NET Runtime also runs in this same process, so communication between the ISAPI extension and the .NET runtime happens in-process which is inherently more efficient than the named pipe interface that IIS 5 must use. Although the IIS hosting models are very different the actual interfaces into managed code are very similar – only the process in getting the request routed varies a bit.

 

The ISAPIRuntime.ProcessRequest() method is the first entry point into ASP.NET

Getting into the .NET runtime

The actual entry points into the .NET Runtime occur through a number of undocumented classes and interfaces. Little is known about these interfaces outside of Microsoft, and Microsoft folks are not eager to talk about the details, as they deem this an implementation detail that has little effect on developers building applications with ASP.NET.

 

The worker processes ASPNET_WP.EXE (IIS5) and W3WP.EXE (IIS6) host the .NET runtime and the ISAPI DLL calls into small set of unmanged interfaces via low level COM that eventually forward calls to an instance subclass of the ISAPIRuntime class. The first entry point to the runtime is the undocumented ISAPIRuntime class which exposes the IISAPIRuntime interface via COM to a caller. These COM interfaces low level IUnknown based interfaces that are meant for internal calls from the ISAPI extension into ASP.NET. Figure 3 shows the interface and call signatures for the IISAPIRuntime  interface as shown in Lutz Roeder’s excellent .NET Reflector tool (http://www.aisto.com/roeder/dotnet/). Reflector an assembly viewer and disassembler that makes it very easy to look at medadata and disassembled code (in IL, C#, VB) as shown in Figure 3. It’s a great way to explore the bootstrapping process.

 

 

Figure 3 – If you want to dig into the low level interfaces open up Reflector, and point at the System.Web.Hosting namespace. The entry point to ASP.NET occurs through a managed COM Interface called from the ISAPI dll, that receives an unmanaged pointer to the ISAPI ECB. The ECB contains has access to the full ISAPI interface to allow retrieving request data and sending back to IIS.

 

The IISAPIRuntime interface acts as the interface point between the unmanaged code coming from the ISAPI extension (directly in IIS 6 and indirectly via the Named Pipe handler in IIS 5). If you take a look at this class you’ll find a ProcessRequest method with a signature like this:

 

[return: MarshalAs(UnmanagedType.I4)]

int ProcessRequest([In] IntPtr ecb,

[In, MarshalAs(UnmanagedType.I4)] int useProcessModel);

 

The ecb parameter is the ISAPI Extension Control Block (ECB) which is passed as an unmanaged resource to ProcessRequest. The method then takes the ECB and uses it as the base input and output interface used with the Request and Response objects. An ISAPI ECB contains all low level request information including server variables, an input stream for form variables as well as an output stream that is used to write data back to the client. The single ecb reference basically provides access to all of the functionality an ISAPI request has access to and ProcessRequest is the entry and exit point where this resource initially makes contact with managed code.

 

The ISAPI extension runs requests asynchronously. In this mode the ISAPI extension immediately returns on the calling worker process or IIS thread, but keeps the ECB for the current request alive. The ECB then includes a mechanism for letting ISAPI know when the request is complete (via ecb.ServerSupportFunction) which then releases the ECB. This asynchronous processing releases the ISAPI worker thread immediately, and offloads processing to a separate thread that is managed by ASP.NET.

 

ASP.NET receives this ecb reference and uses it internally to retrieve information about the current request such as server variables, POST data as well as returning output back to the server. The ecb stays alive until the request finishes or times out in IIS and ASP.NET continues to communicate with it until the request is done. Output is written into the ISAPI output stream (ecb.WriteClient()) and when the request is done, the ISAPI extension is notified of request completion to let it know that the ECB can be freed. This implementation is very efficient as the .NET classes essentially act as a fairly thin wrapper around the high performance, unmanaged ISAPI ECB.

 

Loading .NET – somewhat of a mystery

Let’s back up one step here: I skipped over how the .NET runtime gets loaded. Here’s where things get a bit fuzzy. I haven’t found any documentation on this process and since we’re talking about native code there’s no easy way to disassemble the ISAPI DLL and figure it out.

 

My best guess is that the worker process bootstraps the .NET runtime from within the ISAPI extension on the first hit against an ASP.NET mapped extension. Once the runtime exists, the unmanaged code can request an instance of an ISAPIRuntime object for a given virtual path if one doesn’t exist yet. Each virtual directory gets its own AppDomain and within that AppDomain the ISAPIRuntime exists from which the bootstrapping process for an individual application starts. Instantiation appears to occur over COM as the interface methods are exposed as COM callable methods.

 

To create the ISAPIRuntime instance the System.Web.Hosting.AppDomainFactory.Create() method is called when the first request for a specific virtual directory is requested. This starts the ‘Application’ bootstrapping process. The call receives parameters for type and module name and virtual path information for the application which is used by ASP.NET to create an AppDomain and launch the ASP.NET application for the given virtual directory. This HttpRuntime derived object is created in a new AppDomain. Each virtual directory or ASP.NET application is hosted in a separate AppDomain and they get loaded only as requests hit the particular ASP.NET Application. The ISAPI extension manages these instances of the HttpRuntime objects, and routes inbound requests to the right one based on the virtual path of the request.

 

 

Figure 4 – The transfer of the ISAPI request into the HTTP Pipeline of ASP.NET uses a number of undocumented classes and interfaces and requires several factory method calls. Each Web Application/Virtual runs in its own AppDomain with the caller holding a reference to an IISAPIRuntime interface that triggers the ASP.NET request processing.

 

Back in the runtime

At this point we have an instance of ISAPIRuntime active and callable from the ISAPI extension. Once the runtime is up and running the ISAPI code calls into the ISAPIRuntime.ProcessRequest() method which is the real entry point into the ASP.NET Pipeline. The flow from there is shown in Figure 4.

 

Remember ISAPI is multi-threaded so requests will come in on multiple threads through the reference that was returned by ApplicationDomainFactory.Create(). Listing 1 shows the disassembled code from the IsapiRuntime.ProcessRequest method that receives an ISAPI ecb object and server type as parameters. The method is thread safe, so multiple ISAPI threads can safely call this single returned object instance simultaneously.

 

Listing 1: The Process request method receives an ISAPI Ecb and passes it on to the Worker request

public int ProcessRequest(IntPtr ecb, int iWRType)

{

HttpWorkerRequest request1 = ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);

 

string text1 = request1.GetAppPathTranslated();

string text2 = HttpRuntime.AppDomainAppPathInternal;

if (((text2 == null) || text1.Equals(“.”)) ||

(string.Compare(text1, text2, true, CultureInfo.InvariantCulture) == 0))

{

HttpRuntime.ProcessRequest(request1);

return 0;

}

 

HttpRuntime.ShutdownAppDomain(“Physical application path changed from ” +

text2 + ” to ” + text1);

return 1;

}

 

The actual code here is not important, and keep in mind that this is disassembled internal framework code that you’ll never deal with directly and that might change in the future. It’s meant to demonstrate what’s happening behind the scenes. ProcessRequest receives the unmanaged ECB reference and passes it on to the ISAPIWorkerRequest object which is in charge of creating the Request Context for the current request as shown in Listing 2.

 

The System.Web.Hosting.ISAPIWorkerRequest class is an abstract subclass of HttpWorkerRequest, whose job it is to create an abstracted view of the input and output that serves as the input for the Web application. Notice another factory method here: CreateWorkerRequest, which as a second parameter receives the type of worker request object to create. There are three different versions: ISAPIWorkerRequestInProc, ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestOutOfProc. This object is created on each incoming hit and serves as the basis for the Request and Response objects which will receive their data and streams from the data provided by the WorkerRequest.

 

The abstract HttpWorkerRequest class is meant to provide a highlevel abstraction around the low level interfaces so that regardless of where the data comes from, whether it’s a CGI Web Server, the Web Browser Control or some custom mechanism you use to feed the data to the HTTP Runtime. The key is that ASP.NET can retrieve the information consistently.

 

In the case of IIS the abstraction is centered around an ISAPI ECB block. In our request processing, ISAPIWorkerRequest hangs on to the ISAPI ECB and retrieves data from it as needed. Listing 2 shows how the query string value is retrieved for example.

 

Listing 2: An ISAPIWorkerRequest method that uses the unmanged

// *** Implemented in ISAPIWorkerRequest

public override byte[] GetQueryStringRawBytes()

{

byte[] buffer1 = new byte[this._queryStringLength];

if (this._queryStringLength > 0)

{

int num1 = this.GetQueryStringRawBytesCore(buffer1, this._queryStringLength);

if (num1 != 1)

{

throw new HttpException( “Cannot_get_query_string_bytes”);

}

}

return buffer1;

}

 

// *** Implemented in a specific implementation class ISAPIWorkerRequestInProcIIS6

internal override int GetQueryStringCore(int encode, StringBuilder buffer, int size)

{

if (this._ecb == IntPtr.Zero)

{

return 0;

}

return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode, buffer, size);

}

 

ISAPIWorkerRequest implements a high level wrapper method, that calls into lower level Core methods, which are responsible for performing the actual access to the unmanaged APIs – or the ‘service level implementation’. The Core methods are implemented in the specific ISAPIWorkerRequest instance subclasses and thus provide the specific implementation for the environment that it’s hosted in. This makes for an easily pluggable environment where additional implementation classes can be provided later as newer Web Server interfaces or other platforms are targeted by ASP.NET. There’s also a helper class System.Web.UnsafeNativeMethods. Many of these methods operate on the ISAPI ECB structure performing unmanaged calls into the ISAPI extension.

HttpRuntime, HttpContext, and HttpApplication – Oh my

When a request hits, it is routed to the ISAPIRuntime.ProcessRequest() method. This method in turn calls HttpRuntime.ProcessRequest that does several important things (look at System.Web.HttpRuntime.ProcessRequestInternal with Reflector):

 

  • Create a new HttpContext instance for the request
  • Retrieves an HttpApplication Instance
  • Calls HttpApplication.Init() to set up Pipeline Events
  • Init() fires HttpApplication.ResumeProcessing() which starts the ASP.NET pipeline processing

 

First a new HttpContext object is created and it is passed the ISAPIWorkerRequest that wrappers the ISAPI ECB. The Context is available throughout the lifetime of the request and ALWAYS accessible via the static HttpContext.Current property. As the name implies, the HttpContext object represents the context of the currently active request as it contains references to all of the vital objects you typically access during the request lifetime: Request, Response, Application, Server, Cache. At any time during request processing HttpContext.Current gives you access to all of these object.

 

The HttpContext object also contains a very useful Items collection that you can use to store data that is request specific. The context object gets created at the begging of the request cycle and released when the request finishes, so data stored there in the Items collection is specific only to the current request. A good example use is a request logging mechanism where you want to track start and end times of a request by hooking the Application_BeginRequest and Application_EndRequest methods in Global.asax as shown in Listing 3. HttpContext is your friend – you’ll use it liberally if you
need data in different parts of the request or page processing.

 

Listing 3 – Using the HttpContext.Items collection lets you save data between pipeline events

protected void Application_BeginRequest(Object sender, EventArgs e)

{

//*** Request Logging

if (App.Configuration.LogWebRequests)

Context.Items.Add(“WebLog_StartTime”,DateTime.Now);

}

 

protected void Application_EndRequest(Object sender, EventArgs e)

{

// *** Request Logging

if (App.Configuration.LogWebRequests)

{

try

{

TimeSpan Span = DateTime.Now.Subtract(

(DateTime) Context.Items[“WebLog_StartTime”] );

int MiliSecs = Span.TotalMilliseconds;

 

// do your logging

WebRequestLog.Log(App.Configuration.ConnectionString,

                                    true,MilliSecs);

}

}

 

 

Once the Context has been set up, ASP.NET needs to route your incoming request to the appropriate application/virtual directory by way of an HttpApplication object. Every ASP.NET application must be set up as a Virtual (or Web Root) directory and each of these ‘applications’ are handled independently.

 

The HttpApplication is like a master of ceremonies – it is where the processing action starts

 

Master of your domain: HttpApplication

Each request is routed to an HttpApplication object. The HttpApplicationFactory class creates a pool of HttpApplication objects for your ASP.NET application depending on the load on the application and hands out references for each incoming request. The size of the pool is limited to the setting of the MaxWorkerThreads setting in machine.config’s ProcessModel Key, which by default is 20.

 

The pool starts out with a smaller number though; usually one and it then grows as multiple simulataneous requests need to be processed. The Pool is monitored so under load it may grow to its max number of instances, which is later scaled back to a smaller number as the load drops.

 

HttpApplication is the outer container for your specific Web application and it maps to the class that is defined in Global.asax. It’s the first entry point into the HTTP Runtime that you actually see on a regular basis in your applications. If you look in Global.asax (or the code behind class) you’ll find that this class derives directly from HttpApplication:

 

public class Global : System.Web.HttpApplication

 

HttpApplication’s primary purpose is to act as the event controller of the Http Pipeline and so its interface consists primarily of events. The event hooks are extensive and include:

 

  • BeginRequest
  • AuthenticateRequest
  • AuthorizeRequest
  • ResolveRequestCache
  • AquireRequestState
  • PreRequestHandlerExecute
  • …Handler Execution…
  • PostRequestHandlerExecute
  • ReleaseRequestState
  • UpdateRequestCache
  • EndRequest

 

Each of these events are also implemented in the Global.asax file via empty methods that start with an Application_ prefix. For example, Application_BeginRequest(), Application_AuthorizeRequest(). These handlers are provided for convenience since they are frequently used in applications and make it so that you don’t have to explicitly create the event handler delegates.

 

It’s important to understand that each ASP.NET virtual application runs in its own AppDomain and that there inside of the AppDomain multiple HttpApplication instances running simultaneously, fed out of a pool that ASP.NET manages. This is so that multiple requests can process at the same time without interfering with each other.

 

To see the relationship between the AppDomain, Threads and the HttpApplication check out the code in Listing 4.

 

Listing 4 – Showing the relation between AppDomain, Threads and HttpApplication instances

private void Page_Load(object sender, System.EventArgs e)

{

// Put user code to initialize the page here

this.ApplicationId = ((HowAspNetWorks.Global)

HttpContext.Current.ApplicationInstance).ApplicationId ;

this.ThreadId = AppDomain.GetCurrentThreadId();

 

this.DomainId = AppDomain.CurrentDomain.FriendlyName;

 

this.ThreadInfo = “ThreadPool Thread: ” +

System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() +

“<br>Thread Apartment: ” +

System.Threading.Thread.CurrentThread.ApartmentState.ToString();

 

// *** Simulate a slow request so we can see multiple

//     requests side by side.

System.Threading.Thread.Sleep(3000);

}

 

This is part of a demo is provided with your samples and the running form is shown in Figure 5. To check this out run two instances of a browser and hit this sample page and watch the various Ids.

 

 

Figure 5 – You can easily check out how AppDomains, Application Pool instances, and Request Threads interact with each other by running a couple of browser instances simultaneously. When multiple requests fire you’ll see the thread and Application ids change, but the AppDomain staying the same.

 

You’ll notice that the AppDomain ID stays steady while thread and HttpApplication Ids change on most requests, although they likely will repeat. HttpApplications are running out of a collection and are reused for subsequent requests so the ids repeat at times. Note though that Application instance are not tied to a specific thread – rather they are assigned to the active executing thread of the current request.

 

Threads are served from the .NET ThreadPool and by default are Multithreaded Apartment (MTA) style threads. You can override this apartment state in ASP.NET pages with the ASPCOMPAT=”true” attribute in the @Page directive. ASPCOMPAT is meant to provide COM components a safe environment to run in and ASPCOMPAT uses special Single Threaded Apartment (STA) threads to service those requests. STA threads are set aside and pooled separately as they require special handling.

 

The fact that these HttpApplication objects are all running in the same AppDomain is very important. This is how ASP.NET can guarantee that changes to web.config or individual ASP.NET pages get recognized throughout the AppDomain. Making a change to a value in web.config causes the AppDomain to be shut down and restarted. This makes sure that all instances of HttpApplication see the changes made because when the AppDomain reloads the changes from ASP.NET are re-read at startup. Any static references are also reloaded when the AppDomain so if the application reads values from App Configuration settings these values also get refreshed.

 

To see this in the sample, hit the ApplicationPoolsAndThreads.aspx page and note the AppDomain Id. Then go in and make a change in web.config (add a space and save). Then reload the page. You’ll l find that a new AppDomain has been created.

 

In essence the Web Application/Virtual completely ‘restarts’ when this happens. Any requests that are already in the pipeline processing will continue running through the existing pipeline, while any new requests coming in are routed to the new AppDomain. In order to deal with ‘hung requests’ ASP.NET forcefully shuts down the AppDomain after the request timeout period is up even if requests are still pending. So it’s actually possible that two AppDomains exist for the same HttpApplication at a given point in time as the old one’s shutting down and the new one is ramping up. Both AppDomains continue to serve their clients until the old one has run out its pending requests and shuts down leaving just the new AppDomain running.

Flowing through the ASP.NET Pipeline

The HttpApplication is responsible for the request flow by firing events that signal your application that things are happening. This occurs as part of the HttpApplication.Init() method (look at System.Web.HttpApplication.InitInternal and HttpApplication.ResumeSteps() with Reflector) which sets up and starts a series of events in succession including the call to execute any handlers. The event handlers map to the events that are automatically set up in global.asax, and they also map any attached HTTPModules, which are essentially an externalized event sink for the events that HttpApplication publishes.

 

Both HttpModules and HttpHandlers are loaded dynamically via entries in Web.config and attached to the event chain. HttpModules are actual event handlers that hook specific HttpApplication events, while HttpHandlers are an end point that gets called to handle ‘application level request processing’.

 

Both Modules and Handlers are loaded and attached to the call chain as part of the HttpApplication.Init() method call. Figure 6 shows the various events and when they happen and which parts of the pipeline they affect.

 

 

Figure 6 – Events flowing through the ASP.NET HTTP Pipeline. The HttpApplication object’s events drive requests through the pipeline. Http Modules can intercept these events and override or enhance existing functionality.

 

HttpContext, HttpModules and HttpHandlers

The HttpApplication itself knows nothing about the data being sent to the application – it is a merely messaging object that communicates via events. It fires events and passes information via the HttpContext object to the called methods. The actual state data for the current request is maintained in the HttpContext object mentioned earlier. It provides all the request specific data and follows each request from beginning to end through the pipeline. Figure 7 shows the flow through ASP.NET pipeline. Notice the Context object which is your compadre from beginning to end of the request and can be used to store information in one event method and retrieve it in a later event method.

 

Once the pipeline is started, HttpApplication starts firing events one by one as shown in Figure 6. Each of the event handlers is fired and if events are hooked up those handlers execute and perform their tasks. The main purpose of this process is to eventually call the HttpHandler hooked up to a specific request. Handlers are the core processing mechanism for ASP.NET requests and usually the place where any application level code is executed. Remember that the ASP.NET Page and Web Service frameworks are implemented as HTTPHandlers and that’s where all the core processing of the request is handled. Modules tend to be of a more core nature used to prepare or post process the Context that is delivered to the handler. Typical default handlers in ASP.NET are Authentication, Caching for pre-processing and various encoding mechanisms on post processing.

 

There’s plenty of information available on HttpHandlers and HttpModules so to keep this article a reasonable length I’m going to provide only a brief overview of handlers.

 

HttpModules

As requests move through the pipeline a number of events fire on the HttpApplication object. We’ve already seen that these events are published as event methods in Global.asax. This approach is application specific though which is not always what you want. If you want to build generic HttpApplication event hooks that can be plugged into any Web applications you can use HttpModules which are reusable and don’t require application specific code except for an entry in web.config.

 

Modules are in essence filters – similar in functionality to ISAPI filters at the ASP.NET request level. Modules allow hooking events for EVERY request that pass through the ASP.NET HttpApplication object. These modules are stored as classes in external assemblies that are configured in web.config and loaded when the Application starts. By implementing specific interfaces and methods the module then gets hooked up to the HttpApplication event chain. Multiple HttpModules can hook the same event and event ordering is determined by the order they are declared in Web.config. Here’s what a handler definition looks like in Web.config:

 

<configuration>

<system.web>

<httpModules>

<add name= “BasicAuthModule”

type=”HttpHandlers.BasicAuth,WebStore” />

</httpModules>

</system.web>

</configuration>

 

Note that you need to specify a full typename and an assembly name without the DLL extension.

 

Modules allow you look at each incoming Web request and perform an action based on the events that fire. Modules are great to modify request or response content, to provide custom authentication or otherwise provide pre or post processing to every request that occurs against ASP.NET in a particular application. Many of ASP.NET’s features like the Authentication and Session engines are implemented as HTTP Modules.

 

While HttpModules feel similar to ISAPI Filters in that they look at every request in that comes through an ASP.NET Application, they are limited to looking at requests mapped to a single specific ASP.NET application or virtual directory and then only against requests that are mapped to ASP.NET. Thus you can look at all ASPX pages or any of the other custom extensions that are mapped to this application. You cannot however look at standard .HTM or image files unless you explicitly map the extension to the ASP.NET ISAPI dll by adding an extension as shown in Figure 1. A common use for a module might be to filter content to JPG images in a special folder and display a ‘SAMPLE’ overlay ontop of every image by drawing ontop of the returned bitmap with GDI+.

 

Implementing an HTTP Module is very easy: You must implement the IHttpModule interface which contains only two methods Init() and Dispose(). The event parameters passed include a reference to the HTTPApplication object, which in turn gives you access to the HttpContext object. In these methods you hook up to HttpApplication events. For example, if you want to hook the AuthenticateRequest event with a module you would do what’s shown in Listing 5.

 

Listing 5: The basics of an HTTP Module are very simple to implement

public class BasicAuthCustomModule : IHttpModule

{

 

public void Init(HttpApplication application)

{

// *** Hook up any HttpApplication events

application.AuthenticateRequest +=

new EventHandler(this.OnAuthenticateRequest);

}

public void Dispose() { }

 

public void OnAuthenticateRequest(object source, EventArgs eventArgs)

{

HttpApplication app = (HttpApplication) source;

HttpContext Context = HttpContext.Current;

do what you have to do…                                         }

}

 

Remember that your Module has access the HttpContext object and from there to all the other intrinsic ASP.NET pipeline objects like Response and Request, so you can retrieve input etc. But keep in mind that certain things may not be available until later in the chain.

 

You can hook multiple events in the Init() method so your module can manage multiple functionally different operations in one module. However, it’s probably cleaner to separate differing logic out into separate classes to make sure the module is modular. <g> In many cases functionality that you implement may require that you hook multiple events – for example a logging filter might log the start time of a request in Begin Request and then write the request completion into the log in EndRequest.

 

Watch out for one important gotcha with HttpModules and HttpApplication events: Response.End() or HttpApplication.CompleteRequest() will shortcut the HttpApplication and Module event chain. See the sidebar “Watch out for Response.End() “ for more info.

 

HttpHandlers

Modules are fairly low level and fire against every inbound request to the ASP.NET application. Http Handlers are more focused and operate on a specific request mapping, usually a page extension that is mapped to the handler.

 

Http Handler implementations are very basic in their requirements, but through access of the HttpContext object a lot of power is available. Http Handlers are implemented through a very simple IHttpHandler interface (or its asynchronous cousin, IHttpAsyncHandler) which consists of merely a single method – ProcessRequest() – and a single property IsReusable. The key is ProcessRequest() which gets passed an instance of the HttpContext object. This single method is responsible for handling a Web request start to finish.

 

Single, simple method? Must be too simple, right? Well, simple interface, but not simplistic in what’s possible! Remember that WebForms and WebServices are both implemented as Http Handlers, so there’s a lot of power wrapped up in this seemingly simplistic interface. The key is the fact that by the time an Http Handler is reached all of ASP.NET’s internal objects are set up and configured to start processing of requests. The key is the HttpContext object, which provides all of the relevant request functionality to retireve input and send output back to the Web Server.

 

For an HTTP Handler all action occurs through this single call to ProcessRequest(). This can be as simple as:

 

public void ProcessRequest(HttpContext context)

{

context.Response.Write(“Hello World”);

}

 

to a full implementation like the WebForms Page engine that can render complex forms from HTML templates. The point is that it’s up to you to decide of what you want to do with this simple, but powerful interface!

 

Because the Context object is available to you, you get access to the Request, Response, Session and Cache objects, so you have all the key features of an ASP.NET request at your disposal to figure out what users submitted and return content you generate back to the client. Remember the Context object – it’s your friend throughout the lifetime of an ASP.NET request!

 

The key operation of the handler should be eventually write output into the Respone object or more specifically the Response object’s OutputStream. This output is what actually gets sent back to the client. Behind the scenes the ISAPIWorkerRequest manages sending the OutputStream back into the ISAPI ecb.WriteClient method that actually performs the IIS output generation.

 

 

Figure 7 – The ASP.NET Request pipeline flows requests through a set of event interfaces that provide much flexibility. The Application acts as the hosting container that loads up the Web application and fires events as requests come in and pass through the pipeline. Each request follows a common path through the Http Filters and Modules configured. Filters can examine each request going through the pipeline and Handlers allow implementation of application logic or application level interfaces like Web Forms and Web Services. To provide Input and Output for the application the Context object provides request specific information throughout the entire process.

 

WebForms implements an Http Handler with a much more high level interface on top of this very basic framework, but eventually a WebForm’s Render() method simply ends up using an HtmlTextWriter object to write its final final output to the context.Response.OutputStream. So while very fancy, ultimately even a high level tool like Web forms is just a high level abstraction ontop of the Request and Response object.

 

You might wonder at this point whether you need to deal with Http Handlers at all. After all WebForms provides an easily accessible Http Handler implementation, so why bother with something a lot more low level and give up that flexibility?

 

WebForms are great for generating complex HTML pages and business level logic that requires graphical layout tools and template backed pages. But the WebForms engine performs a lot of tasks that are overhead intensive. If all you want to do is read a file from the system and return it back through code it’s much more efficient to bypass the Web Forms Page framework and directly feed the file back. If you do things like Image Serving from a Database there’s no need to go into the Page framework – you don’t need templates and there surely is no Web UI that requires you to capture events off an Image served.

There’s no reason to set up a page object and session and hook up Page level events – all of that stuff requires execution of code that has nothing to do with your task at hand.

 

So handlers are more efficient. Handlers also can do things that aren’t possible with WebForms such as the ability to process requests without the need to have a physical file on disk, which is known as a virtual Url. To do this make sure you turn off ‘Check that file exists’ checkbox in the Application Extension dialog shown in Figure 1.

 

This is common for content providers, such as dynamic image processing, XML servers, URL Redirectors providing vanity Urls, download managers and the like, none of which would benefit from the WebForm engine.

Have I stooped low enough for you?

Phew – we’ve come full circle here for the processing cycle of requests. That’s a lot of low level information and I haven’t even gone into great detail about how HTTP Modules and HTTP Handlers work. It took some time to dig up this information and I hope this gives you some of the same satisfaction it gave me in understanding how ASP.NET works under the covers.

 

Before I’m done let’s do the quick review of the event sequences I’ve discussed in this article from IIS to handler:

 

  • IIS gets the request
  • Looks up a script map extension and maps to aspnet_isapi.dll
  • Code hits the worker process (aspnet_wp.exe in IIS5 or w3wp.exe in IIS6)
  • .NET runtime is loaded
  • IsapiRuntime.ProcessRequest() called by non-managed code
  • IsapiWorkerRequest created once per request
  • HttpRuntime.ProcessRequest() called with Worker Request
  • HttpContext Object created by passing Worker Request as input
  • HttpApplication.GetApplicationInstance() called with Context to retrieve instance from pool
  • HttpApplication.Init() called to start pipeline event sequence and hook up modules and handlers
  • HttpApplicaton.ProcessRequest called to start processing
  • Pipeline events fire
  • Handlers are called and ProcessRequest method are fired
  • Control returns to pipeline and post request events fire

 

It’s a lot easier to remember how all of the pieces fit together with this simple list handy. I look at it from time to time to remember. So now, get back to work and do something non-abstract…

 

Although what I discuss here is based on ASP.NET 1.1, it looks that the underlying processes described here haven’t changed in ASP.NET 2.0.

 

Many thanks to Mike Volodarsky from Microsoft for reviewing this article and providing a few additional hints and Michele Leroux Bustamante for providing the basis for the ASP.NET Pipeline Request Flow slide.

 

If you have any comments or questions feel free to post them on the Comment link below.

从底层了解ASP.NET架构

让我们回到之前略过的一个话题:当请求到达时,.NET运行时是如何被加载的。具体在哪里加载的,这是比较模糊的。关于这个处理过程,我没有找到相关的文档,由于我们现在讨论的是本地代码,所以通过反编译ISAPI DLL文件并把它描述出来显得不太容易。
最佳猜测是,在ISAPI扩展里,当第一个请求命中一个ASP.NET的映射扩展时,工作线程就会引导.NET运行时启动。一旦运行时存在了,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime对象的实例,当然前提条件是,这个实例还不存在。每一个虚拟目录都会拥有一个 AppDomain,在ISAPIRuntime存在的AppDomain里,它将引导一个单独的程序启动。由于接口被作为COM可调用的方法暴露,所以实例化操作将发生在COM之上。
为了创建ISAPIRuntime的实例,当指定虚拟目录的第一个请求到达时,System.Web.Hosting.AppDomainFactory.Create()方法将被调用。这将会启动程序的引导过程。这个方法接收的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET用于创建AppDomain,接着会启动指定虚拟目录的ASP.NET程序。 HttpRuntime的根对象将会在一个新的AppDomain里创建。每一个虚拟目录或者ASP.NET程序将寄宿在属于自己的AppDomain 里。它们仅仅在有请求到达时启动。ISAPI扩展管理这些HttpRuntime对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程序里。
回到运行时
    这个时候已经拥有了一个ISAPIRuntime的活动实例,并且可以在ISAPI扩展里调用。一旦运行时启动并运行起来,ISAPI扩展就可以调用 ISAPIRuntime.ProcessRequest()方法了,而这个方法就是进入ASP.NET通道真正的登录点。图1展示了这里的流程。

图1把ISAPI的请求转到ASP.NET通道需要调用很多没有正式文档的类和接口,以及几个工厂方法。每一个Web程序/虚拟目录都运行在属于自己的 AppDomain里。调用者将维护一个IISAPIRuntime接口的代理引用,负责触发ASP.NET的请求处理。记住:ISAPI是多线程的,因此请求可以以多线程的方式穿过AppDomainFactory.Create()返回的对象引用。列表1展现了从 IsapiRuntime.ProcessRequest方法反编译得到的代码。这个方法接收一个ISAPI ecb对象和一个服务器类型参数(这个参数用于指定创建何种版本的ISAPIWorkerRequest),这个方法是线程安全的,因此多个ISAPI线程可以同时安全的调用单个返回对象的实例。
列表 1: ProcessRequest请求进入 .NET的登录点

需要提醒的是,这里的代码是通过反编译.NET框架内的代码得到的,我们永远也不会和这些代码打交道,而且这些代码以后可能会有所变动。这里的用意是揭示 ASP.NET在底层发生了什么。ProcessRequest接收了非托管参数ecb的引用,然后把它传给了ISAPIWorkerRequest对象,这个对象负责创建当前请求的内容。如列表2所示。
列表2: 一个ISAPIWorkerRequest 的方法

System.Web.Hosting.ISAPIWorkerRequest继承于抽象类HttpWorkerRequest,它的职责是创建一个抽象的输入和输出视图,为Web程序的输入提供服务。注意这里的另外一个工厂方法CreateWorkerRequest,它的第二个参数用于指定创建什么样的工作请求对象(即ISAPIWorkerRequest的派生类)。这里有3个不同的版本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc。当请求到来时,这个对象(指ISAPIWorkerRequest对象)将被创建,用于给Request和Response对象提供基础服务,而这两个对象将从数据的提供者WorkerRequest接收数据流。
抽象类HttpWorkerRequest围绕着底层的接口提供了高层的抽象(译注:抽象的目的是要把数据的处理与数据的来源解藕)。这样,就不用考虑数据的来源,无论它是一个CGI Web Server,Web浏览器控件还是你自定义的机制(用于把数据流入HTTP运行时),ASP.NET都可以以同样的方式从中获取数据。
有关IIS的抽象主要集中在ISAPI ECB块。在我们的请求处理当中,ISAPIWorkerRequest依赖于ISAPI ECB,当有需要的时候,会从中读取数据。列表2展示了如何从ECB里获取查询字符串的值的例子。
ISAPIWorkerRequest实现了一个高层次包装器方法(wrapper method),它调用了低层次的核心方法,而这些方法负责实际调用非托管API或者说是“服务层的实现”。核心的方法在 ISAPIWorkerRequest的派生类里得以实现。这样可以针对它宿主的环境提供特定的实现。为以后增加一个额外环境的实现类作为新的Web Server接口提供了便利。同样使ASP.NET运行在其它平台上成为可能。另外这里还有一个帮助类:System.Web.UnsafeNativeMethods。它的许多方法是对ISAPI ECB进行操作,用于执行关于ISAPI扩展的非托管操作。

HttpRuntime,HttpContext以及HttpApplication
   当一个请求到来时,它将被路由到ISAPIRuntime.ProcessRequest()方法里。这个方法会接着调用 HttpRuntime.ProcessRequest,在这个方法里,做了几件重要的事情(使用Refector反编译 System.Web.HttpRuntime.ProcessRequestInternal可以看到)。    为请求创建了一个新的HttpContext实例
   获取一个HttpApplication实例    调用HttpApplication.Init()初始化管道事件
nit()触发HttpApplication.ResumeProcessing(),启动ASP.NET管道处理    首先,一个新的HttpContext对象被创建,并且给它传递一个封装了ISAPI ECB 的ISAPIWorkerRequest。在请求的生命周期里,这个上下文(context)一直是有效的。并且可以通过静态的 HttpContext.Current属性访问。正如它的名字暗示的那样,HttpContext对象表示当前活动请求的上下文,因为它包含了在请求生命周期里你会用到的所有必需对象的引用,如:Request,Response,Application,Server,Cache。在请求处理过程的任何时候,你都可以使用HttpContext.Current访问这些对象。
HttpContext对象还包含了一个非常有用的列表集合,你可以使用它存储有关特定的请求需要的数据。上下文(context)对象创建于一个请求生命周期的开始,在请求结束时被释放。因此,保存在列表集合里的数据仅仅对当前的请求有效。一个很好的例子,就是记录请求的日志机制,在这里,通过使用 Global.asax里的Application_BeginRequest和Application_EndRequest方法,你可以从请求的开始时间至结束时间段内,对请求进行跟踪。如列表3所示。记住HttpContext在请求或者页面处理的不同阶段,如果需要相关数据都可以使用它获取。
列表 3: 通过在通道事件里使用HttpContext.Items 集合保存数据

一旦请求的上下文对象被搭建起来,ASP.NET就需要通过一个HttpApplication对象,把你的请求路由到合适的程序/虚拟目录里。每一个ASP.NET程序都拥有各自的虚拟目录(Web根目录),并且它们都是独立处理请求的。

Web程序的主要部分:HttpApplication
   每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个 HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用。这个 HttpApplication对象池的大小可以通过machine.config里的ProcessModel节点中的 MaxWorkerThreads选项配置,默认值是20。
HttpApplication对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有多个请求需要处理时,池中的对象将会随之增加。而 HttpApplication对象池,也将会被监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的值。
对于Web程序而言,HttpApplication是一个外部容器,它对应到Global.asax文件里定义的类。基于标准的Web程序,它是实际可以看到的进入HTTP运行时的第一个登录点。如果你查看Global.asax(后台代码),你就会看到这个类直接派生于 HttpApplication。
public class Global : System.Web.HttpApplication
HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括: BeginRequest
AuthenticateRequest AuthorizeRequest ResolveRequestCache    [此处创建处理程序(即与请求 URL 对应的页)。] AcquireRequestState
PreRequestHandlerExecute    [执行处理程序。] PostRequestHandlerExecute
ReleaseRequestState    [响应筛选器(如果有的话),筛选输出。] UpdateRequestCache
EndRequest     这里的每一个事件都在Global.asax文件中以Application_为前缀,无实现代码的方法出现。举个例子,如 Application_BeginRequest()和Application_AuthorizeRequest()。由于它们在程序中会经常用到,所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。
每一个ASP.NET Web程序运行在各自的AppDomain里,在AppDomain里同时运行着多个HttpApplication的实例,这些实例存放在 ASP.NET管理的一个HttpApplication对象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相干扰的原因。

使用列表4的代码,可以进一步了解AppDomain,线程,HttpApplication之间的关系。
列表 4: AppDomain, Threads and HttpApplication instances之间的关系

这是样例程序的一部分,运行的结果如图5所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID的值。

图2同时运行几个浏览器,你会很容易的看到AppDomains,application对象以及处理请求的线程之间内在的关系。当多个请求触发时会看到线程和application的ID在改变,而AppDomain的ID却没有发生变化。
观察到AppDomain ID一直保持不变,而线程和HttpApplication的ID在请求多的时候会发生改变,尽管它们会出现重复。这是因为 HttpApplications是在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication实例,所以有时候 HttpApplication的ID会重复。
注意:一个HttpApplication实例对象并不依赖于一个特定的线程,它们仅仅是被分配给处理当前请求的线程而已。
线程由.NET的ThreadPool提供服务,默认情况下,线程模型为多线程单元(MTA)。你可以通过在ASP.NET的页面的@Page指令里设置属性ASPCOMPAT=”true”覆盖线程单元的状态。ASPCOMPAT意味着COM组件将在一个安全的环境下运行。ASPCOMPAT使用了单线程单元(STA)的线程为请求提供服务。STA线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。
实际上,这些HttpApplication对象运行在同一个AppDomain里是很重要的。这就是ASP.NET如何保证web.config的改变或者单独的ASP.NET页面得到验证可以贯穿整个AppDomain。改变web.config里的一个值,将导致AppDomain关闭并重新启动。这确保了所有的HttpApplication实例可以看到这些改变,这是因为当AppDomain重新加载的时候,来自ASP.NET的那些改变将会在 AppDomain启动的时候重新读取。当AppDomain重新启动的时候任何静态的引用都将重新加载。这样,如果程序是从程序的配置文件读取的值,这些值将会被刷新。
在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx页面,注意观察AppDomain的ID。然后在 web.config里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会在原来的页面上刷新无效,需要先删除缓存再刷新即可),你就会看到一个新的AppDomain被创建了。
本质上,这些改变将引起Web程序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的 AppDomain里。为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET将强制关闭AppDomain甚至某些请求还没有被处理。因此,在一个特定的时间点上,同一个HttpApplication实例在两个AppDomain里存在是有可能的,这个时间点就是旧的AppDomain 正在关闭,而新的AppDomain正在启动。这两个AppDomain将继续为客户端请求提供服务,直到旧的AppDomain处理完所有的未处理的请求,然后关闭,这时候才会仅剩下新的AppDomain在运行。