Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
Рассмотрим некоторые фильтры действий, которые могут использоваться в различных ситуациях.
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); } }