ASP.NET Cookies

 

Jedną z tych rzeczy, którą musi znać każdy programista ASP.NET jest zarządzanie stanem. Na pierwszy ogień na moim blogu proponuję tematykę Cookies, czyli popularnych ciasteczek. O ciasteczkach chyba każdy posiada jakąś wiedzę, ale zawsze warto pewne informacje usystematyzować, stąd pomysł na niniejszego posta.

Ciasteczko to pewna niewielka porcja tekstu, która jest dołączana do żądań i stron wędrujących pomiędzy serwerem i przeglądarką. Ciasteczko zawiera informacje, które aplikacja może odczytać w momencie wizyty użytkownika na stronie. Taki sposób przechowywania informacji jest wymuszony bezstanowością protokołu http. Zmienne w aplikacji nie mogą być przechowywane w tradycyjny sposób, bowiem z każdym przeładowaniem strony dane są tracone. Stąd konieczność powstania mechanizmów pozwalających zachować stan aplikacji.

Ciasteczka mogą być przydatne do w miarę prostego sposobu przechowywania informacji charakterystycznych dla użytkownika, jednak aplikacja nie może na nich w całości polegąc. Niektóre przeglądarki mogą mieć wyłączoną opcję akceptowania ciasteczek. W takim przypadku aplikacja zostanie pozbawiona pewnych funkcjonalności. Ciasteczka nie powinny również służyć do przechowywania informacji, które nie powinny być jawne, jak np. hasło użytkownika. Ciasteczka są wysyłane do przeglądarki za pomocą obiektu HttpResponse, dostępnego jako właściwość Response strony. Tworząc ciasteczko należy nadać mu właściwość Name oraz Value. Nazwa musi być unikalna, w przeciwnym wypadku utworzenie ponowne ciasteczka o takiej samej nazwie spowoduje nadpisanie starego. Opcjonalnie może również zostać podana data wygaśnięcia ciasteczka przechowywanego na komputerze użytkownika poprzez przypisanie właściwości Expires obiektu typu DateTime. Jeśli taka data nie zostanie podana, to istnieje ono jedynie dla aktualnej sesji i zostanie usunięte po jej zakończeniu. Przykład utworzenia najprostszego ciasteczka może wyglądać następująco:

Response.Cookies["shoppingApp"].Value = "Michael";
Response.Cookies["shoppingApp"].Expires = DateTime.Now.AddDays(1);

W tym przypadku nazwa ciasteczka została przekazana poprzez indeksator, a nastąpiło przypisanie jedynie jego wartości oraz daty wygaśnięcia. Ciasteczko może zostać również utworzone poprzez utworzenie obiektu HttpCookie:
 
HttpCookie myCookie = new HttpCookie("shoppingApp");
...
//Add cookie to collection
Response.Cookies.Add(myCookie);

W tym przypadku nazwa ciasteczka przekazywana jest w konstruktorze obiektu HttpCookie.

Do odczytywania ciasteczek wykorzystywany jest obiekt HttpRequest, dostępny jako właściwość Request dla strony. Zawartość ciasteczka może być zatem odczytana następująco:

if(Request.Cookie["shoppingApp"] != null)
{
    HttpCookie myCookie = Request.Cookie["shoppingApp"];
    ...
}

Przed odczytaniem ciasteczka, należy oczywiście sprawdzić, czy ono istnieje, stąd konstrukcja if w powyższym kodzie.
Przykład 1

Po tym krótkim wprowadzeniu czas na przykład. Celem, dla którego wykorzystane zostanie przeze mnie przechowywanie informacji w ciasteczku, jest udostępnienie użytkownikowi możliwości dokonania ustawień w przygotowanej aplikacji. Ściśle mówiąc chodzi o utworzenie strony z ustawieniami, na której użytkownik będzie mógł zdecydować, czy chce wyświetlać kalendarz, czy też nie (potencjalnie w przyszłości inne gadżety). Wybór ten powinien umożliwiać użytkownikowi konfigurację na stałę, czyli podobnie jak w wielu serwisach wykorzystane zostaną do tego celu właśnie ciasteczka. Ze względu na to, że jeszcze nie dysponuję odpowiednio przygotowanym layoutem pozwalającym na zaimplementowanie tej funkcjonalności w Master Page, dla uproszczenia przyjmijmy, że będzie to odpowiednio włączenie/wyłączenie elementu menu.

