I ran into an interesting post on the ASP.NET forums today where someone is trying to dynamically invoke a page class inside of an HttpHandler. Basically the idea is that you have a class for the page that exist and you're dynamically invoking the class rather than letting the ASP.NET BuildManager deal with loading the page from disk and doing its thing.

This can be pretty useful in a few scenarios - for example you can potentially use library projects to pre-compile ASP.NET pages completely and then execute them from another assembly with everything self contained in the external assembly.

Anyway the basic loading process is pretty simple - you  create a handler and once you're sure you have a compiled class you just instantiate the class and execute it. Remember that the ASP.NET Page class is just an HttpHandler so you can do something like this:

    public class TestHandler : System.Web.IHttpHandler
    {

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(System.Web.HttpContext context)
        {

            //Page page = BuildManager.CreateInstanceFromVirtualPath("/weblog/schedulerTest.aspx", typeof(SchedulerTest) ) as Page; 
            //BuildManager.GetVirtualPathDependencies("/weblog/testpage.axd");

            Page page = new SchedulerTest();  // An ASPX Page class (note: CodeBehind)
            page.AppRelativeVirtualPath = context.Request.AppRelativeCurrentExecutionFilePath; 
            page.ProcessRequest(context);
            
        }
    }

There are a couple of big gotchas with this though.

First notice the AppRelativePath assignment  which is crucial. Normally the BuildManager and the Page factory handle instantiation of the page from disk. The BuildManager handles a number of important tasks like making sure the page is fully compiled and deciding which class actually gets instantiated and executed. If you don't set the AppRelativeVirtualPath any ResolveUrl() calls will fail to resolve.

The second issue is that code like the above depends on pages being pre-compiled and that you instantiate the right class. For example, if you're using Stock projects the name of page class by default is dynamically generated (you can override it by specifying the ClassName attribute).  In Web Application Projects this is even more complicated because the ASPX page is completely dynamically generated at runtime so you can't actually reference the ASPX page directly (the IDE doesn't precompile it for you like stock projects do). So the code above referencing SchedulerTest is actually referencing the CodeBehind class (in WAP) NOT the actual ASPX page which means you don't get markup rendering. This means executing this page only executes the CodeBehind, and not any of the page markup and controls.

This may still be useful if you are using Page subclasses for dynamically generating content. For example, if you use a custom framework to dynamically add controls to a page in CodeBehind or a custom parser etc. etc. this approach works just fine.

But if you need the ASPX markup to render as well things are much more complicated because ASP.NET generates the ASPX via precompilation - ie. dynamically. So that makes it much harder to reference the generated type dynamically.

The bottom line is this: Trying to execute a page dynamically entirely is best done by using the virtual path to launch it. Rather than instantiating the class use a the BuildManager.CreateInstance() to point at the virtual path or rewrite the path to the ASPX file.

Packaging: Use Web Deployment Projects

If you really need to package pages into a fully self contained assembly and execute them from there, you have to use Web Deployment projects to generate the  standalone compiled assembly. Make sure that you use ClassName="" on each page to give the name a concrete name you can reference. Once you have the compiled with WDP you can add areference the generated assembly to your project and then reference the class from there. That will let you instantiate the ASPX class which inherits from the CodeBehind/CodeBeside. The AppRelativeVirtualPath still needs to be set in this scenario as well.

This is a very special case scenario, but I've had a few occasions when this has come in handy. I have one application that has an administration ASPX interface which is basically shipped as a self contained assembly. It works without having to copy files to disk first. It's a bit of effort to make this work, but if you need it it's a life saver.

Note though that I found it's still difficult to get everything wrapped into a single assembly. WDP still generates a few files especially if you have localized resources associated with the page. So if your goal is packaging of components I've found that it may not always be worth the effort and instead just ship a directory that can be installed with a given site. <shrug>