Jamie Treworgy Jamie Treworgy - 1 month ago 15
ASP.NET (C#) Question

Call the default asp.net HttpHandler from a custom handler

I'm adding ASP.NET routing to an older webforms app. I'm using a custom

HttpHandler
to process everything. In some situations I would like to map a particular path back to an
aspx
file, so I need to just pass control back to the default HttpHandler for asp.net.

The closest I've gotten is this

public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on

var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);

MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);


// write headers, etc. & send stream to Response
}


It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."

From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler

There is also this thing
System.Web.UI.PageHandlerFactory
which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.

This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.

Any ideas on how I can do this? Is it possible?

Answer

Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.

http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/

public void ProcessRequest(HttpContext context) {
    // the internal constructor doesn't do anything but prevent you from instantiating
    // the factory, so we can skip it.
    PageHandlerFactory factory =
        (PageHandlerFactory)System.Runtime.Serialization.FormatterServices
        .GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));

     string newTarget  = "default.aspx"; 
     string newQueryString = // whatever you want
     string oldQueryString = context.Request.QueryString.ToString();
     string queryString = newQueryString + oldQueryString!="" ? 
         "&" + newQueryString :
         "";

     // the 3rd parameter must be just the file name.
     // the 4th parameter should be the physical path to the file, though it also
     //   works fine if you pass an empty string - perhaps that's only to override
     //   the usual presentation based on the path?

     var handler = factory.GetHandler(context, "GET",newTarget,
         context.Request.MapPath(context,newTarget));

     // Update the context object as it should appear to your page/app, and
     // assign your new handler.

     context.RewritePath(newTarget  , "", queryString);
     context.Handler = handler;

     // .. and done

     handler.ProcessRequest(context);
}

... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.

I expect this will only work in IIS7.

Comments