Użytkownik może jednak nie zezwolić, aby przeglądarka akceptowała ciasteczka. Z tego powodu na początku powinno nastąpić sprawdzenie tego stanu rzeczy i pozbawienie użytkownika prawa do zmiany ustawień (po co ma się męczyć jak za każdym razem i tak będzie musiał robić to samo). Sprawdzenie, czy przeglądarka w danym momencie jest w stanie zaakceptować ciasteczka polega na utworzeniu tymczasowego ciasteczka, próbie jego zapisu i ewentualnego odczytu.

W zaimplementowanej przeze mnie wersji sprawdzenie, czy przeglądarka akceptuje ciasteczka, następuje przy wykorzystaniu implementacji wykorzystującej dwie strony (chociaż niekoniecznie musi to być rozwiązanie najbardziej efektywne):

protected void Page_Load(object sender, EventArgs e)
{
    //When site is loaded for the first time
    if (!Page.IsPostBack)
    {
        if (Request.QueryString["AcceptCookies"] == null)
        {
            //Create test cookie
            Response.Cookies["TestCookie"].Value = "ok";
            Response.Cookies["TestCookie"].Expires = DateTime.Now.AddMinutes(1);
            Response.Redirect("~/TestCookie.aspx?redirect=" + Server.UrlEncode(Request.Url.ToString()));
        }
        else
        {
            if (Request.QueryString["AcceptCookies"] == "yes")
            {
                ControlsCookies(true);
                ReadDataFromCookie();
            }
            else
            {
                ControlsCookies(false);
            }
        }
    }
    else
    {
        //In PostBack
        System.Diagnostics.Debug.WriteLine("CheckBox in postback is: " + CheckBox1.Checked.ToString());
        Response.Cookies["shoppingApp"].Value = CheckBox1.Checked.ToString();
        Response.Cookies["shoppingApp"].Expires = DateTime.Now.AddDays(1);
    }

    //No matter if this is PostBack or !PostBack
    SiteMaster myPage = Page.Master as SiteMaster;
    myPage.ValidationMenuItem.Enabled = CheckBox1.Checked;
}

Zamieszczony kod jest odrobinę długi, zatem niezbędna będzie chwila wyjaśnienia. Pomysł sprawdzenia czy przeglądarka akceptuje ciasteczka zaczerpnięty został z odpowiedniego materiału źródłowego, który zostanie przytoczony na końcu posta. Po krótce jednak, w przypadku gdy strona jest wczytywana po raz pierwszy, jeśli nie istnieje tymczasowe ciasteczko jest ono tworzone, a następnie następuje przekierowanie do innej strony, przy jednoczesnym przekazaniu adresu strony obecnej, aby można było do niej powrócić. Kod dla strony, na którą następuje przekierowanie wygląda następująco:
 
protected void Page_Load(object sender, EventArgs e)
{
    string redirect = Request.QueryString["redirect"];
    string decision = null;

    if (Request.Cookies["TestCookie"] == null)
    {
        decision = "no";
    }
    else
    {
        //Delete existing cookie
        Response.Cookies["TestCookie"].Expires = DateTime.Now.AddDays(-1);
        decision = "yes";
    }

    Response.Redirect(redirect + "?AcceptCookies=" + decision, true);
}

Na początku adres strony, z której nastąpiło przekierowanie, jest zapisywany. Jeśli przeglądarka akceptujej ciasteczka to będzie ono w tym momencie dostępne. W zależności od tego stanu przy okazji powrotu do strony, z której nastąpiło wywołanie, w query stringu przekazywana jest decyzja o tym, czy ciasteczka są akceptowane. Warto zaznaczyć, że takie sprawdzenie można łatwo oszukać zmieniając po prostu wartość zamieszczoną w query stringu. 

