使用控制器动作过滤器捕获HTML输出

时间:2011-08-17 14:43:35

标签: c# asp.net-mvc-2 stream controller action-filter

我在捕获HTML输出的动作上有以下过滤器,将其转换为字符串,执行一些操作以修改字符串,并返回带有新字符串的ContentResult。不幸的是,我一直以一个空字符串结束。

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

我已经确定了StreamReader(stream).ReadToEnd()返回一个空字符串,但我无法弄清楚原因。

任何想法如何解决这个问题?

编辑:我已将OnActionExecuted更改为OnResultExecuted,现在在生成View后调用它,但流仍为空!

4 个答案:

答案 0 :(得分:11)

我通过劫持HttpWriter解决了这个问题,并将其写入StringBuilder而不是响应,然后在将其写入输出之前对响应做任何需要做的事情。

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

答案 1 :(得分:2)

在阅读之前设置Position = 0;,尝试将流重新排列到开头。

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

答案 2 :(得分:1)

我认为我已经开发了一种很好的方法来实现这一目标。

  • 使用自定义过滤器替换响应过滤器
  • 此过滤器将委托给一个带流的抽象方法
  • 这个委托,因此抽象方法在流的结束时调用,即当所有HTML都可用时
  • 覆盖OnClose方法并根据需要使用流。

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

然后,您可以从此派生到另一个属性以访问流并获取HTML:

public class MyAttribute : ReadOnlyActionFilterAttribute
{
    protected override void OnClose(Stream stream)
    {
        var html = new HtmlDocument();
        html.Load(stream);
        // play with html
    }
}

答案 3 :(得分:0)

您可以在OnActionExectuted方法中验证流是否为NULL?我不确定流变量的状态是通过整个过程存储的。

为什么不尝试从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;
}