Browsing Posts in ASP.NET

Darth VaderThanks to everyone who came out to my session on Convention-over-Configuration on the Web at TechDays Calgary 2010. I enjoyed sharing my ideas about convention-over-configuration and how it can simplify software development. You expend some serious brain power over figuring out how to enable your application-specific conventions, but everything after that flows easily and without repetition. You end up doing more with less code. During the talk, I demonstrated how frameworks like Fluent NHibernate, AutoMapper, Castle Windsor, ASP.NET MVC, and jQuery support this style of development. (Links below.) I only scratched the surface though. With a bit of creative thinking, you can use these techniques in your own code to reduce duplication and increase flexibility.

You can grab a zip of the source code directly from here or view the source tree on GitHub here.

DevTeachDevTeach is heading back to Toronto in a few weeks (March 8-12, 2010)and you’ll get a bigger dose of awesome than ever before. We’ve got a fantastic line-up of top-notch, internationally renowned speakers. 6 tracks covering Agile, Web, Windows, Silverlight, Architecture, and SharePoint. A metric ton of sessions. (I’m both the Agile and Web Track Chairs and am really excited about the speakers and sessions for each.)

ee402630.VisualStudio_lgMicrosoft Canada is a platinum sponsor and every attendee receives a full copy of Visual Studio Professional with MSDN Premium. (N.B. Conference registration costs less than this subscription alone!)

imageAnd if you can’t get enough of that Sugar Crisp James Kovacs,  I’ll be there in full force with two sessions and a one-day post-con on agile development.

Convention-over-Configuration in an Agile World

As developers, we spend an inordinate amount of time writing “glue code”. We write code to transform database rows to domain objects… domain objects to view-models or DTOs… We write code to configure inversion of control containers and wire dependencies together. We write code to style our UIs and respond to UI events. Wouldn’t it be nice if this could happen automagically for us? This session will look at using convention-based approaches using Fluent NHibernate and Castle Windsor to reduce the amount of repetitive code and accelerate application development.

Convention-over-Configuration in a Web World

As developers, we spend an inordinate amount of time writing “glue code”. We write code to transform database rows to domain objects… domain objects to view-models or DTOs… We write code to configure inversion of control containers and wire dependencies together. We write code to style our UIs and respond to UI events. Wouldn’t it be nice if this could happen automagically for us? This session will look at using convention-based approaches using AutoMapper and jQuery to reduce the amount of repetitive code and accelerate application development.

Agile Development with IoC and ORM (Post-Con)

As developers we now have powerful tools in our toolbox, such inversion of control containers and object-relational mappers. But how can we use these tools to rapidly build maintainable and flexible applications? In this pre-con, we will look at advanced techniques such as convention-over-configuration in IoC containers and automapping ORMs to quickly build applications that can evolve over time. We will use test-driven development (TDD) to design and evolve a complete working application with supporting infrastructure during this one-day workshop.

Hope to see you in Toronto!

Sand Zen Garden A few months back, I announced that I was doing a series of articles for MSDN Magazine on improving a “classic” ASP.NET application with modern tooling and frameworks. As an application, I chose ScrewTurn Wiki 3.0 to use as my example throughout. The first article, Extreme ASP.NET Makeover – Getting Your House in Order, went live a few days ago. The article is purposefully a different format for MSDN Magazine than “traditional” articles in that it incorporates short screencasts where appropriate rather than just code snippets and pictures. (Code snippets and pictures are included too, though!) I tried to make the screencasts an integral part of the narrative where actually showing something was easier than text, pictures, or code. I would love to hear your feedback on the format and content.

Nitpickers Corner: In the series, I use MSBuild as the build tool. Yes, I wrote my own PowerShell-based build tool, psake. Yes, I use NAnt on many of my projects for clients. (They’re already using NAnt and PowerShell is a new skillset for them.) So why MSBuild for the series? Because it is installed by default with .NET 2.0 and above. Not my first choice, but a pragmatic choice for a series focused on improving what you have.

Parthenon Under ConstructionI don’t have to remind everyone that we’re in the middle of a world-wide economic depression downturn. When the economy is good, it is hard enough to convince your boss to re-build an application from scratch. When the economy is bad, it is bloody near impossible. In the coming months (and potentially years), I expect that as developers we’re going to be seeing more and more brownfield projects, rather than greenfield ones. We’re going to see more push for evolutionary development of applications rather than wholesale replacement. We will be called upon to improve existing codebases, implement new features, and take these projects in initially unforeseen directions. We will have to learn how to be Working Effectively with Legacy Code. (Took some effort to coerce the title of Michael Feathers’ excellent book into that last sentence.) A lot of companies have tremendous investment in existing “classic” ASP.NET websites, but there is a desire to evolve these sites rather than replace them, especially given these tough economic times. Howard Dierking, editor of MSDN Magazine, has asked me to write a 9-week series entitled From Web Dev to RIA Dev where we will explore refactoring an existing “classic” ASP.NET site. We want to improve an existing ASP.NET using new technologies, such as AJAX, jQuery, and ASP.NET MVC. We want to show that you can adopt better practices, such as continuous integration, web testing (e.g. WatiN, WatiR, Selenium), integration testing, separation of concerns, layering, and more.

So I have two questions for you, Dear Reader…

  1. Can you think of a representative “classic” ASP.NET website (or websites) for the project?
  2. What topics would you like to see covered?

I should clarify what I mean…

“Classic” ASP.NET Applications

I’m currently considering PetShop, IBuySpy, DasBlog, SubText, and ScrewTurn Wiki. I’m not looking for one riff with bad practices. Just an ASP.NET project in need of some TLC – one that doesn’t have a decent build script, isn’t under CI, a bit shy on the testing, little to no AJAX, etc. The code should be typical of what you would see in a typical ASP.NET application. (For that reason, I am probably going to discount IBuySpy as it is built using a funky webpart-like framework, which is not typical of most ASP.NET applications.) Some of the ASP.NET applications that I just mentioned don’t exactly qualify because they do have build scripts, tests, and other features that I would like to demonstrate. I will get permission from the project owner(s) before embarking on this quest and plan to contribute any code back to the project. Needless to say that the project must have source available to be considered for this article series. So please make some suggestions!

Topics

I have a lot of ideas of technologies and techniques to explore including proper XHTML/CSS layout, jQuery, QUnit, AJAX, HTTP Modules/Handlers, build scripts, continuous integration (CI), ASP.NET MVC, web testing (probably WatiN or Selenium), refactoring to separate domain logic from codebehind/sprocs, … I will cover one major topic per week over the 9-week series. So I’ve got lots of room for cool ideas. What would you like to see? What do you think is the biggest bang for your buck in terms of improving an existing ASP.NET application?

Depending on the topics covered (based on your feedback here), I might use one site for the entire series or different sites to cover each topic. It would add some continuity to the series to use a single site over the 9 weeks, but after a brief inspection of the codebases mentioned above, I am having my doubts about finding a single representative site. We’ll have to see. Please leave your suggestions in the comments below. Thanks in advance!

You upload an Office 2007 file (pptx, xlsx, docx, etc.) to your IIS6 web server and someone tries to download it, but receives:

HTTP Error 404 – File or directory not found.

Internet Information Services (IIS)

The file is on the server. The URL is correct. You can even download ppt, doc, xls, zip, and other files from the same location. What’s up???

Out of the box, IIS6 only accepts requests for known MIME types. Since Office 2007 was released after Windows Server 2003 and IIS6, IIS6 knows nothing about the new MIME types. So you need to manually add them:

  1. Open Computer Management. (Right-click My Computer… Manage…)
  2. Right-click Internet Information Services (IIS) Management… Properties…
  3. Click MIME Types…
  4. Click New… and add the following:
