views:

412

answers:

3

Please help me with this action filter.

I think i need to use OnResultExecuted method

How can i have access to otput html and replace something in them?

thank you.

+1  A: 

Now I see what you want to do. And I think I might have a solution. I have used parts of this approach in a output cache solution a while back, so I think it will work.

First you need your own stream class that looks like this:

private class CapturingResponseFilter : Stream
{
    private readonly Stream _sink;
    private readonly MemoryStream _mem;

    public CapturingResponseFilter(Stream sink)
    {
        _sink = sink;
        _mem = new MemoryStream();
    }

    public override bool CanRead
    {
        get { return true; }
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override long Length
    {
        get { return 0; }
    }

    public override long Position { get; set; }

    public override long Seek(long offset, SeekOrigin direction)
    {
        return 0;
    }

    public override void SetLength(long length)
    {
        _sink.SetLength(length);
    }

    public override void Close()
    {
        _sink.Close();
        _mem.Close();
    }

    public override void Flush()
    {
        _sink.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _sink.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _mem.Write(buffer, 0, count);
    }

    public string GetContents(Encoding enc)
    {
        var buffer = new byte[_mem.Length];
        _mem.Position = 0;
        _mem.Read(buffer, 0, buffer.Length);
        return enc.GetString(buffer, 0, buffer.Length);
    }
}

And then you do something like this in your action filter:

private Stream _originalOutputStream;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _originalOutputStream = filterContext.HttpContext.Response.Filter;
        filterContext.HttpContext.Response.Flush();
        filterContext.HttpContext.Response.Filter = new CapturingResponseFilter(filterContext.HttpContext.Response.Filter);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (_originalOutputStream == null) return;

        filterContext.HttpContext.Response.Flush();
        var capturingResponseFilter = (CapturingResponseFilter)filterContext.HttpContext.Response.Filter;
        filterContext.HttpContext.Response.Filter = _originalOutputStream;
        var textWritten = capturingResponseFilter.GetContents(filterContext.HttpContext.Response.ContentEncoding);
        //Do what you want with your text (textWritten).
        filterContext.HttpContext.Response.Write(textWritten);
    }

I would consider it a little bit of a hack solution. But I haven't seen anything that isn't.

Mattias Jakobsson
Thank you. Your method is similar to http://madskristensen.net/post/A-whitespace-removal-HTTP-module-for-ASPNET-20.aspx , but have some problems.I'm applied them to base controller (all controllers inherits from them) and now i have problems with Redirect actions: Cannot redirect after HTTP headers have been sent!
Maxim
You can't do it for all actions, only the once that renders html. You might want to check out Russell's solution. That is pretty much the same thing but uses a http module instead of a action filter.
Mattias Jakobsson
+1  A: 

How about using a whitespace removal HTTP module? It's simple to implement, clean and reusable...

http://madskristensen.net/post/A-whitespace-removal-HTTP-module-for-ASPNET-20.aspx

As with any generic whitespace removal solution though, it can easily introduce errors by removing white space where it is required. It wouldn't take long to give it a try though. :-)

Edited after comments

This will not work with .aspx files out of the box so you'll need to change the context_BeginRequest to the following...

void context_BeginRequest(object sender, EventArgs e)
{
    HttpApplication app = sender as HttpApplication;
    if (app.Response.ContentType == "text/html"
        || app.Response.ContentType == "application/xhtml+xml")
    {
        app.Response.Filter = new WhitespaceFilter(app.Response.Filter);
    }
}
Russell Giddings
That module does pretty much the same thing as I suggested. However, it will not work with asp.net mvc out of the box as it requires that you have a .aspx extension in your url (probably not the case here). So you will have to find another way to check if you should remove white spaces in the response. You can do that by checking the response against a regexp and see if it is html.
Mattias Jakobsson
How about just checking the content type on the response for text/html (or application/xhtml+xml depending on what you're returning)? See edit to my original answer. I think the HTTP Module would be beneficial due to its modularity and ease of reuse.
Russell Giddings
I'm used this method and applied in global.asaxwith .aspx extention i resolved this way: string rawUrl = Request.RawUrl.ToLower(); if (rawUrl.Contains("/account/") || rawUrl.Contains("/feedback/") || rawUrl.Contains("/home/")) {(by controller names)
Maxim
@Russell Yes, that is a much better idea to check the content type. And I agree that a http module is probably the better solution for this task.
Mattias Jakobsson
A: 

I would like to extend Russel's solution. In MVC or i guess everywhere in beginrequest event the Response.ContentType is "text/html" since we don't know what we will answer. I founf another event where the content is defined and the filter is applyable: PostReleaseRequestState

http://www.4guysfromrolla.com/articles/120308-1.aspx

Géza