Please see the original article first: ASP.NET 1.1 Web Application Compilation and Pre-compilation.
Introduction
The idea given by Peter Tewkesbury:
The requirement to change the IIS configuration for embedded resources troubles me. I look after a number of websites, which are hosted for me and I do not have access to the IIS settings. On my websites, I have a handler that creates thumbnail images from the full image. The code for this is not important.
This is the idea that I have had:
- Add a new handler called XYZ.
- The XYZ handler when it is called with a parameter, name of resource, would get and return this file.
- When you pre-compile the source, you could scan the code for objects which access these files (Image object, HTML code with src tag etc.) and then patch the code so that:
- the resource is included in the assembly,
- and the compiled code is patched so that the XYZ handler is called with the correct parameter so the correct resource file is returned.
Then you don't need to change any IIS settings - A big plus for everyone who has hosted websites.
This article is concerned with the implementation of this idea and its usage in your application. The source code is also rewritten and has a better design so that you can use this instead of the one in the old article.
Idea
As the previous article describes it is possible to embed any external file that your application requires (e.g. .gif, .css, etc.) in an assembly and requests to such files are to be served by your application instead of IIS. In order that IIS forwards requests to such files (e.g. "/MyApp/Css/Global.css") to ASP.NET runtime (and your application) the file name extensions should be mapped at IIS to aspnet_isapi.dll. Additionally, the extensions should be mapped to System.Web.IHttpHander
implementator (e.g. WebApp.Web.Compilation.Runtime.ResourceFileHandler
) that will load the file content from the assembly and return it as a response to the requests. This mapping is done through web.config. If, for example, you want your application to serve requests to .css and .gif files you need to manually (or by using script) map these extensions at IIS to aspnet_isapi.dll and add the following lines in your web.config:
<system.web>
<httpHandlers>
<add verb="*" path="*.css"
type="WebApp.Web.Compilation.Runtime.ResourceFileHandler,
WebApp.Web.Compilation.Runtime" />
<add verb="*" path="*.gif"
type="WebApp.Web.Compilation.Runtime.ResourceFileHandler,
WebApp.Web.Compilation.Runtime" />
</httpHandlers>
</system.web>
However, changing the IIS settings make the deployment of your application more difficult and it may not be always possible to do that. The solution is to make requests to such files to appear, as they are requests to files whose extensions are already registered at IIS: for example .aspx or .asmx. This may be done by substituting links to such files (e.g. "/MyApp/Css/Global.css") by links of type "handler?p=file_id" (e.g. "FH.aspx?p=Css/Global.css"). "handler" should have a path whose extension is already mapped at IIS to aspnet_isapi.dll (.aspx, .asmx, etc.) and file_id
should be string by which the Win32 resource that corresponds to the file can be loaded from the assembly. As a result, no IIS settings need be changed but the following lines should be presented in your web.config:
<system.web>
<httpHandlers>
<add verb="*" path="FH.aspx"
type="WebApp.Web.Compilation.Runtime.ResourceFileHandler,
WebApp.Web.Compilation.Runtime" />
</httpHandlers>
</system.web>
Implementation
A solution should target the following issues:
- Embedding files
- Links substitution
- Serving requests to embedded files.
Embedding files
Embedding files to assembly is done by the WebApp.Web.Compilation.ResourceBuilder
type. It enables building of Win32 Resource (.res) file which is to be included in compilation. The type provides two kinds of methods:
- Adding an unnamed data or file by specifying Win32 resource type and name (unsigned short).
- Adding a named data or file by specifying Win32 resource type and name (unsigned short).
If a resource is named then later on it can be accessed only by its name by the WebApp.Web.Compilation.Runtime.ResourceManager
type. The name of a resource is of System.String
type and differs from the Win32 resource name, which is of type System.UInt16
. To enable support of string names of resources a common index is built and saved as an additional resource. Note: Although a Win32 resource name and/or type can be a string this is not implemented.
The main issue is how to choose the name for the resource of a file being added:
- If all links to the file are to be substituted by links of type "handler?p=file_id" the name can be any string that is unique among all named resources. Subsequently, the value of "file_id" should be that name so the resource can be loaded on request at runtime. The only issue is that the name should denote the extension of the file in some way so an appropriate content-type can be returned with the HTTP response.
- If the links to the file are to remain untouched, the name of the resource should be derived from the virtual path to the file so that the resource can be located at runtime.
This solution provides two WebApp.Web.Compilation.Runtime.IResourceNameProvider
implementations that are responsible to provide names for files being added as resources:
WebApp.Web.Compilation.Runtime.RelativePathNameProvider
WebApp.Web.Compilation.Runtime.EncodedNameProvider
The RelativePathNameProvider
derives the name of the resource from the relative path to the file based on the application's root directory. That is, if the application is located in C:\MyApp and the file is C:\MyApp\Css\Global.css the name would be Css/Global.css.
EncodedNameProvider
does the same as the RelativePathNameProvider
but applies base64 encoding on the relative path.
Links substitution
The following files that may contain links to other embedded files should be the target:
- Pages and user controls: .aspx and .acsx.
- Resource files: .htm, .html, .js, .css etc.
The following strategy is used:
- Only links to embedded files should be substituted: that is links to files whose extensions are specified to be embedded as resources and that are within the scope of the application (i.e. the file physically resides within the subdirectory tree of the application's base directory). Note: to determine if a file is within the scope of the application the physical path is obtained by the
System.Web.HttpRequest.MapPath(string,string,bool)
method.
- A link is any string that begins and ends with '"', ''', '(', ')' and contains the path that ends with one of the target file name extensions (exactly the regular expression used is
@"('|""|\()(?<1>(\w|[.% \/~])*?\.(htm|html|js|...))('|""|\))"
). This allows links to be located regardless of the name of the tag and attribute as they are specified in JavaScript/CSS code. For more information, see the WebApp.Web.Compilation.FileReferencesFixer
type.
Pages and user controls
Links in such files are substituted prior to the files to be parsed by the System.Web.UI.TemplateParser
descendants (i.e. a file's content is read, altered and then passed to the parser). Because of the way links are searched links specified within tags that has runat="server"
attribute specified are also handled (e.g. <asp:image ImageUrl="~/Images/Logo.gif" runat="server">
will become <asp:image ImageUrl="FH.aspx?p=Images/Logo.gif" runat="server">
).
Note: Only links that reside within .aspx and .asxc files will be substituted. Any links in code-behind should be handled manually by redesigning the code.
Resource files
Links in such files are substituted prior to adding their content to Win32 resource file (i.e. a file is read, altered and then appended to the resource file). That means only the files that are embedded itself will be processed. Links in files that are not specified to be embedded will not be fixed.
Choosing handler name
Generally, there are two types of handler names to choose from: relative to the current virtual path (e.g. "FH.aspx") and relative to the web site root (e.g. "/FH.aspx", "/MyApp/FH.aspx"). The differences are:
- The way a browser will see the links: if you specify "FH.aspx" as a handler name all links will appear as "FH.aspx?p= ..." which is relative to the virtual path where the file containing the link is located. That means if you have /Default.aspx that contains
<link href="FH.aspx?p=Css/Global.css" rel="stylesheet">
and /SubDir/WebForm1.aspx that contains <link href="FH.aspx?p=Css/Global.css" rel="stylesheet">
a browser will see links to two different files: "/FH.aspx?p= Css/Global.css" and "/SubDir/FH.aspx?p= Css/Global.css" and will request "Css/Global.css" twice (note that "Css/Global.css" is not the relative path to a file but the name of a resource). Because of that links in files that reside in subdirectories are adjusted by adding number of "../" strings in front of the handler's name: e.g. the link in /SubDir/WebForm1.aspx would become "../FH.aspx?p=Css/Global.css". That will cause a browser to request "Css/Global.css" only once. Otherwise if you specify "/FH.aspx?p=..." or "/MyApp/FH.aspx?p=..." as a handler name all links to the same file will be the same (e.g. "/MyApp/FH.aspx?p=Css/Global.css").
- Deployment: if you choose a handler name relative to the virtual path (e.g. "FH.aspx") the application can be deployed in any virtual directory, as the links does not include any path dependent information. If you choose a name relative to the web site root the application should be deployed in a specific virtual directory.
If you need to generate links in your code-behind you can use the System.Web.UI.Control.ResolveUrl
method (e.g. ResolveUrl( "~/FH.aspx?p=..." )
).
Serving requests to embedded files
At runtime, requests are served by the WebApp.Web.Compilation.Runtime.ResourceFileHandler
type. All you need to do is to map the name of the handler to this type in your web.config:
<system.web>
<httpHandlers>
<add verb="*" path="FH.aspx"
type="WebApp.Web.Compilation.Runtime.ResourceFileHandler,
WebApp.Web.Compilation.Runtime" />
</httpHandlers>
</system.web>
Note that the value of the path
attribute does not include directory information (e.g. "/FH.aspx", "/MyApp/FH.aspx"). That will cause ASP.NET runtime to forward all requests made to "FH.aspx" anywhere within the application (e.g. "/FH.aspx", "/MyApp/FH.aspx", "/MyApp/SubDir/FH.aspx") to the WebApp.Web.Compilation.Runtime.ResourceFileHandler
type.
Additionally, you will need to specify the type of the resource name provider (WebApp.Web.Compilation.Runtime.IResourceNameProvider
descendant) in the WebApp.Web.Compilation.Runtime.ResourceFileManager
configuration section:
<WebApp.Web.Compilation.Runtime.ResourceFileManager>
<add key="ResourceNameProvider"
value="WebApp.Web.Compilation.Runtime.RelativePathNameProvider"/>
</WebApp.Web.Compilation.Runtime.ResourceFileManager>
or
<WebApp.Web.Compilation.Runtime.ResourceFileManager>
<add key="ResourceNameProvider"
value="WebApp.Web.Compilation.Runtime.EncodedNameProvider" />
</WebApp.Web.Compilation.Runtime.ResourceFileManager>
Final notes
- There are changes in the configuration sections compared to the previous version of the library. Please see the source code and demo.
- Applications complied with the previous version may not be compatible with this one.
Bugs
- Please report bugs, if any.