Extension MIME Type
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template
.potx application/vnd.openxmlformats-officedocument.presentationml.template
.ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.sldx application/vnd.openxmlformats-officedocument.presentationml.slide
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template
.xlam application/vnd.ms-excel.addin.macroEnabled.12
.xlsb application/vnd.ms-excel.sheet.binary.macroEnabled.12

N.B. These MIME types were added to IIS7 as noted in KB936496.

I was recently asked how to configure a site to redirect automatically from HTTP to HTTPS. By this I mean when the user types in http://server.example.com/app/page.aspx,* the browser will automatically redirect to https://server.example.com/app/page.aspx. You can do it through code, but with a little ingenuity, you can do it strictly through IIS configuration. Let’s walk through the setup…

Open IIS Manager and select properties for the website for which you want to require SSL. For TCP port, enter any unused port other than port 80 (the default HTTP port). For example, use 8888. For SSL port, enter the default SSL port, which is 443. Now go to the Directory Security tab… Secure communications… Edit… Set the “Require secure channel (SSL)” (required) and “Require 128-bit encryption” (optional, but recommended). Restart IIS. Browse to http://server.example.com:8888 now and you will get “The page must be viewed over a secure channel”. So far, so good.

Create a brand new IIS website by right-clicking… New… Web site… Click Next and give the website a name such as “Redirect to SSL”. Click Next… For TCP port, choose port 80, the default HTTP port. For path, point it to c:\inetpub\wwwroot. (It doesn’t really matter as we’ll be changing this in a minute.) Click Next… Give it Read permissions. Click Next… Finish… to create the website. Right-click, properties on the new website. Select the Home Directory tab. Change “The content for this resource should come from:” to “A redirection to a URL”. In the “Redirect to:” textbox, enter https://server.example.com. You can also optionally select “A permanent redirection for this resource”, which will cause bookmarks to update to the new URL. DO NOT, I repeat, DO NOT select “The exact URL entered above” or “A directory below URL entered”. Restart IIS. Now browse to http://server.example.com and you’ll be redirected to the SSL site. Note that the path portion of the URL is preserved and only the protocol and server are modified. So http://server.example.com/some/path/deep/within/my/app.aspx will redirect to https://server.example.com/some/path/deep/within/my/app.aspx just by applying the redirection steps noted above.

N.B. The redirect URL is sent back to the client. So if you type https://localhost as the redirect, the client browser will try to redirect to localhost on the client’s machine, which probably won’t exist. Same thing goes for NetBIOS names. (e.g. https://server rather than https://server.example.com)

* Little known fact. Example.com, example.net, and example.org are reserved top level DNS names as specified in RFC 2606, Section 3 and are intended for – surprise, surprise – examples. This allows authors to create bogus URLs in books, blog posts, and elsewhere that are guaranteed to go nowhere, which is generally the author’s intent when creating a bogus URL.

If you’ve developed on a web application of any degree of complexity, you’ve encountered times when you would like to politely take the site offline or make it unavailable to some users. ASP.NET 2.0 introduced a nifty new feature the App_Offline.htm file. If ASP.NET detects this file in your vroot, it drains the request queue, displays the contents of App_Offline.htm for any new request, and politely shuts down the app domain. You can then update the application, remove (or rename) the App_Offline.htm file and a new app domain will be spun up to start servicing requests again. Wonderful stuff, but what happens when your requirements are more complex? For instance, what if you need to take the application offline for a nightly database
backup? What if certain users can only access a data entry application
during certain hours of the day? What if you want to deploy updates to
an application and only allow a group of testers and/or admins in to
verify the updates? Enter the SiteAvailabilityModule. The SiteAvailabilityModule allows you to control whether or not your site is available to users based on group membership and time of day. For instance, normal users are only allowed access during normal business hours, but administrators can access the site at any time for maintenance and troubleshooting.

