2010-09-28 3 views
5

Я пытаюсь использовать пользовательский ITempDataProvider поставщика для хранения TempData в куки браузера, вместо состояния сеанса. Однако все работает отлично, за исключением того, что я не могу удалить cookie из потока Response после его чтения.ASP.NET MVC TempData в браузере куки

Любые идеи?
Спасибо!

public class CookieTempDataProvider : ITempDataProvider 
    { 
     internal const string TempDataCookieKey = "__ControllerTempData"; 
     HttpContextBase _httpContext; 

     public CookieTempDataProvider(HttpContextBase httpContext) 
     { 
      if (httpContext == null) 
      { 
       throw new ArgumentNullException("httpContext"); 
      } 
      _httpContext = httpContext; 
     } 

     public HttpContextBase HttpContext 
     { 
      get 
      { 
       return _httpContext; 
      } 
     } 

     protected virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) 
     { 
      HttpCookie cookie = _httpContext.Request.Cookies[TempDataCookieKey]; 
      if (cookie != null && !string.IsNullOrEmpty(cookie.Value)) 
      { 
       IDictionary<string, object> deserializedTempData = DeserializeTempData(cookie.Value); 

       // Remove cookie     
       cookie.Expires = DateTime.MinValue; 
       cookie.Value = string.Empty; 
       _httpContext.Request.Cookies.Remove(TempDataCookieKey); 

       if (_httpContext.Response != null && _httpContext.Response.Cookies != null) 
       { 
        HttpCookie responseCookie = _httpContext.Response.Cookies[TempDataCookieKey]; 
        if (responseCookie != null) 
        { 
         // Remove cookie 
         cookie.Expires = DateTime.MinValue; 
         cookie.Value = string.Empty; 
         _httpContext.Response.Cookies.Remove(TempDataCookieKey); 

        } 
       } 

       return deserializedTempData; 
      } 

      return new Dictionary<string, object>(); 
     } 

     protected virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) 
     { 

      string cookieValue = SerializeToBase64EncodedString(values); 
      var cookie = new HttpCookie(TempDataCookieKey); 
      cookie.HttpOnly = true; 
      cookie.Value = cookieValue; 

      _httpContext.Response.Cookies.Add(cookie); 
     } 

     public static IDictionary<string, object> DeserializeTempData(string base64EncodedSerializedTempData) 
     { 
      byte[] bytes = Convert.FromBase64String(base64EncodedSerializedTempData); 
      var memStream = new MemoryStream(bytes); 
      var binFormatter = new BinaryFormatter(); 
      return binFormatter.Deserialize(memStream, null) as IDictionary<string, object> /*TempDataDictionary : This returns NULL*/; 
     } 

     public static string SerializeToBase64EncodedString(IDictionary<string, object> values) 
     { 
      MemoryStream memStream = new MemoryStream(); 
      memStream.Seek(0, SeekOrigin.Begin); 
      var binFormatter = new BinaryFormatter(); 
      binFormatter.Serialize(memStream, values); 
      memStream.Seek(0, SeekOrigin.Begin); 
      byte[] bytes = memStream.ToArray(); 
      return Convert.ToBase64String(bytes); 
     } 

     IDictionary<string, object> ITempDataProvider.LoadTempData(ControllerContext controllerContext) 
     { 
      return LoadTempData(controllerContext); 
     } 

     void ITempDataProvider.SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) 
     { 
      SaveTempData(controllerContext, values); 
     } 
    } 
+2

Будьте очень осторожны, что вы храните в cookie клиента. Вообще-то, это плохая идея держать там вещи. Не пытайтесь судить, но он делает такие вещи, которые привели к [DotNetNuke, страдающему плохой] (http://www.youtube.com/watch?v=yghiC_U2RaM) из недавнего эксплойта оракула (http: // weblogs.asp.net/scottgu/archive/2010/09/18/important-asp-net-security-vulnerability.aspx). –

+0

Согласен с @cottsak. Кроме того, он отправляется с * каждым запросом. * Каждое изображение. Каждый скрипт. Каждый ... –

+0

@cottsak и @Craig: Я пытаюсь сохранить только уведомления на дисплее, такие как «ваше сообщение отправлено». Я не храню никаких конфиденциальных данных. – 2010-09-29 02:17:34

ответ

3

Привет У меня тоже была такая же проблема, и это была проблема с реализацией CookieTempDataProvider.

Так что я немного изменил код и теперь он отлично работает.

Когда он считывает данные из файла cookie, он удаляет его из запроса и ответа. Но добавьте еще один файл cookie с пустым значением в функцию SaveData, которая вызывается, когда обработка запроса завершена.

Замечания: Если вы хотите удалить cookie, вы должны установить значение таймаута и отправить его клиенту, а затем браузер удалит его. Мы не можем сделать это иначе, чем код a cookie обрабатывается браузером

И я узнал, что установка истечения срока действия DateTime.MinValue не заканчивает работу с cookie в chrome (не знаю о других браузерах) поэтому я его 2001-01-01 :)

Вот рабочий код

public class CookieTempDataProvider : ITempDataProvider 
{ 
    internal const string TempDataCookieKey = "__ControllerTempData"; 
    HttpContextBase _httpContext; 

    public CookieTempDataProvider(HttpContextBase httpContext) 
    { 
     if (httpContext == null) 
     { 
      throw new ArgumentNullException("httpContext"); 
     } 
     _httpContext = httpContext; 
    } 

    public HttpContextBase HttpContext 
    { 
     get 
     { 
      return _httpContext; 
     } 
    } 

    protected virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) 
    { 
     if (_httpContext.Request.Cookies.AllKeys.Contains(TempDataCookieKey)) //we need this because 
     //Cookies[TempDataCookieKey] will create the cookie if it does not exist 
     { 
      HttpCookie cookie = _httpContext.Request.Cookies[TempDataCookieKey]; 
      if (cookie != null && !string.IsNullOrEmpty(cookie.Value)) 
      { 
       IDictionary<string, object> deserializedTempData = DeserializeTempData(cookie.Value); 

       // Remove cookie     
       cookie.Expires = new DateTime(2000, 1, 1); 
       cookie.Value = string.Empty; 
       _httpContext.Request.Cookies.Remove(TempDataCookieKey); 

       if (_httpContext.Response != null && _httpContext.Response.Cookies != null) 
       { 
        HttpCookie responseCookie = _httpContext.Response.Cookies[TempDataCookieKey]; 
        if (responseCookie != null) 
        { 
         // Remove cookie 
         cookie.Expires = new DateTime(2000, 1, 1); 
         cookie.Value = string.Empty; 
         _httpContext.Response.Cookies.Remove(TempDataCookieKey); 

        } 
       } 

       return deserializedTempData; 
      } 
     } 
     return new Dictionary<string, object>(); 
    } 

    protected virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) 
    { 
     if (values != null && values.Count > 0) 
     { 
      //there are values to set, so add the cookie. But no need to expire it as we need the browser to send the 
      //cookie back with the next request 
      string cookieValue = SerializeToBase64EncodedString(values); 
      var cookie = new HttpCookie(TempDataCookieKey); 
      cookie.HttpOnly = true; 
      cookie.Value = cookieValue; 

      _httpContext.Response.Cookies.Add(cookie); 
     } 
     else 
     { 
      //Still we need to add the cookie with the expiration set, to make the client browser remove the cookie from the request. 
      //Otherwise the browser will continue to send the cookie with the response 

      //Also we need to do this only if the requet had a tempdata cookie 

      if (_httpContext.Request.Cookies.AllKeys.Contains(TempDataCookieKey)) 
      { 
       { 
        HttpCookie cookie = _httpContext.Request.Cookies[TempDataCookieKey]; 

        // Remove the request cookie     
        cookie.Expires = new DateTime(2000, 1, 1); 
        cookie.Value = string.Empty; 
        _httpContext.Request.Cookies.Remove(TempDataCookieKey); 

        var rescookie = new HttpCookie(TempDataCookieKey); 
        rescookie.HttpOnly = true; 
        rescookie.Value = ""; 
        rescookie.Expires = new DateTime(2000, 1, 1); //so that the browser will remove the cookie when it receives the request 
        _httpContext.Response.Cookies.Add(rescookie); 
       } 
      } 
     } 
    } 

    public static IDictionary<string, object> DeserializeTempData(string base64EncodedSerializedTempData) 
    { 
     byte[] bytes = Convert.FromBase64String(base64EncodedSerializedTempData); 
     var memStream = new MemoryStream(bytes); 
     var binFormatter = new BinaryFormatter(); 
     return binFormatter.Deserialize(memStream, null) as IDictionary<string, object> /*TempDataDictionary : This returns NULL*/; 
    } 

    public static string SerializeToBase64EncodedString(IDictionary<string, object> values) 
    { 
     MemoryStream memStream = new MemoryStream(); 
     memStream.Seek(0, SeekOrigin.Begin); 
     var binFormatter = new BinaryFormatter(); 
     binFormatter.Serialize(memStream, values); 
     memStream.Seek(0, SeekOrigin.Begin); 
     byte[] bytes = memStream.ToArray(); 
     return Convert.ToBase64String(bytes); 
    } 

    IDictionary<string, object> ITempDataProvider.LoadTempData(ControllerContext controllerContext) 
    { 
     return LoadTempData(controllerContext); 
    } 

    void ITempDataProvider.SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) 
    { 
     SaveTempData(controllerContext, values); 
    } 
} 
2

Вот пример рабочего раствора без большого количества избыточного кода. Он использует Json.NET для сериализации, которая быстрее, чем BinaryFormatter + Base64Encoding, а также производит намного меньше более короткой строки (= меньше накладных расходов по HTTP).

public class CookieTempDataProvider : ITempDataProvider 
{ 
    const string cookieKey = "temp"; 

    public IDictionary<string, object> LoadTempData(ControllerContext controllerContext) 
    { 
     var cookie = controllerContext.HttpContext.Request.Cookies[cookieKey]; 

     if (cookie != null) { 
      return JsonConvert.DeserializeObject<IDictionary<string, object>>(cookie.Value); 
     } 

     return null; 
    } 

    // Method is called after action execution. The dictionary mirrors the contents of TempData. 
    // If there are any values in the dictionary, save it in a cookie. If the dictionary is empty, 
    // remove the cookie if it exists. 
    public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) 
    { 
     var ctx = controllerContext.HttpContext; 

     if (values.Count > 0) { 
      var cookie = new HttpCookie(cookieKey) 
      { 
       HttpOnly = true, 
       Value = JsonConvert.SerializeObject(values) 
      }; 

      ctx.Response.Cookies.Add(cookie); 
     } else if (ctx.Request.Cookies[cookieKey] != null) { 

      // Expire cookie to remove it from browser. 
      ctx.Response.Cookies[cookieKey].Expires = DateTime.Today.AddDays(-1); 
     } 
    } 
} 
+0

вы должны обернуть несколько trycatches вокруг десериализации –

4

Существует лучшее решение по Brock Allen на GitHub, который использует шифрование, 2 формы сериализации и сжатие для защиты и оптимизации печенья.

https://github.com/brockallen/CookieTempData

Вот ссылка на блог о нем:

http://brockallen.com/2012/06/11/cookie-based-tempdata-provider/

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

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