2010-01-21 5 views
15

У меня есть приложение ASP.NET MVC. Мне нужно кэшировать некоторые страницы, однако только для не прошедших проверку подлинности пользователей.Как отключить кэширование вывода для аутентифицированных пользователей в ASP.NET MVC?

Я пытался использовать VaryByCustom="user" со следующей GetVaryByCustomString реализации:

public override string GetVaryByCustomString(HttpContext context, string custom) 
{ 
    if (custom == "user") 
    { 
     if (context.User.Identity.IsAuthenticated) 
     { 
     return context.User.Identity.Name; 
     } 
     else 
     { 
     return ""; 
     } 
    } 

    return base.GetVaryByCustomString(context, custom); 
} 

Однако это не совсем то, что мне нужно, потому что страницы все еще в кэше. Единственное отличие заключается в том, что теперь кешируется для каждого пользователя отдельно.

Одним из возможных решений является возвращение Guid.NewGuid() каждый раз, когда пользователь аутентифицируется, но для меня это выглядит огромной тратой ресурсов.

У вас есть какие-либо советы для меня?

ответ

32

Так вот что я сделал:

public class NonAuthenticatedOnlyCacheAttribute : OutputCacheAttribute 
{ 
    public override void OnResultExecuting(ResultExecutingContext filterContext) 
    { 
     var httpContext = filterContext.HttpContext; 

     if (httpContext.User.Identity.IsAuthenticated) 
     { 
     // it's crucial not to cache Authenticated content 
     Location = OutputCacheLocation.None; 
     } 

     // this smells a little but it works 
     httpContext.Response.Cache.AddValidationCallback(IgnoreAuthenticated, null); 

     base.OnResultExecuting(filterContext); 
    } 

    // This method is called each time when cached page is going to be 
    // served and ensures that cache is ignored for authenticated users. 
    private void IgnoreAuthenticated(HttpContext context, object data, ref HttpValidationStatus validationStatus) 
    { 
     if (context.User.Identity.IsAuthenticated)    
     validationStatus = HttpValidationStatus.IgnoreThisRequest;   
     else   
     validationStatus = HttpValidationStatus.Valid;   
    } 
} 

Большое спасибо Craig Stuntz, кто указал мне, чтобы исправить направление и чей ответ я невольно downvoted.

+1

Круто! Хорошее решение. –

+0

Интересно - были ли у вас какие-либо проблемы с этим методом с этого поста (1 год назад)? Спасибо – UpTheCreek

+0

@UpTheCreek: Мы используем немного более сложную версию этого кода в нашем продукте. Очевидно, я ничего не гарантирую, но по своему опыту это работает. –

12

Атрибуты в целом кэшированы, тогда вам необходимо сохранить оригинальное местоположение. Если вы заходите на страницу «Записан», она устанавливает «Местоположение» на «Нет», а затем, когда вы получаете доступ как анонимный, она по-прежнему отсутствует.

public class AuthenticatedOnServerCacheAttribute : OutputCacheAttribute 
{ 
    private OutputCacheLocation? originalLocation; 

    public override void OnResultExecuting(ResultExecutingContext filterContext) 
    { 
     var httpContext = filterContext.HttpContext; 

     if (httpContext.User.Identity.IsAuthenticated) 
     { 
      originalLocation = originalLocation ?? Location; 
      Location = OutputCacheLocation.None; 
     } 
     else 
     { 
      Location = originalLocation ?? Location; 
     } 

     base.OnResultExecuting(filterContext); 
    } 
} 
+2

Это должен быть принятый ответ. Ответ Jakub не будет работать – Alex

+0

.. но вам все равно потребуется часть «IgnoreAuthenticated» из принятого ответа – Alex

2

Принятый ответ верен, но он не работает для кеширования таким образом Частичные виды. Я объединил оба варианта: GetVaryByCustomString и установил Duration как минимум - для частичных просмотров и AddValidationCallback метод для страниц. На самом деле можно использовать только первый метод, но второй выглядит не так дорого - он не вызывает OnResultExecuting каждый раз, а только зарегистрированный обработчик.

Таким образом, пользовательский атрибут кэш класса

public class CacheAttribute : OutputCacheAttribute 
{ 

    public CacheAttribute() 
    { 
     Duration = 300; /*default cache time*/ 
    } 

    private bool _partialView; 

    /// <summary> 
    /// Set true if Partial view is cached 
    /// </summary> 
    public bool PartialView 
    { 
     get { return _partialView; } 
     set 
     { 
     _partialView = value; 
     if (_partialView) { 
      VaryByCustom = "Auth"; 
     } 
     } 
    } 

    public override void OnResultExecuting(ResultExecutingContext filterContext) 
    { 
     if (PartialView) OnCachePartialEnabled(filterContext); 
     else OnCacheEnabled(filterContext); 

     base.OnResultExecuting(filterContext);  
    } 

    private OutputCacheLocation? originalLocation; 
    private int? _prevDuration; 
    protected void OnCachePartialEnabled(ResultExecutingContext filterContext) 
    { 
     var httpContext = filterContext.HttpContext; 

     if (!_prevDuration.HasValue) _prevDuration = Duration; 
     Duration = httpContext.User.Identity.IsAuthenticated ? 1 : _prevDuration.Value; 
    } 

    protected void OnCacheEnabled(ResultExecutingContext filterContext) 
    { 
     var httpContext = filterContext.HttpContext; 

     if (httpContext.User.Identity.IsAuthenticated) { 
     // it's crucial not to cache Authenticated content 
     originalLocation = originalLocation ?? Location; 
     Location = OutputCacheLocation.None; 
     } 
     else { 
     Location = originalLocation ?? Location; 
    } 

     // this smells a little but it works 
     httpContext.Response.Cache.AddValidationCallback(IgnoreAuthenticated, null);  
    } 

    // This method is called each time when cached page is going to be 
    // served and ensures that cache is ignored for authenticated users. 
    private void IgnoreAuthenticated(HttpContext context, object data, ref HttpValidationStatus validationStatus) 
    { 
     validationStatus = context.User.Identity.IsAuthenticated 
     ? HttpValidationStatus.IgnoreThisRequest 
     : HttpValidationStatus.Valid; 
    } 
} 

Переопределить метод GetVaryByCustomString в Global.asax.cs

public override string GetVaryByCustomString(HttpContext context, string custom) 
{ 
    if (custom == "Auth") { 
     //do not cache when user is authenticated 
     if (context.User.Identity.IsAuthenticated) { 
     return base.GetVaryByCustomString(context, custom); 
     } 
     return "NotAuth"; 
    }  
    return base.GetVaryByCustomString(context, custom); 
} 

Используйте это так:

[Cache] 
public virtual ActionResult Index() 
{ 
    return PartialView(); 
} 

[ChildActionOnly, Cache(PartialView=true)] 
public virtual ActionResult IndexPartial() 
{ 
    return PartialView(); 
} 

Обновлено: У меня есть также добавлено исправление Fujiy здесь

Смежные вопросы