Now before we get too deeply engrossed in the code, let’s take a step back and think about how one might naively implement this feature. If you don’t know your ASP.NET pipeline, Grasshopper, you would likely code this logic into every Page_Load method on every ASPX page. If you were smart (but still didn’t know your ASP.NET pipeline), you would write a helper class so that the logic isn’t repeated in every Page_Load. (Neither solution is ideal because it’s too easy to forget to add the logic when implementing a new page.) If you were really smart, you would know a thing or two about the ASP.NET pipeline and realize that you could implement a HttpModule to accomplish this in one place.

Let’s start by taking a look at the ASP.NET pipeline:

The stages in the above diagram marked with an asterisk (*) have a corresponding “Post” event. For example, AuthenticateRequest and PostAuthenticateRequest. (This is a prettier version than the one I included in the third ImpostorHttpModule post.)

Now at which stage do we want to patch into the ASP.NET pipeline? As a general rule, you want to patch in as early as possible. For instance, if we were only concerned with the time of day, we would patch into the BeginRequest. The reason for patching in as early as possible is that the later you patch in, the more work ASP.NET has done. If you’re going to display a static page (or at least one with low processing overhead), try to do as little work as possible in making that determination.

Now let’s see why you might want to patch in at other places…

If you need to know who is accessing the site… PostAuthenticateRequest.

If you need to know whether that user is authorized to access a particular resource… PostAuthorizeRequest.

If you only want to execute if there is no cached content for that resource… PostResolveRequestCache.

If you need to know which handler will be executed… PostMapRequestHandler.

If you need something that you squirreled away in session state… PostAcquireRequestState.

You get the idea. Depending on what information you require, you will want to patch in at a particular stage. For our purposes, we need to know if the user is in the Administrators group. The user’s group memberships are determined in AuthorizeRequest. So we need to hook into PostAuthorizeRequest.

public void Init(HttpApplication context) {
    context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
}

Easy enough, but let’s look at the event handler as that is where the real action is happening…

void context_PostAuthorizeRequest(object sender, EventArgs e) {
    string ext = Path.GetExtension(HttpContext.Current.Request.Path).ToLowerInvariant();
    if(ext == “.aspx”) {
        TimeSpan now = DateTime.Now.TimeOfDay;
        if(now < Availability.Instance.StartTime.TimeOfDay
                || now > Availability.Instance.EndTime.TimeOfDay) {
            // If we’re outside normal hours, check to see if the user is in any
            // of the exempt roles. By default, only members of the SiteAvailabilityExempt
            // role are exempt from site availability constraints.
            if(!HttpContext.Current.User.IsInRole(Availability.Instance.ExemptRole)) {
                HttpContext.Current.RewritePath(Availability.Instance.SiteUnavailablePage);
            }                   
        }
    }
}

We first check to see if we’re dealing with a request for an ASPX page. Remember, we’re patched into the ASP.NET pipeline, which in ASP.NET 2.0 means that we’re being called when serving up both dynamic and static content. So if we’re dealing with a request for an image, cascading stylesheet (CSS), JavaScript file, or other non-ASPX content, we better not serve up the site unavailable page! We need to allow requests for resources to pass through unaltered. Hence the check for whether we’re dealing with an ASPX page or not.

Next part is simple. Are we outside of the normal hours of operation? The Availability class is derived from System.Configuration.ApplicationSettingsBase and leverages off the new System.Configuration features. We set our configuration properties in Web.config beneath the <configuration/> root element:

<configSections>
    <sectionGroup name=”applicationSettings” type=”System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″>
        <section name=”JamesKovacs.Web.HttpModules.Availability” type=”System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″/>
    </sectionGroup>
</configSections>

<applicationSettings>
    <JamesKovacs.Web.HttpModules.Availability>
        <setting name=”StartTime” serializeAs=”String”>
            <value>8:00 AM</value>
        </setting>
        <setting name=”EndTime” serializeAs=”String”>
            <value>4:00 PM</value>
        </setting>
    </JamesKovacs.Web.HttpModules.Availability>
