I’m constantly looking for ways to make deployment of ASP.NET applications easier. One of the things that makes it difficult is the ASPX markup files that have to be deployed with the app. Wouldn’t it be nice not to have those? Wouldn’t it be nice to be able to drop the application .dll into the bin folder, pop in your web.config file, and let ‘er rip?

Since I work primarily in ASP.NET 1.1 right now, the solution I came up with is for ASP.NET 1.1. There is a different/better way to do this in ASP.NET 2.0 and I will be writing/posting that as I get time.

UPDATE: The ASP.NET 2.0 version is here - based on System.Web.Hosting.VirtualPathProvider.

The idea: Embed your ASPX files in your ASP.NET application assembly. To deploy the app, drop your .dll in the bin folder and set up your web.config file. At runtime, embedded ASPX pages get extracted to a temporary location and get served up from there. When the app shuts down, the temporary fileset gets cleaned up. Easy deployment, easy upgrades.

The solution: An HttpModule that does exactly that. You set your application web.config file to use the EmbeddedPageHandlerFactory and in your ASP.NET project set your ASPX files from “Content” to “Embedded Resource.” At application startup, the module will go through the assemblies you registered as containing pages and extracts the ASPX to a temporary location. A replacement for the standard PageHandlerFactory redirects requests to the temporary location so pages get served up just like usual. When the application shuts down, the temporary files get cleaned up.

Here’s a snippet of the relevant bits in web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
      name="embeddedPageAssemblies"
      type="System.Configuration.DictionarySectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null"
      />
  </configSections>
  <embeddedPageAssemblies>
    <add
      key="Paraesthesia.EmbeddedPageHandlerFactory.Demo"
      value="Paraesthesia.EmbeddedPageHandlerFactory.Demo" />
  </embeddedPageAssemblies>
  <system.web>
    <httpModules>
      <add
        name="EmbeddedPageHandlerFactory"
        type="Paraesthesia.Web.UI.EmbeddedPageHandlerFactory, Paraesthesia.EmbeddedPageHandlerFactory" />
    </httpModules>
    <httpHandlers>
      <add
        verb="*"
        path="*.aspx"
        type="Paraesthesia.Web.UI.EmbeddedPageHandlerFactory, Paraesthesia.EmbeddedPageHandlerFactory" />
    </httpHandlers>
  </system.web>
  </appSettings>
    <add
      key="Paraesthesia.Web.UI.EmbeddedPageHandlerFactory.AllowFileSystemPages"
      value="false" />
  <appSettings>
</configuration>

Key points:

  • The embeddedPageAssemblies section is how you tell the module which assemblies have ASPX in them. The “key” is the name of the assembly; the “value” is the root namespace in the assembly.
  • The httpModules section is where you register the module portion of the solution. This is how the pages get extracted at app startup.
  • The httpHandlers section is where you tell all requests for ASPX files to go through the EmbeddedPageHandlerFactory and get served up from the temporary location.
  • The appSettings has an optional key for AllowFileSystemPages. Setting this to true will allow you to “override” ASPX by putting a file of the same path/name in the ASP.NET application file system. If the file exists in the app, it will be served from there. If not, it falls back to the temporary filesystem.

In your web project, change your ASPX pages in your application to be “Embedded Resource” rather than “Content.” Embedded resources get named based on namespace and file path. For example, if you have an assembly called “MyApp.Web” where the default namespace is “MyApp.Web” and you have a file at “~/Admin/Default.aspx” then when it gets embedded as a resource, it’ll be called “MyApp.Web.Admin.Default.aspx” - notice the namespace, then the path, then the file, all delimited by periods.

What the module does is look for resources that end with “.aspx” and, if it finds them, removes the namespace from the front (as specified in web.config) and substitutes out / for . to convert back to a path. In this example, “MyApp.Web.Admin.Default.aspx” would become “Admin.Default.aspx” and then “Admin/Default.aspx.”

Once the mapping is done, a temporary location is generated and the page is extracted to that location with the full relative path intact. As requests come in, the EmbeddedPageHandlerFactory will look at the request, map it into the temporary location, and serve the temporary file.

Using the appSettings key, you can specify that you’d like to allow pages in the actual ASP.NET filesystem to override the extracted pages. In this case, the actual ASP.NET filesystem would be searched for “Admin/Default.aspx” and if it is found, serve it from there just like a standard ASP.NET application. If it isn’t found, then it’ll fall back to look in the temporary location.

Caveats:

  • This won’t work for sites that rely on file system security. I primarily work with forms authentication, so this isn’t a problem for me. It may be for you.
  • This is definitely not the way you’d want to do this for .NET 2.0. This is really meant for .NET 1.1.
  • It only works for ASPX. The ASCX load process is different and doesn’t pass through a handler factory like pages. The .NET 2.0 mechanism should work for any files, not just ASPX.
  • This is going to be a one-shot deal. I’m not going to be posting updates or actively supporting it or anything. Take it at your own risk, your mileage may vary, etc.
  • The source bundle includes the source for the EmbeddedPageHandlerFactory as well as a demo web application and unit tests.
  • I wrote the unit tests using TypeMock so you’ll need to go get that if you want to build/run the tests.
  • It’s totally free and open-source. Do whatcha like.

