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

本文关键字:HTML 输出 过滤器 控制器 | 更新日期: 2023-09-27 18:05:47

我在一个动作上设置了以下过滤器,用于捕获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()返回一个空字符串,但我不知道为什么。

有什么办法解决这个问题吗?

EDIT:我已经将onactionperformed更改为onresultperformed,现在它在视图生成后被调用,但流仍然是空的!

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

我通过劫持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);
    }
}

上面的代码使用HttpContext来避免线程错误——参见jaminto的注释

private class RenderFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        StringBuilder sb = new StringBuilder();
        StringWriter sw = new StringWriter(sb);
        HtmlTextWriter tw = new HtmlTextWriter(sw);
        HttpWriter output = (HttpWriter)filterContext.RequestContext.HttpContext.Response.Output;
        filterContext.HttpContext.Items["sb"] = sb;
        filterContext.HttpContext.Items["output"] = output;
        filterContext.RequestContext.HttpContext.Response.Output = tw;
    }
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        string response = filterContext.HttpContext.Items["sb"].ToString();
        //response processing
        ((HttpWriter)filterContext.HttpContext.Items["output"]).Write(response);
    }
}

尝试在读取之前通过设置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;
}

我想我已经想出了一个很好的方法。

  • 用自定义过滤器替换响应过滤器
  • 这个过滤器接受一个抽象方法的委托,抽象方法接受一个流
  • 这是委托,因此抽象方法在流结束时被调用,即当所有的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
    }
}

你能在onactionexecute -method中验证流不是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;
}