</applicationSettings>

The class defines some sensible default values so that we can run without any configuration in Web.config.

If we’re outside of regular hours, we need to find out if the user is in the exempt role, which by default is a local group called SiteAvailabilityExempt. If we’re not a member of the exempt role, we re-write the URL to point to SiteUnavailablePage, which by default is ~/SiteUnavailable.aspx.

Let’s look at what is actually happening because it’s not a redirect or transfer. We are in PostAuthorizeRequest. We don’t decide which ASPX page we will be executing until the step marked “IHttpHandler Created”. So we haven’t created the HttpHandler, or System.Web.UI.Page-derived class, in this case. HttpContext.Current.RewritePath() actually changes the URL of the request while the request is “in-flight”. So no matter which page the user browses to, she will see the contents of SiteUnavailable.aspx. She won’t know it’s SiteUnavailable.aspx, but it is. The URL will show http://www.example.com/Default.aspx or whatever page was originally requested. Think about how powerful this technique is. If we redirected to SiteUnavailable.aspx, the user could refresh her browser all day and she would always see SiteUnavailable.aspx even if the clock rolled over into business hours. To the server, you’re requesting SiteUnavailable.aspx and that’s what I’m going to serve up. With URL rewriting, we keep the original URL, but serve up alternate content. If the user refreshes and we’ve rolled into business hours, the SiteAvailabilityModule lets the request through unaltered and the user sees the correct page. Cool or what?

You can download the code here. I’ve included all the HttpModules from the Calgary Code Camp in addition to an updated version of the SiteAvailabilityModule. As always, feedback is appreciated. Enjoy!

[EDIT -- Corrected text to match the code. Thanks, Lance, for catching the error.]

For our first event, the Calgary Code Camp was a huge success, if I do say so myself. We had over 80 developers attend and both tracks were constantly buzzing with great discussions. Thanks to everyone who presented for generously sharing their time and knowledge! Thanks also to everyone who attended and made the day a success. We’ll be posting slidedecks and demos to the Calgary Code Camp website in the coming week. In the meantime you can find mine here. You can find some extra goodies in the zip file that I didn’t have time to demo, such as the DatabaseImageHandler and the SiteAvailabilityModule, which I’ll be blogging about in the next while.

In the previous two parts (Part 1 and Part 2), I introduced the ImpostorHttpModule as a way to test intranet applications that use role-based security without having to modify your group memberships. (I’ll assume that you know what I’m talking about. If not, go back and re-read the first two parts.) In the final part, let’s look at what exactly is going on behind the scenes with ImpostorHttpModule…

The ImpostorHttpModule requires surprisingly little code to work its magic. Let’s think about exactly what we want to do. We want to intercept every HTTP request and substitute the list of roles defined for the incoming user in the ~/App_Data/Impostors.xml file instead of the user’s actual roles. (In an intranet scenario, a user’s roles are often just the local and domain groups to which the user belongs.) To do this, we need to implement a HttpModule. We’ll start with the simplest HttpModule, which we’ll call NopHttpModule for “No operation”.

using System.Web;

namespace JamesKovacs.Web.HttpModules {
    public class NopHttpModule : IHttpModule {
        public void Init(HttpApplication context) {
        }

        public void Dispose() {
        }
    }
}

To be a HttpModule, we simply need to implement IHttpModule and provide implementations for the two methods, Init() and Dispose(). We now have to register ourselves with the ASP.NET pipeline. We do this using the <httpModules> section of Web.config.

<?xml version=”1.0″?>
<configuration>
    <system.web>
        <httpModules>
            <add name=”NopHttpModule” type=”JamesKovacs.Web.HttpModules.NopHttpModule, JamesKovacs.Web.HttpModules”/>
        </httpModules>
    </system.web>
</configuration>

