Capturing HTML output with a controller action fil

2019-02-02 16:57发布

I've got the following filter in place on an action to capture the HTML output, convert it to a string, do some operations to modify the string, and return a ContentResult with the new string. Unfortunately, I keep ending up with an empty string.

private class UpdateFilter : ActionFilterAttribute
    {
        private Stream stream;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            stream = filterContext.HttpContext.Response.Filter;
            stream = new MemoryStream();
            filterContext.HttpContext.Response.Filter = stream;
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            StreamReader responsereader = new StreamReader(filterContext.HttpContext.Response.Filter);  //empty stream? why?
            responsereader.BaseStream.Position = 0;
            string response = responsereader.ReadToEnd();
            ContentResult contres = new ContentResult();
            contres.Content = response;
            filterContext.Result = contres;
        }
    }

I've pinned down that StreamReader(stream).ReadToEnd() returns an empty string, but I can't figure out why.

Any ideas how to fix this?

EDIT: I've changed the OnActionExecuted to OnResultExecuted, and now it is called after the View has been generated, but the stream is still empty!

4条回答
Bombasti
2楼-- · 2019-02-02 17:35

Can you verify that stream is not NULL in the OnActionExectuted-method? I'm not sure the state of the stream-variable is being stored through the process..

Why don't you try to get the stream out of the filterContext:

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    var stream = filterContext.HttpContext.Response.Filter;
    string response = new StreamReader(stream).ReadToEnd();
    ContentResult contres = new ContentResult();
    contres.Content = response;
    filterContext.Result = contres;
}
查看更多
看我几分像从前
3楼-- · 2019-02-02 17:37

I solved this by hijacking the HttpWriter, and having it write into a StringBuilder rather than the response, and then doing whatever needs to be done to/with the response before writing it to the output.

 private class UpdateFilter : ActionFilterAttribute
    {
        private HtmlTextWriter tw;
        private StringWriter sw;
        private StringBuilder sb;
        private HttpWriter output;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            sb = new StringBuilder();
            sw = new StringWriter(sb);
            tw = new HtmlTextWriter(sw);
            output = (HttpWriter)filterContext.RequestContext.HttpContext.Response.Output;
            filterContext.RequestContext.HttpContext.Response.Output = tw;
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            string response = sb.ToString();
            //response processing
            output.Write(response);
        }
    }
查看更多
唯我独甜
4楼-- · 2019-02-02 17:50

I think I've developed a pretty good way to do this.

  • Replace the Reponse Filter with a custom one
  • This filter takes a delegate to an abstract method which takes a stream
  • This the delegate, and hence the abstract method are called on the close of the stream, i.e. when all the HTML is available
  • Override the OnClose method and play with the stream as you like.

public abstract class ReadOnlyActionFilterAttribute : ActionFilterAttribute
{
    private delegate void ReadOnlyOnClose(Stream stream);

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Filter = new OnCloseFilter(
            filterContext.HttpContext.Response.Filter, 
            this.OnClose);
        base.OnActionExecuting(filterContext);
    }

    protected abstract void OnClose(Stream stream);

    private class OnCloseFilter : MemoryStream
    {
        private readonly Stream stream;

        private readonly ReadOnlyOnClose onClose;

        public OnCloseFilter(Stream stream, ReadOnlyOnClose onClose)
        {
            this.stream = stream;
            this.onClose = onClose;
        }

        public override void Close()
        {
            this.Position = 0;
            this.onClose(this);
            this.Position = 0;
            this.CopyTo(this.stream);
            base.Close();
        }
    }
}

You can then derive from this to another attribute to access the stream and get the HTML:

public class MyAttribute : ReadOnlyActionFilterAttribute
{
    protected override void OnClose(Stream stream)
    {
        var html = new HtmlDocument();
        html.Load(stream);
        // play with html
    }
}
查看更多
smile是对你的礼貌
5楼-- · 2019-02-02 17:52

Try rewinding the stream to the beginning by setting Position = 0; before you read it.

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    stream.Position = 0;
    string response = new StreamReader(stream).ReadToEnd();
    ContentResult contres = new ContentResult();
    contres.Content = response;
    filterContext.Result = contres;
}
查看更多
登录 后发表回答