3

I need to create filter that replace tags <h2> in the HTML to <h3>:

My filter

public class TagsFilter:Stream
{
    HttpContext qwe;

    public TagsFilter(HttpContext myContext)
    {
        qwe = myContext;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        string html = System.Text.Encoding.UTF8.GetString(buffer);
        html = html.Replace("<h2>", "<h3>");
        qwe.Response.Write(html.ToCharArray(), 0, html.ToCharArray().Length);
    }

My module

public class TagsChanger : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.Response.Filter = new TagsFilter(context.Context);
    }

I get error System.Web.HttpException:In this context, the answer is not available.

MikeSmithDev
  • 15,731
  • 4
  • 58
  • 89
Stalli
  • 374
  • 2
  • 5
  • 16
  • In the "input html"? or what you are rendering to the client, [like in this SO post](http://stackoverflow.com/questions/12999665/how-to-get-trace-asp-net-outgoing-response-text/13440223#13440223)? – MikeSmithDev Feb 17 '13 at 20:03
  • what i am rendering to the client. ty for link but i must use Response.Filter and HttpModule. (my task) – Stalli Feb 17 '13 at 20:19

3 Answers3

7

Look at Rick Strahl's post about "Capturing and Transforming ASP.NET Output with Response.Filter".

Response.Filter content is chunked. So to implement a Response.Filter effectively requires only that you implement a custom stream and handle the Write() method to capture Response output as it’s written. At first blush this seems very simple – you capture the output in Write, transform it and write out the transformed content in one pass. And that indeed works for small amounts of content. But you see, the problem is that output is written in small buffer chunks (a little less than 16k it appears) rather than just a single Write() statement into the stream, which makes perfect sense for ASP.NET to stream data back to IIS in smaller chunks to minimize memory usage en route.

Unfortunately this also makes it a more difficult to implement any filtering routines since you don’t directly get access to all of the response content which is problematic especially if those filtering routines require you to look at the ENTIRE response in order to transform or capture the output as is needed for the solution the gentleman in my session asked for.

So in order to address this a slightly different approach is required that basically captures all the Write() buffers passed into a cached stream and then making the stream available only when it’s complete and ready to be flushed.

As I was thinking about the implementation I also started thinking about the few instances when I’ve used Response.Filter implementations. Each time I had to create a new Stream subclass and create my custom functionality but in the end each implementation did the same thing – capturing output and transforming it. I thought there should be an easier way to do this by creating a re-usable Stream class that can handle stream transformations that are common to Response.Filter implementations.

Rick Strahl wrote own implementation of stream filter that permits text replacing in right way.

Community
  • 1
  • 1
Evgeni Nabokov
  • 2,421
  • 2
  • 32
  • 36
  • Be careful with this, though. In this example, he is capturing the entire request in memory before processing it. This will have a performance impact that needs to be considered. – Neil Sep 15 '14 at 19:13
  • 1
    @NeilWhitaker I tried out the Rick's solution on high loaded e-commerce website. It works acceptably. Filtering response on loaded sites is not good idea though. – Evgeni Nabokov Sep 16 '14 at 07:59
4

I did a small example. I think you have to access the original stream, rather than accessing the httpContext.

public class ReplacementStream : Stream
{
    private Stream stream;
    private StreamWriter streamWriter;

    public ReplacementStream(Stream stm)
    {
        stream = stm;
        streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        string html = System.Text.Encoding.UTF8.GetString(buffer);
        html = html.Replace("<h2>", "<h3>");
        streamWriter.Write(html.ToCharArray(), 0, html.ToCharArray().Length);
        streamWriter.Flush();
    }

    // all other necessary overrides go here ...
}

public class FilterModule : IHttpModule
{
    public String ModuleName
    {
        // Verweis auf Name in Web.config bei Modul-Registrierung
        get { return "FilterModule"; }
    }
    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current;
        context.Response.Filter = new ReplacementStream(context.Response.Filter);
    }
    public void Init(HttpApplication context)
    {
       context.BeginRequest += new EventHandler(context_BeginRequest);
    }
}

Found the solution at this post on SO. Worked for me.

Community
  • 1
  • 1
Pilgerstorfer Franz
  • 8,303
  • 3
  • 41
  • 54
  • 4
    Your example isn't correct. It may not replace

    tag because Response.Filter content is chunked. More info http://www.west-wind.com/weblog/posts/2009/Nov/13/Capturing-and-Transforming-ASPNET-Output-with-ResponseFilter

    – Evgeni Nabokov Apr 19 '13 at 15:52
  • @EvgeniNabokov Rick Strahl pointed out that using Response.Filter as shown in the above example will only work with smaller amounts of code (less than 16kb). But referring to the original question (wrong stream used) I think my example provides a good (and correct!) point. In case stalli needs a 100% working example for larger amounts of code - the link given in your answer is a good solution, too. – Pilgerstorfer Franz Apr 20 '13 at 19:36
2

The problem is that you are applying the filter in the Init event, which only occurs once per application instance (it is essentially close to App_Start).

What you need to do is hook in the BeginRequest event from the Init event, and then apply the filter on BeginRequest.

public void Init(HttpApplication application)
{
    application.BeginRequest += BeginRequest;
}

private void BeginRequest(object sender, EventArgs e)
{
    var app = (HttpApplication)sender;
    var context = app.Context;
    context.Response.Filter = new TagsFilter(context);
}
Dan Turner
  • 2,233
  • 18
  • 19