Примеры фильтров действий

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Рассмотрим некоторые фильтры действий, которые могут использоваться в различных ситуациях.

Фильтр кэширования

using System.Web;
using System.Web.Mvc;
//..................
public class CacheAttribute: ActionFilterAttribute
{
    // Время кэширования, по умолчанию - 3600 секунд
    public int Duration  { get; set; }

    public CacheAttribute()
    {
        Duration = 3600;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
		// если установили длительность в 0, то кэширование не применяется
        if (Duration <= 0) return;

        HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
        TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
        // задаем публичный кэш
        cache.SetCacheability(HttpCacheability.Public);
        // установка продолжительности кэширования
        cache.SetExpires(DateTime.Now.Add(cacheDuration));
        // установка параметра max-age
        cache.SetMaxAge(cacheDuration);
        // добавляем дополнительные параметры для кэширования
        cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
    }
}

Данный фильтр во многом аналогичен встроенному фильтру кэширования OutputCache за тем исключением, что в данном случае мы сами можем определить все параметры кэширования при различных условиях. Например, определить разную логику кэширования для разных браузеров и т.д.

Для управления временем задается свойство Duration, принимающее время в секундах. Применение фильтра:

[Cache (Duration=300)]
public ActionResult Index()
{
	//.............
}

В итоге при обращении к методу мы получим следующие заголовки:

HTTP/1.1 200 OK
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=300
//..............................

Фильтр компрессии

using System.Web;
using System.Web.Mvc;
using System.IO.Compression;
//...................
public class CompressAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;
		//получаем заголовок Accept-Encoding, который указывает
		//какие алгоритмы сжатия он поддерживает
        string acceptEncoding = request.Headers["Accept-Encoding"];
        if (string.IsNullOrEmpty(acceptEncoding)) return;
        acceptEncoding = acceptEncoding.ToUpperInvariant();
        HttpResponseBase response = filterContext.HttpContext.Response;
        if (acceptEncoding.Contains("GZIP"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

С помощью заголовка Accept-Encoding в фильтре получаем, какой алгоритм сжатия поддерживается браузером: deflate или gzip (нередко поддерживаются оба, тогда выбираем gzip). Затем в зависимости от условий используем либо класс GZipStream, либо DeflateStream для сжатия выходного потока.

Фильтр пробелов

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Mvc;
//.....................
public class WhitespaceAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        // Если sitemap, то ничего не делаем
        if (filterContext.HttpContext.Request.RawUrl == "/sitemap.xml") return;

        if (response.ContentType != "text/html" || response.Filter == null) return;

        response.Filter = new SpaceCleaner(response.Filter);
    }
    // вспомогательный класс для удаления пробелов
    private class SpaceCleaner : Stream
    {
        private readonly Stream outputStream;
        StringBuilder _s = new StringBuilder();

        public SpaceCleaner(Stream filterStream)
        {
            if (filterStream == null)
                throw new ArgumentNullException("filterStream is not determined");
            outputStream = filterStream;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            var html = Encoding.UTF8.GetString(buffer, offset, count);
            //регулярное выражение для поиска пробелов между тегами
            var reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");
            html = reg.Replace(html, string.Empty);
            buffer = Encoding.UTF8.GetBytes(html);
            outputStream.Write(buffer, 0, buffer.Length);
        }
        // нереализованные методы Stream
        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }
        public override bool CanRead { get { return false; } }
        public override bool CanSeek { get { return false; } }
        public override bool CanWrite { get { return true; } }
        public override long Length { get { throw new NotSupportedException(); } }
        public override long Position
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }
        public override void Flush()
        {
            outputStream.Flush();
        }
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }
    }
}

Данный фильтр получает выходной поток в виде объекта filterContext.HttpContext.Response.Filter и передает его для обработки в объект SpaceCleaner. SpaceCleaner с помощью регулярных выражений находит все пробелы, удаляет их и измененные данные записывает в выходной поток. Таким образом, происходит минификация html-контента, объем данных немного сжимается.

Фильтр логгирования

Вначале определим модель, которая будет описывать посетителя сайта, и контекст данных:

public class Visitor
{
    public int Id { get; set; }
    public string Login { get; set; }
    public string Ip { get; set; }
    public string Url { get; set; }
    public DateTime Date { get; set; }
}

public class LogContext : DbContext
{
        public DbSet<Visitor> Visitors { get; set; }
}

Тогда фильтр, производящий логгирование в базу данных, будет выглядеть следующим образом:

using System.Web.Mvc;
using SomeActionFiltersApp.Models;
using System;
//.....................
public class LogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var request = filterContext.HttpContext.Request;
            
        Visitor visitor= new Visitor()
        {
            Login = (request.IsAuthenticated) ? filterContext.HttpContext.User.Identity.Name : "null",
            Ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress,
            Url = request.RawUrl,
            Date = DateTime.UtcNow
        };

        using(LogContext db = new LogContext())
        {
            db.Visitors.Add(visitor);
            db.SaveChanges();
        } 
        base.OnActionExecuting(filterContext);
    }
}
Custom Action filters in ASP.NET MVC 5
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850