public Stream Filter { get; set; }

Update: I have completely re-written this post - Apologies if you have already read it, but if you read it again it might actually make sense this time :-) [PJ]

Work has been really busy recently so my posts have not been as regular as I would have liked; however, in the short term I wanted to share a little something that I thought might useful to others. Very soon I'll be doing a piece (maybe a couple of pieces) on a private project which has involved JavaScript, XML, ASP.Net, C# and JSON - focusing on the "How To" part of JSON.

Anyway, on with this post. I have recently had occasion to write a post processing Rules engine for ASP.Net, the basic idea being that we have a need to change the content of the out-going HTTP response stream without the originator of the HTML content being involved in the process. If this all sounds a little convoluted then that'll be because it is - the real issue is that we do not want change the existing codebase but we still need to make some minor modifications to the HTML that the existing code spews out.

What follows is some pseudo code that outlines the intent of how we wanted to achieve that goal:

string responseText = null;
HttpResponse.OutputStream.Position = 0;
using(StreamReader reader =
new StreamReader(HttpResponse.OutputStream)
responseText = reader.ReadToEnd();

// ...check the response text for "whatever"
// ...and replace
responseText = FindAndReplace();

HttpResponse.OutputStream.Position = 0;
using(StreamWriter writer =
new StreamWriter(HttpResponse.OutputStream)
The idea would then be to wire-up this all up in an event within an HttpModule. Well, that was the intent - rather than the implementation. In fact, what is shown above is the prototype code that I originally wrote when trying to prove the idea; however, what has been described above is not possible - the HttpResponse.OutputStream is write-only, you cannot move the position of the cursor manually nor can you read from this stream in any way what-so-ever; all you can to is add more stuff to it.

So if this isn't what I did the question surely must be: So, how do you replace the contents of the response stream at runtime?

Well, the answer lies in the filtering capability of HttpResponse object (and HttpRequest object for that matter); which has the capability to insert a chain of filters which can get in the way when writing to the underlying stream. The kudos for this answer goes to Fritz Onion and his great book (the only one you ever need to read for ASP.Net) to which there is now a second volume that discusses .Net 2.0, which I have yet to read but most definitely will be. An example of the technique is illustrated below:
HttpResponse.Filter = new MyCustomStream(HttpResponse.Filter);
Now any time a call is made to the underlying stream in the HttpResponse object your filter is called, this then gives you the opportunity to mess with the data to be written in any way you see fit, before passing it along to the base stream (or the next stream the in chain).

All that remains is for you to write a custom stream class as the filter is really a stream - WTF! I hear you say... well, this is not as daunting a task as it may sound or first seem; the only method that you do not pass directly on to your contained base filter is the call to Write(), so really it's just a bunch of typing, rather than being hard or complicated to actually do. Here's an example:
public class MyCustomStream : Stream
private Stream _baseStream;

public MyCustomStream(Stream baseStream)
this._baseStream = baseStream;

public void Write(byte[] buffer, int offset, int count)
// your magic with the buffer here

// ...once done write the data to the buffer
this._baseStream.Write(buffer, offset, count);

// ...remainder of the class implementation

The only thing left to do it to wire up your stream in the right place. This can be achieved in an HttpModule where ever you see fit, but think about the fact that you may not want your filter to play a part in all requests. A sample application that shows off this concept, and saves you a whole bunch of typing, can be downloaded from here.

No comments: