Post-processing the Output of ASP.NET Pages

Leveraging .NET streams


November 10, 2008
URL:http://www.drdobbs.com/web-development/post-processing-the-output-of-aspnet-pag/212001499


The ASP.NET pipeline has been devised at the time the very first version of ASP.NET came out back in 2000. Since then, very few changes occurred to the pipeline and none of them structural. In the end, the ASP.NET pipeline is still articulated on a list of modules that process any incoming request up to generating some output for the client browser.

In the course of four different releases of ASP.NET -- the latest being ASP.NET 3.5 -- the only type of change that occurred to the pipeline is the addition of new modules to the standard list of modules that are attached to process each request.

Every ASP.NET application is free of attaching its own modules and removing any of the standard ones. You should note, though, that attaching new modules is less dangerous than removing existing one. As an example, consider that if you detach the authentication HTTP module no authentication will be possible for any requests directed at the application.

HTTP modules are extremely powerful tools and a significant number of the features we find today in the ASP.NET MVC Framework or ASP.NET AJAX wouldn't be possible without HTTP modules.

An HTTP module is a special class that implements a fairly simple interface -- IHttpModule. The interface counts only two methods -- Init and Dispose.


void Init(HttpApplication app);
void Dispose();

These methods are invoked only once in the application's lifetime -- upon application loading and unloading. Essentially, the methods in the interface serve the purpose of attaching and detaching the module to and from the pipeline.

The pipeline fires a number of events to registered modules during the processing of the request. By handling one or more of such events each module can implement its own logic and do its own things.

For example, an HTTP module can post-process the HTML, or whatever output, produced by a page request. The page processing is just one step in the request lifecycle. An HTTP module that wants to validate or modify the output of a request as generated by the selected HTTP handler will register its own handler for the EndRequest event, as shown below:


public void Init(HttpApplication app)
{
    app.EndRequest += new EventHandler(OnEndRequest);
}
public void OnEndRequest(object sender, EventArgs e)
{
    HttpApplication app = (HttpApplication) sender;
    HttpContext ctx = app.Context;
    DoCustomProcessing(ctx.Response);
}

The EndRequest event is the closing event in the pipeline and fires when all has been done and just before the output is sent back to the browser. The module can still access the output stream and make updates. The output stream is accessible through the Response object. It should be noted, though, that the OutputStream property exported by the Response object is a write-only stream meaning that you can write on it, but not read from it. As it turns out, this approach for post-processing is only partially effective. It doesn't work, for example, if you want to read, verify, and then enter some updates. What would be a better approach?

You must register an handler for another event -- PostRequestHandlerExecute. The event fires when the page handler has completed and the output for the browser has been generated.


public void Init(HttpApplication app)
{
   app.PostRequestHandlerExecute += 
            new EventHandler(OnPostRequestHandlerExecute);
}

A few other things will happen from now to the EndRequest event. One of the intermediate steps, however, has just to do with post-processing. The Response object features a property named Filter which evaluates to a Stream object. If you assign a custom stream object to the property, then the generated output will be written through your custom stream, thus giving you a chance to parse and update. You can register your output filter at any time in the page or request lifecycle:


Response.Filter = new YourStream(Response.Filter);

Here's an excerpt of the code required to parse:


public class YourStream : MemoryStream
{
    private Stream _outputStream;

    public YourStream(Stream outputStream)
    {
        _outputStream = outputStream;
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        // buffer contains the output to parse
        :
        // Now buffer contains your modified content, if any
        Write(buffer, offset, count); 
    }
}

The trick leverages the ability of .NET streams to be piled up. This is the most powerful and effective way to post-process the response of an ASP.NET page before it is sent out. And, more importantly, you get this by only writing a single additional component and registering it declaratively into the system.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.