That’s it. Not terribly interesting because it does absolutely nothing. So let’s move on and implement the HelloWorldHttpModule, which simply returns “Hello, world!” no matter what you browse to, whether it exists or not!

using System;
using System.Web;

namespace JamesKovacs.Web.HttpModules {
    public class HelloWorldHttpModule : IHttpModule {
        public void Init(HttpApplication context) {
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }

        void context_BeginRequest(object sender, EventArgs e) {
            HttpContext.Current.Response.Write(“<html><body><h1>Hello, World!</h1></body></html>”);
            HttpContext.Current.Response.End();
        }

        public void Dispose() {
        }
    }
}

Try browsing to /Default.aspx, /Reports/Default.aspx, /ThisDoesNotExist.aspx, or even /ThisDoesNotExistEither.jpg. They all return “Hello, World!” (N.B. ASP.NET 1.X will return a 404 for the JPEG. ASP.NET 2.0 will return “Hello, World!” In 1.X, static
files were served up directly by IIS without ASP.NET getting involved. Although this gives excellent
performance for images, CSS, JavaScript files, etc., it also meant that
those files were not protected by ASP.NET security. With ASP.NET 2.0, all unknown files types are handled by
the System.Web.DefaultHttpHandler, which allows non-ASP.NET resources to be protected by ASP.NET security as well. See here for more information.)

Now back to our regularly scheduled explanation… In our Init() method, we tell the HttpApplication which events we would like to be informed of. In this case, we grab the BeginRequest event, which is the first event of the ASP.NET pipeline. It occurs even before we determine if the URL is valid, hence our ability to serve up “missing content”.

ASP.NET provides many hooks into its processing pipeline. Here is an excerpt from MSDN2 on the sequence of events that HttpApplication fires during processing:

  1. BeginRequest
  2. AuthenticateRequest
  3. PostAuthenticateRequest
  4. AuthorizeRequest
  5. PostAuthorizeRequest
  6. ResolveRequestCache
  7. PostResolveRequestCache

    After the PostResolveRequestCache event and before the PostMapRequestHandler event, an IHttpHandler (a page or other handler corresponding to the request URL) is created.

  8. PostMapRequestHandler
  9. AcquireRequestState
  10. PostAcquireRequestState
  11. PreRequestHandlerExecute

    The IHttpHandler is executed.

  12. PostRequestHandlerExecute
  13. ReleaseRequestState
  14. PostReleaseRequestState

    After the PostReleaseRequestState event, response filters, if any, filter the output.

  15. UpdateRequestCache
  16. PostUpdateRequestCache
  17. EndRequest

The pipeline in ASP.NET 1.X had many, but not all, of these events. ASP.NET 2.0 definitely gives you much more flexibility in plugging into the execution pipeline. I’ll leave it as an exercise to the reader to investigate why you might want to capture each of the events.

Armed with this information, you can probably figure out which event we want to hook in the ImpostorHttpModule. Let’s walk through the thought process anyway… We are trying to substitute the actual user’s roles/groups for one that we’ve defined in the ~/App_Data/Impostors.xml file. To do this we need to know the user. So we need to execute after the user has been authenticated. We need to execute (and substitute the groups/roles) before any authorization decisions are made otherwise you might get inconsistent behaviour. For instance, authorization may take place against your real groups/roles and succeed, but then a PrincipalPermission demand for the same group/roles might fail because the new groups/roles have been substituted. So which event fits the bill? PostAuthenticateRequest is the one we’re after. In this event, we know the user, which was determined in AuthenticateRequest, but authorization has not been performed yet as it occurs in AuthorizeRequest.

public void Init(HttpApplication context) {
    context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
}

We know which event we want to hook. Now what to do once we hook it. In .NET, we have Identities and Principals. An Identity object specifies who has been authenticated, but does not indicate membership in groups/roles. A Principal object encapsulates the groups/roles and the identity. So what we want to do is construct a new Principal based on the authenticated Identity and populate with the groups/roles that we read in from ~/App_Data/Impostors.xml. As it so happens, the built-in GenericPrinicpal fits the bill quite nicely. It takes a IIdentity object and a list of roles (in the form of an array of strings). N.B. It doesn’t matter if the Identity is a WindowsIdentity, a FormsIdentity, a GenericIdentity, or any other. All that matters is that the Identity implements the IIdentity interface. This makes the group/role substitution code work equally well regardless of authentication technology.

IIdentity identity = HttpContext.Current.User.Identity;
string[] roles = lookUpRoleListFromXmlFile(identity);    // pseudo-code
IPrincipal userWithRoles = new GenericPrincipal(identity, roles);

Armed with userWithRoles, we just need to patch it into the appropriate places:

HttpContext.Current.User = userWithRoles;
Thread.CurrentPrincipal = userWithRoles;

We have discarded the original principal (but kept the original identity) and patched in our custom one. That’s about it. Any authorization requests are evaluated against the new GenericPrincipal and hence the group/role list that we substituted.

An additional feature I would like to point out is caching of the users/roles as you probably don’t want to parse a XML file on every request. The users/roles list will auto-refresh if the underlying ~/App_Data/Impostors.xml file changes. Let’s see how this works. We store a Dictionary<string, string[]> in the ASP.NET Cache, which contains users versus roles as parsed from the ~/App_Data/Impostors.xml file. If it doesn’t exist in the Cache, we parse the XML file and insert it into the Cache along with a CacheDependency like this:

HttpContext.Current.Cache.Insert(“ImpostorCache”, impostors, new CacheDependency(pathToImpostorsFile));

When the underlying file changes, the entry is flushed from the cache. The next time the code runs, the cache is re-populated with the contents of the updated ~/App_Data/Impostors.xml.

One last point… The ImpostorHttpModule is meant for development/testing purposes, which means that I haven’t optimized it for performance, but for ease of implementation and comprehension.

So there you have it – the ImpostorHttpModule. Hopefully you have a better appreciation for the power and extensibility built into ASP.NET as well as some cool ideas of what else you can implement using HttpModules. Full source code can be found here.

In our last cliff-hanger episode, I introduced the ImpostorHttpModule. I’m going to show how you can use it to implement and test a sitemap and navigation menu in ASP.NET. We’ll use the new ASP.NET 2.0 Master Pages feature because it’s the easiest way to ensure that the same menu ends up on every page. We’ll start from the previous solution with the ImpostorHttpModule registered in the Web.config. I’ve created three pages, ~/Default.aspx, ~/Reports/Default.aspx, and ~/Admin/Default.aspx. The Web.config files are set up as follows:

Path Allowed Roles
~/Default.aspx User, Manager, Administrator
~/Reports/Defaults.aspx Manager, Administrator
~/Admin/Default.aspx Administrator

We’ll be using the security trimming feature of sitemaps, which removes nodes from the sitemap that are not accessible by the current user. Note that this is simply a UI nicety and not actual security – without the appropriate Web.config files and <authorization> sections, users could still access those areas by navigating directly to them. So let’s start by implementing the menu and then we’ll enable security trimming. We’ll see how ImpostorHttpModule can help us in testing these features.

Our first step is to add a sitemap, which we’ll leave with the default name of Web.sitemap. We’ll add three nodes – Home, Reports and Administration. One of the odd things about the sitemap XML schema is that the child of <siteMap> must be a single <siteMapNode>. So if you want to have multiple nodes, you need to create an empty <siteMapNode> with further child nodes like this:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0” >
    <siteMapNode>
        <siteMapNode url=”~/Default.aspx” title=”Home”  description=”Return to the Home page” />
        <siteMapNode url=”~/Reports/Default.aspx” title=”Reports” description=”Go to reports” />
        <siteMapNode url=”~/Admin/Default.aspx” title=”Administration”  description=”Go to site administration” />
    </siteMapNode>
