Могу ли я использовать [CompressFilter] в ASP.NET MVC без нарушения кэширования пончиков
Я пытаюсь заставить [ CompressFilter] работать с кэшированием пончиков и сталкиваться с проблемами.
Что происходит, так это то, что вся страница кэшируется, а не только пончик. Источник для CompressFilter
Я использую ниже. Я изменил это из исходного источника, чтобы использовать OnResultExecuted
вместо OnActionExecuting()
потому что мне нужен был доступ к типу результата, чтобы избежать кэширования определенных подклассов ActionResult.
Глядя на фактический исходный код MVC v1 для OutputCacheAttribute
похоже, что он также использует OnResultExecuted()
Но я не думаю, что этот факт непосредственно вызывает конфликт.
Я не знаю достаточно о том, как работает кэширование замещения, чтобы понять, почему оно ведет себя так, как оно работает. Я думаю, что стоит сказать, что это не приведет к повреждению дисплея. Это просто ведет себя так, как будто нет пончика!
Похоже, мне придется использовать какой-то плагин IIs для обработки кеширования, чего я действительно хотел избежать, но похоже, что мне нужно и кеширование пончиков.
На самом деле мне сейчас интереснее узнать, почему это имеет такой эффект, но решение, если это возможно, было бы также замечательно.
public class CompressFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
// dont encode images!
if (filterContext.Result is ImageResult)
{
return;
}
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);
}
}
}
2 ответа
Это плохая реализация класса CompressFilter.
Пожалуйста, прочтите это: Поиск предпочтительной кодировки Accept в C#
Я написал свой собственный, который будет подчиняться Accept Encoding на основе вышеупомянутой статьи:
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string[] supported = new string[] { "gzip", "deflate" };
IEnumerable<string> preferredOrder = new AcceptList(request.Headers["Accept-Encoding"], supported);
string preferred = preferredOrder.FirstOrDefault();
HttpResponseBase response = filterContext.HttpContext.Response;
switch (preferred)
{
case "gzip":
response.AppendHeader("Content-Encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
break;
case "deflate":
response.AppendHeader("Content-Encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
break;
case "identity":
default:
break;
}
}
}
public class AcceptList : IEnumerable<string>
{
Regex parser = new Regex(@"(?<name>[^;,\r\n]+)(?:;q=(?<value>[\d.]+))?", RegexOptions.Compiled);
IEnumerable<string> encodings;
public AcceptList(string acceptHeaderValue, IEnumerable<string> supportedEncodings)
{
List<KeyValuePair<string, float>> accepts = new List<KeyValuePair<string, float>>();
if (!string.IsNullOrEmpty(acceptHeaderValue))
{
MatchCollection matches = parser.Matches(acceptHeaderValue);
var values = from Match v in matches
where v.Success
select new
{
Name = v.Groups["name"].Value,
Value = v.Groups["value"].Value
};
foreach (var value in values)
{
if (value.Name == "*")
{
foreach (string encoding in supportedEncodings)
{
if (!accepts.Where(a => a.Key.ToUpperInvariant() == encoding.ToUpperInvariant()).Any())
{
accepts.Add(new KeyValuePair<string, float>(encoding, 1.0f));
}
}
continue;
}
float desired = 1.0f;
if (!string.IsNullOrEmpty(value.Value))
{
float.TryParse(value.Value, out desired);
}
if (desired == 0.0f)
{
continue;
}
accepts.Add(new KeyValuePair<string, float>(value.Name, desired));
}
}
this.encodings = from a in accepts
where supportedEncodings.Where(se => se.ToUpperInvariant() == a.Key.ToUpperInvariant()).Any() || a.Key.ToUpperInvariant() == "IDENTITY"
orderby a.Value descending
select a.Key;
}
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
return this.encodings.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.encodings).GetEnumerator();
}
}
Я переопределяю метод OnResultExecuting. Это вызывается до рендеринга ActionResult. Прежде чем проверять, принимает ли клиент сжатие, я проверяю тип результата, который я пытаюсь отобразить. Если это не ViewResult, я не применяю никакой компресс.
Чтобы это работало, ваши Действия должны явно вызывать либо View(), либо PartialView().
Вот как выглядит CompressOutputAttrtibute:
public class CompressOutputAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var result = filterContext.Result;
if (!(result is ViewResult))
return;
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding))
return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("GZIP"))
{
// we want to use gzip 1st
response.AppendHeader("Content-encoding", "gzip");
//Add DeflateStream to the pipeline in order to compress response on the fly
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.Contains("DEFLATE"))
{
//If client accepts deflate, we'll always return compressed content
response.AppendHeader("Content-encoding", "deflate");
//Add DeflateStream to the pipeline in order to compress response on the fly
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}
Внутри контроллера:
[CompressOutput]
public class ArticleController : Controller
public PartialViewResult MostPopular()
{
var viewModel = ArticleMostPopularViewModel();
viewModel.Articles = CmsService.GetMostPopularArticles();
return PartialView(viewModel);
}
public ViewResult Show(int id)
{
var viewModel = ArticleShowViewModel();
viewModel.Article = CmsService.GetArticle(id);
return View(viewModel);
}
}