HttpHandler loading Web.UI.Page classes without .aspx files

Our work product is a bit unique in regard to how our pages are coded. Due to the high amount of inheritance in our pages we implemented all the pages in class files which inherit Web.UI.Page and just had .aspx page stubs which pointed the code behind to this class. This worked well until we hit the annoying issue of having to synchronize the aspx files between resources, and making sure they pointed to the right level down the inheritance chain. A messy solution was presented which was hard to maintain, so I did a bit of research and by using a HttpHandler I found we could solve our problem by keeping all pages inside the dll itself.

I have included the code for the concept below and a small sample of how its used.

First is just a class to hold the path, assembly and class definition.

PageHandlerFactory.vb

‘ To use the handler add the following section to the <system.web /> tag
‘<httpHandlers>
‘   <add verb=”*” path=”*.aspx”
‘       type=”PageHandlerExample.Handler.PageHandlerFactory, PageHandlerExample”/>
‘</httpHandlers>
‘ And for IIS 7 support
‘<system.webServer>
‘  <handlers>
‘    <add name=”ReconcilorHandler” preCondition=”integratedMode” verb=”*” path=”*.aspx”
‘         type=”PageHandlerExample.Handler.PageHandlerFactory, PageHandlerExample” />
‘  </handlers>
‘</system.webServer>

Namespace Handler
”’ <summary>
”’ Custom PageHandlerFactory class to parse page requests and return the appropriate classes rather than using the aspx files.
”’ </summary>
”’ <remarks></remarks>
Public Class PageHandlerFactory
Inherits System.Web.UI.PageHandlerFactory

#Region “Properties”
Private _pageList As New Dictionary(Of String, PageItem)
Private _pageListParsed As Boolean = False

Protected ReadOnly Property PageList() As Dictionary(Of String, PageItem)
Get
Return _pageList
End Get
End Property
#End Region

”’ <summary>
”’ Overwritten GetHandler to parse the custom page list.
”’ </summary>
”’ <remarks>Should not be overwritten at a lower level</remarks>
Public Overrides Function GetHandler(ByVal context As System.Web.HttpContext, ByVal requestType As String, ByVal virtualPath As String, ByVal path As String) As System.Web.IHttpHandler
Dim page As IHttpHandler
Dim relativePath As String = context.Request.AppRelativeCurrentExecutionFilePath.ToUpper()

SetupPageList()

‘ If the page has been declared, use the class provided.
If PageList.ContainsKey(relativePath) Then
Try
page = DirectCast(System.Activator.CreateInstance(PageList(relativePath).Assembly, _
PageList(relativePath).NamespaceClass).Unwrap(), System.Web.UI.Page)
Catch ex As System.TypeLoadException
context.Response.Write(“<b>Unable to load class.</b>”)
context.Response.Write(“<br>Assembly: <i>” & PageList(relativePath).Assembly & “</i>”)
context.Response.Write(“<b><br>Class: <i>” & PageList(relativePath).NamespaceClass & “</i></b>”)
context.Response.Write(“<br>Path: <i>” & context.Request.AppRelativeCurrentExecutionFilePath & “</i>”)
context.Response.Write(“<br>Check to ensure the class in the page mapping references the correct class.”)
page = Nothing
Catch ex As System.IO.FileNotFoundException
context.Response.Write(“<b>Unable to load assembly.</b>”)
context.Response.Write(“<b><br>Assembly: <i>” & PageList(relativePath).Assembly & “</i></b>”)
context.Response.Write(“<br>Class: <i>” & PageList(relativePath).NamespaceClass & “</i>”)
context.Response.Write(“<br>Path: <i>” & context.Request.AppRelativeCurrentExecutionFilePath & “</i>”)
context.Response.Write(“<br>Check to ensure the assembly in the page mapping references the correct assembly.”)
page = Nothing
End Try

Else
page = PageParser.GetCompiledPageInstance(virtualPath, _
context.Server.MapPath(virtualPath), context)
End If

Return page
End Function

”’ <summary>
”’ Adds the aspx path to list of class bound pages to load.
”’ </summary>
”’ <param name=”path”>Realtive path to the aspx file.</param>
”’ <param name=”assemblyName”>Assembly name</param>
”’ <param name=”fullClass”>Class name with full namespace</param>
”’ <remarks></remarks>
Protected Sub AddPage(ByVal path As String, ByVal assemblyName As String, ByVal fullClass As String)
PageList(path.ToUpper()) = New PageItem(assemblyName, fullClass)
End Sub

”’ <summary>
”’ Removes the aspx page from the list.
”’ </summary>
”’ <param name=”path”>Realtive path to the aspx file.</param>
”’ <remarks></remarks>
Protected Sub DeletePage(ByVal path As String)
If PageList.ContainsKey(path.ToUpper()) Then
PageList.Remove(path.ToUpper())
End If
End Sub

”’ <summary>
”’ Overwritable sub to populate the class bound pages.
”’ </summary>
”’ <remarks>Overwrite this sub to add additional pages to the page list.</remarks>
Protected Overridable Sub SetupPageList()

If Not _pageListParsed Then
AddPage(“~/Page1.aspx”, “PageHandlerExample”, “PageHandlerExample.Page1”)
AddPage(“~/FakePath/Default.aspx”, “PageHandlerExample”, “PageHandlerExample.FakePath._Default”)
AddPage(“~/AnotherPath/Page2.aspx”, “PageHandlerExample”, “PageHandlerExample.FakePath._Default”)
AddPage(“~/NoAssembly.aspx”, “I dont exist”, “PageHandlerExample.FakePath.Default”)
AddPage(“~/NoClass.aspx”, “PageHandlerExample”, “I dont exist”)

_pageListParsed = True
End If
End Sub
End Class
End Namespace

The sample web program is located here.

Edit: Over time we have converted this method to use System.Type for strongly typing our page classes. Using the following methods to invoke the system.type.

A)

System.Activator.CreateInstance(PageList(relativePath))
B)

Dim constructor As ConstructorInfo
Dim pageObect As Object
constructor = PageList(relativePath).GetConstructor(System.Type.EmptyTypes)
pageObect = constructor.Invoke(New Object() {})
page = DirectCast(pageObect, System.Web.UI.Page)