</siteMap>

One quick note is the use of ~/ which allows us to create links that are relative to the application’s virtual directory. This is a great ASP.NET feature, which works in most places links are found in server-side controls, as it allows you to deploy the site at http://servername/ or http://servername/someVdir/ or anywhere else.

Now that we have our sitemap set up, we can create a Menu that is driven by the sitemap. We’ll create a master page called Main.master and add the Menu to it in design mode. We’ll create a new SiteMapDataSource and point the Menu control at it. Since we have an empty starting node, we’ll set SiteMapDataSource.ShowStartingNode = false. (I also changed the menu to use the Classic style and horizontal orientation.) We should now see three nodes: Home, Reports, and Administration in the Menu. If we try to browse the site, we’ll get a 401 – Access Denied because our user account is not in any of the appropriate roles. At least the Web.config files are doing their jobs. Let’s see how to use the ImpostorHttpModule to give ourselves access to these directories without having to create a local or domain group or add our user account to it. (In a deployment situation, you would likely have a domain group that a domain admin would add users to. We’re trying to avoid all the overhead of calling up your friendly neighbourhood domain admin every time you want to test a different security context when accessing your app. Believe me – your domain admin will thank you.)

So let’s add our user, DOMAIN\Foo, to the User, Manager, and Administrator role in the ~/App_Data/Impostors.xml file. (N.B. If you’re logged on using a local account, you’ll need to specify MACHINE\Bar. If you logged in using the alternate user name syntax, foo@example.com, you’ll need to use this in the name.)

<?xml version=”1.0″ encoding=”utf-8″ ?>
<impostors>
    <impostor name=”DOMAIN\Foo” roles=”User, Manager, Administrator”/>
</impostors>

Clicking on the Menu links should allow you to see the Reports and Administration sections. All is good in the world. Now let’s implement security trimming! Our next stop is Web.config where we’ll have to add a new sitemap provider as the default one declared in Machine.config does not have security trimming enabled. The key attribute here is securityTrimmingEnabled=”true”.

<siteMap defaultProvider=”XmlSiteMapProvider” enabled=”true”>
   <providers>
      <add name=”XmlSiteMapProvider” description=”Default SiteMap provider.” type=”System.Web.XmlSiteMapProvider” siteMapFile=”Web.sitemap” securityTrimmingEnabled=”true”/>
   </providers>
</siteMap>

With this change, the menu disappears entirely! We haven’t defined any roles that can see the nodes. So they are all trimmed off. Let’s update the Web.sitemap to add the roles:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0” >
    <siteMapNode roles=”User, Manager, Administrator”>
        <siteMapNode url=”~/Default.aspx” title=”Home”  description=”Return to the Home page” roles=”User, Manager, Administrator” />
        <siteMapNode url=”~/Reports/Default.aspx” title=”Reports” description=”Go to reports” roles=”Manager, Administrator” />
        <siteMapNode url=”~/Admin/Default.aspx” title=”Administration”  description=”Go to site administration” roles=”Administrator” />
    </siteMapNode>
</siteMap>

A quick note is that you must specify all the roles on the empty <siteMapNode>. If you do not, it gets trimmed and none of the child nodes get displayed either. (Took me awhile to realize this the first time I used sitemaps and security trimming.)

Now that we have everything in order, try removing your user account from the Administrator role in ~/App_Data/Impostors.xml and browse to ~/Default.aspx. The Administration link disappears. You can now happily add and remove roles from your user account to test what different kinds of users would see when they browse the site.

In summary, the ImpostorHttpModule allows us to easily test different security configurations for our site without having to change your local or domain group memberships. Since we’re replacing the list of roles that the incoming user is a member of, this technique not only works for testing sitemaps and website security using <authorization>, but it also works for declarative and imperative security demands in code. Full source code is available here. In our next episode, we’ll look at how the ImpostorHttpModule works under the covers.