Powróćmy do wcześniejszego kodu. Po wykonaniu wcześniej omówionej czynności, w przypadku gdy nie zostanie stwierdzona akceptacja ciasteczek, zgodnie z ustaleniami, nie ma sensu udostępniać użytkownikowi opcji pozwalających na zmiany. Do określenia dostępnych elementów wykorzystywana jest metoda ControlsCookies():

/// <summary>
/// Sets state of controls depending on decision if browser accepts cookies
/// </summary>
/// <param name="acceptsCookies">If browser accepts cookies - true</param>
protected void ControlsCookies(bool acceptsCookies)
{
    if (acceptsCookies == true)
    {
        CheckBox1.Visible = true;
        Button1.Visible = true;
        Label1.Visible = false;
    }
    else
    {
        CheckBox1.Visible = false;
        Button1.Visible = false;

        //Show label to user - your browser doesn't accept cookies
        Label1.Visible = true;
    }
}

Jeśli przeglądarka akceptuje ciasteczka wykonywany jest blok else, którego zadaniem jest ukrycie przed użytkownikiem CheckBoxa oraz Buttona. Wyświetlana jest natomiast informacja o tym, że przeglądarka nie akceptuje ciasteczek. Ta sama metoda, w przypadku przekazania parametru o wartości true (akceptowanie ciasteczek), analogicznie powoduje wyświetlenie CheckBoxa oraz Buttona umożliwiającego zapisanie zmian, a także wygaszenie informacji dla użytkownika.

W tym kontekście interesujący będzie przypadek, w którym przeglądarka akceptuje ciasteczka. Wykonywana wtedy jest metoda ReadDataFromCookie():

protected void ReadDataFromCookie()
{
    if (Request.Cookies["shoppingApp"] != null)
    {
        try
        {
            CheckBox1.Checked = bool.Parse(Request.Cookies["shoppingApp"].Value);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Exception caught after reading a cookie " + ex.Message);
        }
    }
}

Metoda ta, jeśli stosowne ciasteczko zostało zapisane na dysku, ma za zadanie odczytanie zachowanego stanu CheckBoxa. Informacja ta jest potem wykorzystywana do włączenia/wyłączenia elementu menu o nazwie “Validation” (konieczne było utworzenie wcześniej odpowiedniej właściwości dla obiektu Master Page).

Gdy przeglądarka akceptuje ciasteczka użytkownik ma możliwość dokonania zmian, poprzez zmianę stanu CheckBoxa. Tutaj jednak czeka pułapka na osoby nie mające do tej pory do czynienia z programowaniem webowym. Ze względu na cykl życia strony (Page Life Cycle) http://msdn.microsoft.com/en-us/library/ms178472.aspx, nie można ulec pokusie zrobienia np. czegoś takiego:

protected void Button1_Click(object sender, EventArgs e)
{
    //Changes here won't affect page look            
    Label1.Text = "Button clicked";
}

Wszelkie zmiany dotyczące wyglądu strony muszą być dokonane zanim zostanie ona wyrenderowana, a elementy nie zostaną jeszcze załadowane, czyli najpóźniej w metodzie Load() strony. To samo dotyczy się ciasteczek, w związku z czym wbrew przyzwyczajeniom należy implementację tych elementów zawrzeć w bloku, który zostanie wykonany po submicie wykonanym na Buttonie, czyli w momencie wystąpienia stanu PostBack. Przenieśmy się więc do pierwszego kodu w tym przykładzie. Na podstawie stanu CheckBoxa zapisywana jest tam o nim informacja w ciasteczku. Zapewniam, że taka implementacja działa poprawnie, czyli po odznaczeniu CheckBoxa i naciśnięciu przycisku, użytkownik traci możliwość kliknięcia w element Validation w menu nawigacyjnym.
 

Przedstawione informacje zostały opracowane na podstawie http://msdn.microsoft.com/en-us/library/ms178194(v=VS.90).aspx

P.S. Jak widać szablon WordPressa ślicznie się rozjeżdża 🙂

Reklamy

One Response to ASP.NET Cookies

  1. radzimskik says:

    Dzięki, Ten artykuł mi się bardzo przydał.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s

%d blogerów lubi to: