MVC FilterAttribute获取响应长度

时间:2014-01-05 05:29:26

标签: c# asp.net asp.net-mvc-4

我有一个MVC4(.NET 4.5)应用程序。我在控制器上有一个使用简单Response.Write()方法的void Action。我想写一个FilterAttribute来检查OnActionExecuted()方法中的响应长度是否为0。但是,我似乎无法找到合适的ActionExecutedContext属性来执行此操作。 到目前为止我尝试了什么:

public void OnActionExecuted(ActionExecutedContext filterContext)
{
    if (filterContext.HttpContext.Response.OutputStream.Length == 0)
    {
        Logger.Log("empty response detected");
    }
}

但是,这会引发Specified method is not supported.异常。我发现这个流是只写的。我应该检查什么属性?

1 个答案:

答案 0 :(得分:0)

是的,由于@Darin描述的原因,你不能依靠HttpContext.Response.OutputStream.Length来获得响应长度。即使你确实得到了长度,但这不仅仅是内容体的长度,所以它无论如何都不准确。

我将向您展示如何实现类似的任务:如果当天任何AJAX / JSON请求超过应用程序中定义的限制的95%,则在一天结束时发送电子邮件。注意:我只检查ajax请求,即JsonResult

1。覆盖MaxJsonLength

的默认JsonResult

MaxJsonLength的默认JsonResult设置为102400字节(100k)。所以我需要覆盖JsonResult,尤其是Json() Controller。因此我为此定义了一个自定义Mvc控制器。

public abstract class PowerfulControllerBase : Controller
{
    protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)
    {
        return new JsonResult()
        {
            Data = data,
            ContentType = contentType,
            ContentEncoding = contentEncoding,
            MaxJsonLength = JsonResultFilter.MaxJsonLengthInBytes
        };

    protected override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
    {
        return new JsonResult()
        {
            Data = data,
            ContentType = contentType,
            ContentEncoding = contentEncoding,
            JsonRequestBehavior = behavior,
            MaxJsonLength = JsonResultFilter.MaxJsonLengthInBytes
        };
    }        
}

MaxJsonLength在名为ResultFilter的自定义JsonResultFilter中定义,我稍后会展示。

2。定义自定义JsonResult

由于我们无法依靠Response对象来获取内容体长度,因此我们必须先以某种方式处理响应数据。因此,我将在此定义一个自定义JsonResult类,该类使用 Newtonsoft JSON Serializer来序列化对象/数据。

public class JsonNetResult : JsonResult
{
    public int ContentLength { get; private set; }

    public JsonNetResult()
    {
        this.ContentLength = 0;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var response = context.HttpContext.Response;

        response.ContentType = !String.IsNullOrEmpty(base.ContentType)
            ? base.ContentType
            : "application/json";

        var serializedObject = JsonConvert.SerializeObject(base.Data, Formatting.None);

        if (base.ContentEncoding != null)
        {
            response.ContentEncoding = base.ContentEncoding;

            this.ContentLength = response.ContentEncoding.GetByteCount(serializedObject);
        }
        else
        {
            this.ContentLength = Encoding.UTF8.GetByteCount(serializedObject);
        }

        response.Write(serializedObject);
    }
}

在使用JsonNetResult进行序列化后,新ContentLength有一个名为JsonConvert的附加属性,用于包含内容的长度。

现在我们需要在步骤1中用此类替换JsonResult

public abstract class PowerfulControllerBase : Controller
{
    protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)
    {
        return new JsonNetResult()
        {
            ...
        };
    }

    protected override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
    {
        return new JsonNetResult()
        {
            ...
        };
    }
}

现在,从此JsonResult返回的PowerfulControllerBase将具有名为ContentLength的特殊属性。我们需要做的最后一件事是定义ActionFilter,更具体地说是ResultFilter

3。定义结果过滤器

我们希望在AJAX响应内容大小即将超过我们定义的限制时检测到。因此,我们需要定义一个过滤器并将其注入GlobalFilterCollection

public class JsonResultFilter : IResultFilter
{
    public const int MaxJsonLengthInBytes = 15728640;    // 15360 k = 15 mb
    public const double AlertPercentage = .95;           // 95%

    // A service that records this warning
    private readonly ISystemWarningService _systemWarningService;

    public JsonResultFilter(ISystemWarningService systemWarningService)
    {
        _systemWarningService = systemWarningService;
    }

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        // We're only interested in JsonNetResult
        if (filterContext.Result is JsonNetResult)
        {
            JsonNetResult jsonResult = filterContext.Result as JsonNetResult;
            // See if the content length exceeds the percentage of the limit
            if ((double)jsonResult.ContentLength / MaxJsonLengthInBytes >= AlertPercentage)
            {
                _systemWarningService.LogSizeWarning(filterContext.HttpContext.Request.RawUrl,
                    filterContext.HttpContext.User.Identity.Name,
                    jsonResult.ContentLength,
                    MaxJsonLengthInBytes);
            }
        }
    }

    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
    }
}

例如,所有警告都可以插入到数据库中,您可以定义在每天结束时运行的SQL作业以发送电子邮件通知。