ASP.NET Response.Filter

2020-07-24 04:22发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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);
}