Download EmbeddedPageHandlerFactory Compiled Assembly

Download EmbeddedPageHandlerFactory Source

This Memorial Day weekend was very relaxing and one of the nicest weekends I’ve had in a while.

Saturday Jenn and I ran some errands, mostly on a mission to find the Guitar Hero dual gig bag to store our guitars in. We didn’t find it, but had a nice day out and ended up at my parents’ place, heading out to see Shrek 3 at the end of the day.

Sunday was chore day, cleaning things up around the house. In the evening we headed over to Jason and Tracy’s place where we played four-person Mario Kart until all hours.

Monday was my easy day, though Jenn got a lot of great stuff done that had been needing to get done around the house. I caught up on my comic reading, watched some shows, and generally took it easy. Long about 1:00p I finally hit that point when I had totally decompressed and was stress-free (yeah, it takes about three days to get there) and was suddenly dead tired. The sun was out, the wind was gently blowing, and I could barely keep my eyes open.

That’s about the time Jenn wanted to go to the store and ask for my input on which plants would be best to put in containers. Any other day I might have had an opinion, but my brain had just turned off and I could not engage it.

We got home, I had myself a little something caffeinated to wake me up, and I played some Settlers of Catan and Marvel: Ultimate Alliance, finally breaking through the 10,000 Gamerpoint barrier. I admit I stood up and did the happy dance with that.

My brain is once again engaged this morning, I’m sipping some crappy generic French Roast coffee, and I’m ready to face the week, slightly less stressed.

net comments edit

I’m working on a DataGridColumn that can automatically sum up the data in the column and display a total of the data in the footer. This sort of thing comes in handy when displaying tables with account activity or balance data in them and allows you to just put the special column type right into the DataGrid without having to modify your page behavior to do the summation.

So I created my column class have it set up so when the column is databound, each cell in the column will contribute its bound value toward a total. That’s where I ran into a snag - since I don’t know what object type is being bound to the column, my “total” variable is just an object… and base objects don’t support addition.

How do you tell if an object type supports addition?

That’s actually something I can’t answer. My original thought was that when you override the addition operator, the compiler generates a static “Add” method for your data type. You can see this in Reflector. Here’s the System.Decimal class:

System.Decimal supports the Add
method.

Notice there’s a static method called “Add” that takes in two System.Decimal objects and returns a System.Decimal object. That’s what you see when you override the addition operation.

My idea was that I would get the first object being bound to the column, get its type, and see if it had a static method called “Add” that takes in two objects of that type and returns an object of that type. If that method exists, I’ll hold a reference to it and invoke it for every subsequent data item being added.

The problem is, this static “Add” method doesn’t show up for other base types. For example, there’s no “Add” method for System.Int32:

System.Int32 has no Add
method.

So that idea won’t work.

I talked to Hanselman and Sells and they both thought the “Add” method would be there. No such luck.

Right now I’m working around it by special casing all of the types in mscorlib that I know we use that can be added together… but is there some better way I can, through Reflection, determine if two objects can be added (and then actually perform that addition)?

It’s been a heck of a busy week, so I haven’t had a chance to blog about the wedding I went to this past weekend.

Saturday Jenn and I packed up and headed to Puyallup, WA, to see my cousin Haley get married. The ceremony was at High Cedars Golf Club. It was a nice ceremony, though the minister seemed a little frazzled like he had never performed a marriage ceremony before. Regardless, congratulations to Haley and Adam Horton.

Immediately following the wedding there was a minor incident involving Adam running over Haley’s dress with a golf cart and ripping the back on it, but a few safety pins later and Adam’s first married-life “I’m sorry,” everything was back in order.

The real excitement came when we checked into our hotel up there. We stayed at the Crossland Economy Studios up there because we knew the wedding would go reasonably late and we didn’t want to drive the three hours back that night.

When we made the reservation, they offered us our choice of a room with two double beds or a room with two queen beds. A double’s a little small, so I picked the room with two queens. When we got there, they told me the hotel was packed, so rather than getting one room with two beds, they had me in two rooms, each with one bed, where each room would only cost me half price. Not needing two beds, I told the clerk I only needed one of the rooms. The cool part - I got to keep the half price. So I stayed there for $35, including tax.

The not so cool part - the bed was a freaking double. Oh, and the mattress was firm, sort of in the sense that a sheet of plywood laying on cement is firm. But, hey, $35, right?

We had a decent drive home the next day (though the trip south on I-5 is one of the most boring drives ever) and spent most of the day Sunday lounging on the couch, exhausted.

GeekSpeak comments edit

Eli Lopian from TypeMock has blogged the case study we did with TypeMock showing how much more productive we are in testing using TypeMock.

We’ve been vastly more productive using TypeMock than we were before we adopted it. If you get a chance, check out the Case Study PDF. It pretty much says it all. And if you haven’t tried TypeMock yet, what are you waiting for? Go get it. You’ll like it, I promise.

(In the interest of shameless-self-promotion full disclosure, I’m fairly prominently quoted in the case study. I can’t lie; it makes me smile. Can’t help but love TypeMock.)