Почему я не могу объединить атрибуты [Authorize] и [OutputCache] при использовании кэша Azure (приложение .NET MVC3)?
Использование Windows AzureMicrosoft.Web.DistributedCache.DistributedCacheOutputCacheProvider
в качестве поставщика outputCache для приложения MVC3. Вот соответствующий метод действия:
[ActionName("sample-cached-page")]
[OutputCache(Duration = 300, VaryByCustom = "User",
Location = OutputCacheLocation.Server)]
[Authorize(Users = "[email protected],[email protected]")]
public virtual ActionResult SampleCachedPage()
{
return View();
}
Я получаю следующее исключение при загрузке этого представления из веб-браузера:
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
at System.Web.Caching.OutputCache.InsertResponse(String cachedVaryKey, CachedVary cachedVary, String rawResponseKey, CachedRawResponse rawResponse, CacheDependency dependencies, DateTime absExp, TimeSpan slidingExp)
at System.Web.Caching.OutputCacheModule.OnLeave(Object source, EventArgs eventArgs)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Если я удалю атрибут [Authorize], представление будет кэшироваться, как и ожидалось. Означает ли это, что я не могу поместить [OutputCache] в метод действия, который должен иметь [Authorize]? Или мне нужно переопределить AuthorizeAttribute с пользовательской реализацией, которая использует метод обратного вызова статической проверки для кэша?
Обновление 1
После ответа Эвана я протестировал описанный выше метод действия в IIS Express (за пределами Azure). Вот мое переопределение для свойства VaryByCustom = "User" в атрибуте OutputCache:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
return "User".Equals(custom, StringComparison.OrdinalIgnoreCase)
? Thread.CurrentPrincipal.Identity.Name
: base.GetVaryByCustomString(context, custom);
}
Когда я захожу на пример кэшированной страницы с именем [email protected], выходные данные страницы кэшируются, и в представлении отображается «Эта страница была кэширована 31.12.2011 11:06:12 AM (UTC) ". Если я затем выйду из системы и войду как [email protected] и заеду на страницу, на ней отобразится сообщение" Эта страница была кэширована 31.12.2011 11:06:38 AM (UTC) ". Повторный вход в систему как [email protected] и повторное посещение страницы приводит к отображению кэша" Эта страница была кэширована 31.12.2011 11:06:12 AM (UTC) "снова. Дальнейшие попытки входа / выхода показывают, что различные выходные данные кэшируются и возвращаются в зависимости от пользователя.
Это наводит меня на мысль, что выходные данные кэшируются отдельно в зависимости от пользователя, что и является намерением с моей настройкой и переопределением VaryByCustom = "User". Проблема заключается в том, что он не работает с поставщиком распределенного кэша Azure. Эван, вы отвечаете только о кешировании публичного контента?
Обновление 2
Я откопал исходный код и обнаружил, что во встроенном AuthorizeAttribute действительно есть нестатический обратный вызов проверки. Вот выдержка изOnAuthorization
:
if (AuthorizeCore(filterContext.HttpContext)) {
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
CacheValidationHandler
делегирует проверку кэшаprotected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase)
что, конечно, не является статичным. Одна из причин, почему он не является статичным, заключается в том, что, как отмечено в ВАЖНОМ комментарии выше, он вызываетprotected virtual bool AuthorizeCore(HttpContextBase)
.
Чтобы выполнить любую логику AuthorizeCore из метода обратного вызова проверки статического кэша, ему необходимо знать свойства Users и Roles экземпляра AuthorizeAttribute. Однако, кажется, нет простого способа подключить его. Мне пришлось бы переопределить OnAuthorization, чтобы поместить эти 2 значения в HttpContext (коллекция Items?), А затем переопределить OnCacheAuthorization, чтобы вернуть их обратно. Но это пахнет грязно.
Если мы осторожно используем свойство VaryByCustom = "User" в атрибуте OutputCache, можем ли мы просто переопределить OnCacheAuthorization, чтобы всегда возвращать HttpValidationStatus.Valid? Если у метода действия нет атрибута OutputCache, нам не нужно беспокоиться о том, что этот обратный вызов когда-либо будет вызван, верно? И если у нас есть атрибут OutputCache без VaryByCustom = "User", то должно быть очевидно, что страница может вернуть любую кэшированную версию независимо от того, какой запрос пользователя создал кэшированную копию. Насколько это рискованно?