ASP.NET provides a number of options for extending the path that data takes between client and server (known as the HTTP Pipeline). A popular method to extend the pipeline is through the use of custom components known as HTTP modules. An HTTP module enables you to add pre-and post-processing to each HTTP request coming into your application.
DotNetNuke implements a number of HTTP modules to extend the pipeline. They include features such as URL Rewriting, Exception Management, Users Online, Profile, Anonymous Identification, Role Management, DotNetNuke Membership, and Personalization.
Originally, a lot of the HTTP modules were implemented inside the core application (global.asax.vb). There were a number of reasons why the functionally was moved to HTTP modules:
Administrators can optionally enable or disable an HTTP module.
Developers can replace or modify HTTP modules without altering the core application.
Provides templates for extending the HTTP Pipeline.
This section further examines the concepts of HTTP modules so you'll know when and where to implement them. To comprehend how HTTP modules work, it's necessary to understand the HTTP Pipeline and how ASP.NET processes incoming requests. Figure 8-1 shows the HTTP Pipeline.
When a request is first made, it passes through a number of stages before it is actually handled by your application. The first participant in the pipeline is Microsoft Internet Information Server (IIS); its job is to route ASP.NET requests to the ASP.NET runtime. When an ASPX file is requested (or any other ASP.NET file), IIS forwards the request to the ASP.NET runtime (via an ISAPI extension).
Now that the request has been received by ASP.NET, it must pass through an instance of HttpApplication. The HttpApplication object handles application-wide methods, data, and events. It is also responsible for pushing the request through one or more HTTP module objects. The ASP.NET runtime determines which modules to load by examining the configuration files located at either machine level (machine.config) or application level (web.config). Listing 8-10 shows the HTTP modules configuration section of DotNetNuke.
Listing 8-10: HTTP Modules Configuration Section
<httpModules> <add name="UrlRewrite" type="DotNetNuke.HttpModules.UrlRewriteModule, DotNetNuke.HttpModules.UrlRewrite" /> <add name="Exception" type="DotNetNuke.HttpModules.ExceptionModule, DotNetNuke.HttpModules.Exception" /> <add name="UsersOnline" type="DotNetNuke.HttpModules.UsersOnlineModule, DotNetNuke.HttpModules.UsersOnline" /> <add name="Profile" type="Microsoft.ScalableHosting.Profile.ProfileModule, MemberRole, Version=22.214.171.124, Culture=neutral, PublicKeyToken=b7c773fb104e7562" /> <add name="AnonymousIdentification" type="Microsoft.ScalableHosting.Security.AnonymousIdentificationModule, MemberRole, Version=126.96.36.199, Culture=neutral, PublicKeyToken=b7c773fb104e7562" /> <add name="RoleManager" type="Microsoft.ScalableHosting.Security.RoleManagerModule, MemberRole, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b7c773fb104e7562" /> <add name="DNNMembership" type="DotNetNuke.HttpModules.DNNMembershipModule, DotNetNuke.HttpModules.DNNMembership" /> <add name="Personalization" type="DotNetNuke.HttpModules.PersonalizationModule, DotNetNuke.HttpModules.Personalization" /> </httpModules>
To invoke each HTTP module, the Init method of each module is invoked. At the end of each request, the Dispose method is invoked to enable each HTTP module to clean up its resources. In fact, those two methods form the interface (IHttpModule) each module must implement. Listing 8-11 shows the IHttpModule interface.
Listing 8-11: The IHttpModule Interface Implemented by Each HTTP Module
Public Interface IHttpModule Sub Init(ByVal context As HttpApplication) Sub Dispose() End Interface
During the Init event, each module may subscribe to a number of events raised by the HttpApplication object. Table 8-3 shows the events that are raised before the application executes. The events are listed in the order in which they occur.
Signals a new request; guaranteed to be raised on each request.
Signals that the request is ready to be authenticated; used by the Security module.
Signals that the request is ready to be authorized; used by the Security module.
Used by the Output Cache module to short-circuit the processing of requests that have been cached.
Signals that the per-request state should be obtained.
Signals that the request handler is about to execute. This is the last event you can participate in before the HTTP handler for this request is called.
Table 8-4 shows the events that are raised after an application has returned. The events are listed in the order in which they occur.
Signals that the HTTP handler has completed processing the request.
Signals that the request state should be stored because the application is finished with the request.
Signals that code processing is complete and the file is ready to be added to the ASP.NET cache.
Signals that all processing has finished for the request. This is the last event called when the application ends.
In addition, there are three per-request events that can be raised in a nondeterministic order. They are described in Table 8-5.
Signals that HTTP headers are about to be sent to the client. This provides an opportunity to add, remove, or modify the headers before they are sent.
Signals that content is about to be sent to the client. This provides an opportunity to modify the content before it is sent.
Signals an unhandled exception.
After the request has been pushed through the HTTP modules configured for your application, the HTTP handler responsible for the requested file's extension (.ASPX) handles the processing of that file. If you are familiar with ASP.NET development, you'll be familiar with the handler for an ASPX page — System.Web.UI.Page. The HTTP handler then handles the life cycle of the page-level request raising events such as Page_Init, Page_Load, and so on.
As stated earlier, DotNetNuke (like ASP.NET) comes with a number of HTTP modules. These modules enable developers to customize the HTTP Pipeline to provide additional functionality on each request. In this section, you explore several DotNetNuke HTTP modules, and examine their purpose and possibilities for extension.
The URL rewriter is an HTTP module that provides a mechanism for mapping virtual resource names to physical resource names at runtime — in other words, it provides a URL that is friendly. The term "friendly" has two aspects. One is to make the URL search-engine friendly, which is solved with the default implementation.
Most search engines ignore URL parameters, and because DotNetNuke relies on URL parameters to navigate to portal pages, the older application is not search-engine friendly. To effectively index your site, you need a parameterless mechanism for constructing URLs that search engines will process.
If you browse to a DotNetNuke site that is version 3.0 or greater, you may notice different URLs from earlier versions. Traditionally, a DotNetNuke URL looks something like the following:
With friendly URLs enabled, the preceding URL might look like this:
URL rewriter is invoked during the HTTP Pipeline's processing of a request and can optionally subscribe to application-wide events. The particular event of interest for this module is BeginRequest. This event enables you to modify the URL before the Page HTTP handler is invoked and make it believe the URL requested was that of the old non-friendly format.
The transformation process occurs through the use of regular expressions defined in SiteUrls.config in the root of your DotNetNuke installation. This file contains a number of expressions to LookFor and with corresponding URLs to SendTo. Listing 8-12 shows the default SiteUrls.config.
Listing 8-12: SiteUrls.config
<?xml version="1.0" encoding="utf-8" ?> <RewriterConfig> <Rules> <RewriterRule> <LookFor>.*/TabId/(\d+)(.*)/Logoff.aspx</LookFor> <SendTo>~/Admin/Security/Logoff.aspx?tabid=$1</SendTo> </RewriterRule> <RewriterRule> <LookFor>.*/TabId/(\d+)(.*)/rss.aspx</LookFor> <SendTo>~/rss.aspx?TabId=$1</SendTo> </RewriterRule> <RewriterRule> <LookFor>[^?]*/TabId/(\d+)(.*)</LookFor> <SendTo>~/Default.aspx?TabId=$1</SendTo> </RewriterRule> </Rules> </RewriterConfig>
The rules defined in this configuration file cover the default login and logoff page. You could potentially add any number of additional rules, and even hardcode some extra rules in there. For example, if you wanted to hardcode a link such as http://www.dotnetnuke.com/FriendlyUrl.aspx and have it map to another URL elsewhere, your entry might look like Listing 8-13.
Listing 8-13: SiteUrls.config with a Modified Rule
<?xml version="1.0" encoding="utf-8" ?> <RewriterConfig> <Rules> <RewriterRule> <LookFor>.*/FriendlyUrl.aspx</LookFor> <SendTo>~/default.aspx?tabid=622</SendTo> </RewriterRule> <RewriterRule> <LookFor>.*/TabId/(\d+)(.*)/Logoff.aspx</LookFor> <SendTo>~/Admin/Security/Logoff.aspx?tabid=$1</SendTo> </RewriterRule> <RewriterRule> <LookFor>.*/TabId/(\d+)(.*)/rss.aspx</LookFor> <SendTo>~/rss.aspx?TabId=$1</SendTo> </RewriterRule> <RewriterRule> <LookFor>[^?]*/TabId/(\d+)(.*)</LookFor> <SendTo>~/Default.aspx?TabId=$1</SendTo> </RewriterRule> </Rules> </RewriterConfig>
The preceding URL scheme is an excellent implementation for your own applications as well, particularly those with fixed pages. Unfortunately DotNetNuke has potentially any number of pages, so the team added some functionality that would transform any number of query string parameters.
Take a look at the default scheme for URL rewriting. You can see from the friendly URL shown earlier (http://www.dotnetnuke.com/RoadMap/Friendly URLs/tabid/622/default.aspx) that the requirement is met — that is, the URL would have no parameters. URLs generally adhere to the following pattern:
http://www.dotnetnuke.com/: The site Host URL.
RoadMap/Friendly URLs/: The breadcrumb path back to the home page.
tabid/622/: The query string from the original URL transformed (?tabid=622).
default.aspx: The standard web page for DotNetNuke.
The advantage of this scheme is that it requires no database lookups for the transformation, just raw regular expression processing that is typically quite fast.
For some situations, the breadcrumb path may not be desired. In those cases, simply modify the web.config friendlyUrl provider setting to turn off the feature. To turn it off, change the includePageName value shown in Listing 8-14 from "true" to "false":
Listing 8-14: Modifying SiteUrls.config
<friendlyUrl defaultProvider="DNNFriendlyUrl"> <providers> <clear /> <add name="DNNFriendlyUrl" type="DotNetNuke.Services.Url.FriendlyUrl.DNNFriendlyUrlProvider, DotNetNuke.HttpModules.UrlRewrite" includePageName="true" regexMatch="[^a-zA-Z0-9 _-]" /> </providers> </friendlyUrl>
Earlier in this chapter, it was mentioned that there are two aspects of friendly URLs; so far, only one (search-engine friendly) has been discussed. The second aspect, known as human-friendly URLs, can sometimes impact performance.
A URL that is human friendly is easily remembered or able to be worked out by a human. For example, if you had a login to dotnetnuke.com and you wanted to visit your profile page without navigating to it, you might expect the URL to be http://www.dotnetnuke.com/profile/smcculloch.aspx.
That URL could easily be remembered, but would require additional processing on the request for two reasons:
The URL contains no TabID. That would have to be looked up.
The URL contains no UserID. A lookup on smcculloch is needed to find the corresponding UserID.
For these reasons, this approach was not chosen. Human-friendly URLs can be implemented by hardcoding the tabid and any other necessary parameters in the rewriter rules. You can see this in action on the Industrial Press web site at www.industrialpress.com/en — the left column contains links like http://www.industrialpress.com/en/AutoCAD/default.aspx. Listing 8-15 shows the rewriter rule for implementing this link.
Listing 8-15: Human Readable URL Example
<RewriterRule> <LookFor>[^?]*/AutoCad/(.*)</LookFor> <SendTo>~/Default.aspx?TabId=108</SendTo> </RewriterRule>
So far, how incoming requests are interpreted has been explained, but how outgoing links are transformed into the friendly URL scheme have not. A number of options have been explored on how to transform the outgoing links, but the best option was to implement a provider-based component that would transform a given link into the chosen scheme. Figure 8-2 shows the URL rewriter architecture.
Luckily, DotNetNuke already had used two shortcut methods for building links within the application (NavigateUrl and EditUrl). It was relatively simple to place a call to the provider from each of these methods, effectively upgrading the site to the new URL format instantly.
This approach also tightly coupled the HTTP module with the provider, which is why you can find them in the same assembly (DotNetNuke.HttpModules.UrlRewrite.dll).
You can see from the architecture that it is quite plausible for you to write your own URL rewriting scheme. If it was more important for your site to have human-friendly URLs, you could write a scheme by creating a new provider to format outgoing URLs, and a new HTTP module to interpret the incoming requests.
Writing a new provider involves supplying new implementations of the methods in the FriendlyUrlProvider base class. Listing 8-16 shows these methods.
Listing 8-16: Friendly URL Provider Methods
Public MustOverride Function FriendlyUrl(ByVal objtab as TabInfo, _ ByVal path As String) As String Public MustOverride Function FriendlyUrl(ByVal objtab as TabInfo, _ ByVal path As String, ByVal pageName As String) As String Public MustOverride Function FriendlyUrl(ByVal objtab as TabInfo, _ ByVal path As String, ByVal pageName As String, _ ByVal settings As PortalSettings) As String Public MustOverride Function FriendlyUrl(ByVal objtab as TabInfo, _ ByVal path As String, ByVal pageName As String, ByVal portalAlias As String) _ As String
As you can see, there are only four methods to implement so that you can write your URLs in your desired format. The most important part is to come up with a scheme and to find an efficient, reliable way of interpretation by your HTTP module. After you have written your provider, you can make an additional entry in the providers section of web.config as shown in Listing 8-17. Make sure to set the defaultProvider attribute.
Listing 8-17: Friendly URL Provider Configuration
<friendlyUrl defaultProvider="CustomFriendlyUrl"> <providers> <clear /> <add name="DNNFriendlyUrl" type="DotNetNuke.Services.Url.FriendlyUrl.DNNFriendlyUrlProvider, DotNetNuke.HttpModules.UrlRewrite" includePageName="true" regexMatch="[^a-zA-Z0-9 _-]"/> <add name="CustomFriendlyUrl" type="CompanyName.FriendlyUrlProvider, CompanyName.FriendlyUrlProvider" /> </providers> </friendlyUrl>
The exception management HTTP module subscribes to the error event raised by the HttpApplication object. Any time an error occurs within DotNetNuke, the error event is called. During the processing of this event, the last error to have occurred is captured and sent to the exception logging class, which calls the Logging Provider that handles the writing of that exception to a data store (the default is the DB Logging Provider).
Users Online was implemented during version 2 of DotNetNuke. It allows other modules to interrogate the applications' data store for information regarding who is online, expressed as registered users and anonymous users. Previously it had been a custom add-on and was session-based. Before the addition of the functionality to the core (like many add-ons incorporated into the core), research was undertaken to investigate the best way to handle not only registered users, but also anonymous users.
The module subscribes to the AuthorizeRequest event. This event is the first chance an HTTP module has to examine details about the user performing the request. The HTTP module examines the request, determines whether the user is anonymous or authenticated, and stores the request in cache. Anonymous users are also given a temporary cookie so they are not counted twice in the future. A scheduled job from the Scheduler executes every minute on a background thread, pulling the relevant details out of cache and updating them in the database. It also clears up any old records. The records are stored within two tables: AnonymousUsers and UsersOnline.
The HTTP module is a good module to disable (comment out of web.config) if you do not need this information within your portal. Alternatively, you can just disable it in Host Settings.
The DNNMembership HTTP module performs tasks around the security of a user. It stores role information about a user in an HTTP cookie so the same information does not have to be requested again and performs security checks for users switching portals.
There is no real need to extend this module because it is critical to DotNetNuke's operation.
The Personalization HTTP module is very similar to the Microsoft-provided Profile HTTP module, and in fact, was based on the same concept, just integrated much earlier. It loads a user's personalized information into a serialized XML object at the beginning of the request, and saves it at the end of the request.
If you are interested in storing personalized information about a user, see the personalization classes (described in Table 8-6) under /Components/Personalization/.
The primary API for using the personalization system. It encapsulates the few DotNetNuke business rules for using the personalization system.
Represents a low-level API that converts personalization database references into business objects.
The data transfer object that represents the data in a programming friendly object.