O SVN słów kilka i news

 

Na początek przytoczę źródło o może niezbyt wysokiej renomie w ostatnim czasie, charakteryzujące się jednak dużą liczbą opisywanych nowinek – http://www.chip.pl/news/wydarzenia/umowy-i-fuzje/2010/09/sojusz-microsoftu-i-wordpressa-oznacza-koniec-dla-windows-live-spaces Jako, że sam posiadam bloga w serwisie Windows Live jest to dla mnie smutna wiadomość, tym bardziej, że gorąco kibicuje całej platformie jaką jest Windows Live. Prawdopodobnie nigdy nie stanie się ona popularna w Polsce, chociaż spotkałem się z opiniami, że komunikator Windows Live Messenger jest o wiele lepszy niż rodzima produkcja. Być może gdyby protokół GG został wsparty, wiele osób zaczęłoby ze wspomnianego komunikatora korzystać. Usługa Windows Live jest w fazie stałego rozwoju, a więc produkt końcowy może według mnie być bardzo dobry. Czemu Microsoft zrezygnował z hostowania blogów? Zapewne było to uzasadnione, czy to kosztami biznesowymi, czy też skupieniem uwagi na innych produktach. Jeśli chodzi natomiast o WordPress.com mam pewne wątpliwości. To prawda, że filtr antyspamowy jest rewelacyjny, możliwośc konfiguracji owszem istnieje. Nie znam innych platform blogowych, więc być może nie doceniam jakości WordPressa. Mam wrażenie jednak, że wszystko opiera się na wbudowanych szablonach, których jest w tym momencie 101, przy czym znaczna część z nich jest po prostu brzydka. Kolejnym problemem jest to, że kolejna połowa z nich nie nadaje się np. do prowadzenia bloga programistycznego. To sprawia, że przy takiej liczbie użytkowników większość blogów będzie wyglądała tak samo, a obserwując zaciętość z jaką np. większość użytkowników Linuxa spędza czas na uczynieniu swojego pulpitu wyjątkowym, domyślam się że powtarzalność szablonów doprowadzi wiele osób do jakiejś psychozy maniakalno-depresyjnej. Z kolejnymi szablonami natomiast odczułem problemy np. z interpretowaniem znaku przejścia do nowej linii wstawianego z Windows Live Writer, tak więc wybór staje się jeszcze mniejszy. Ponadto za możliwość edycji szablonu przy użyciu CSS trzeba niestety dopłacić. Co z tego sojuszu powstanie, czas pokaże 🙂

Druga częśc posta dotyczyć będzie systemu kontroli wersji (SVN). Na własny użytek potrzebuję jakiegoś sposobu dokonywania backupu tworzonych projektów. Puryści będą tutaj protestować, że SVN nie służy do robienia backupu, i że jest to ogromny grzech. Uspokoję, że nie chodzi tutaj o kopiowanie plików o określonym czasie w celu utworzenia kopii. Kopię taką staram się wykonywać w miarę regularnie tak, aby nie płakać w razie utraty ważnych danych. Do tej pory miałem jednak nawyk, po ukończeniu pracy nad jakimś elementem całości projektu, kopiowania całego projektu do wydzielonego na dysku katalogu. Podejście to, chociaż dość proste, jest często spotykane. Niestety jego wartość użytkowa podlega głębokim wątpliwościom. Mój nawyk polegał na tworzeniu katalogów o nazwie “dzień-miesiąc-rok godzina-minuty”. Można sobie wyobrazić w tym momencie masę powstających katalogów z jedynie niewielką adnotacją w postaci 2-3 słów o tym, co zostało zrobione. Co gdy chcemy skopiować gdzieś taki backup? Przenoszone jest kilkadziesiąt razy, zapewne kilkadziesiąt-kilkaset małych plików. Obciążenie czasowe dość pokaźne. Ponadto powstaje drugi problem. Polski sposób notacji daty w wyżej wspomnianej postaci dzień-miesiąc-rok, powoduje w Total Commanderze, którego przywykłem używać, sortowanie po dniu. Trzeba więc bardzo wprawnych oczu, żeby odnaleźć jakiś ciąg chronologiczny w tworzonych katalogach. Rozwiązaniem mogłaby tu być zmiana przyzwyczajenia i nazywanie katalogow w postaci rok-miesiac-dzien itd. Możliwe jest również ustawienie sortowania po dacie, ale…. po prostu nie lubię i nie korzystam póki nie muszę, bo wolę po nazwie. Problemy może niezbyt wielkie, możliwe do przezwyciężenia, ale postanowiłem po chwili refleksji, że będzie to dobry moment na zapoznanie się z jakimś systemem kontroli wersji i ucywilizowanie swojej pracy, a zwłaszcza przygotowanie do przyszłej pracy grupowej nad projektami.

Jako, że na co dzień korzystam z systemu Windows, aczkolwiek gdy mam możliwość nie ograniczam się do oprogramowania typowego dla danej platformy, mój pierwszy wybór padł na połączenie VisualSVN Server + TortoiseSVN. Instalacja, jak to zawsze bywa w przypadku Windowsa, bezproblemowa – klikanie next. Sposób wykonania podstawowych czynności łatwo odnaleźć w Internecie, natomiast jeśli chodzi o dobre praktyki, jakich należy przestrzegać w przypadku pracy z SVN, tu już trudniej, chociaż też się da. Dłuższa chwila zapoznania z TortoiseSVN i kolejne kurtyny tajemnicy opadają. Jako, że do założenia źródeł projektu konkursowego korzystałem z TortoiseHg było już nieco łatwiej. Tutaj pierwsze spostrzeżenie, być może ja nie widzę gdzie to ustawić, być może się nie da, ale w przypadku TortoiseHg podobało mi się to, że graf przedstawiający sposób pracy (branche) widoczny był z poziomu “eksploratora projektu”. W TortoiseSVN tego nie widzę. Tak, oczywiście jest w menu kontekstowym opcja Revision graph, ale to nie jest taka funkcjonalność jakiej bym oczekiwał, a z poziomu logów za nic w świecie nie da się zorientować które commity są do brancha, a które do trunk. Ale póki co poznaje tego klienta, może później znajdzie się jakiś ciekawszy.

Odnośnie pracy z wykorzystaniem tego programu, nie mam doświadczenia, ale mam poważne wątpliwości, czy w przypadku pracy w dużej grupie utrzymanie kontroli nad tym co się dzieje jest możliwe. Zawsze może zdarzyć się, że ktoś zapomni coś dodać, coś źle złączy itd. W takiej sytuacji, w przypadku niedostatecznej znajomości sposobu obsługiwania kontroli wersji mam wrażenie, że można narobić sobie więcej szkód niż pożytku. Cóż, na razie pozostaję przy tym co jest, zapewne w niedalekiej przyszłości przyjdzie czas na skorzystanie z dobrodziejstwa programu MSDN AA i poznanie Team Foundation Server.

Reklamy

ASP.NET Session state

 

Kolejna odsłona z cyklu pod tytułem: co każdy programista ASP.NET musi wiedzieć. Tym razem niezbyt długi post o jednym z mechanizmów zarządzania stanem aplikacji odbywającym się po stronie serwera, czyli o stanie sesji (session state). Poruszone zostaną w nim jedynie podstawowe kwestie, być może w późniejszym czasie do zagadnienia tego powrócę.

Stan sesji w ASP.NET umożliwia przechowywanie wartości specyficznych dla użytkownika w momencie, gdy porusza się on po stronach aplikacji. Pamiętamy o tym, że protokół HTTP jest bezstanowy, przez co wprowadzenie różnych mechanizmów zarządzania sesją jest konieczne, aby można było przechować jakiekolwiek wartości pomiędzy kolejnymi żądaniami. Wartości przechowywane za pomocą zmiennych sesyjnych są dostępne typowo do momentu zamknięcia przeglądarki. Możliwe jest również ustalenie czasu trwania sesji oraz jej wcześniejsze zakończenie.

W wielu miejscach można spotkać się ze stwierdzeniem, że przechowywanie zmiennych za pomocą stanu sesji jest najpowszechniej wykorzystywanym mechanizmem zarządzania stanem. Do czego mogłoby to być wykorzystane w tworzonej przeze mnie aplikacji? Np. do przechowywania danych wprowadzonych do formularza przez użytkownika, które mogłyby być wyświetlone na innej stronie. Załóżmy, dla ustalenia uwagi, że będzie to adres zamieszkania użytkownika.

Zmienne sesyjne na stronie ASP.NET dostępne są za pomocą właściwości Session dla obiektu Page. Zmienne sesyjne nie muszą być deklarowane, a ich tworzenie odbywa się poprzez wykorzystanie indeksu typu integer lub typu string. Drugi ze sposobów, czyli odwołanie poprzez nazwę zmiennej, wygląda następująco:

Session["Address"] = TextBox1.Text;
W tym przypadku zmiennej sesyjnej o nazwie “Address” przypisana została wartość typu string. Warto jednak podkreślić, że zmienne sesyjne mogą być dowolnego typu (przy domyślnym trybie sesji). Można zatem przechowywać obiekty własnego typu. Sprawdźmy to tworząc własną klasę przechowującą dane osoby:
public partial class Person
{
    protected string firstName;
    protected string lastName;

    //Constructors
    ...

    //Class variables exposed by standard properties
    public string FirstName
    {
        ...
    }

    public string LastName
    {
        ...
    }
}
Po stworzeniu odpowiedniego kodu okazuje się, że poniżej przedstawione przypisanie jest jak najbardziej poprawne:
Person p1 = new Person("Walt", "Kowalsky");
Session["person1"] = p1;
Odczytanie tak utworzonych zmiennych wymaga natomiast rzutowania:
if (Session["person1"] != null)
{
    Person p1 = (Person)Session["person1"];
    this.Label3.Text = p1.FirstName + " " + p1.LastName;
}
Sesje identyfikowane są poprzez unikalny identyfikator sesji (SessionID), który jest analizowany w trakcie każdego odwołania. Kłopot jednak w tym, że domyślnie wartości SessionID są przechowywane jako niewygasające w trakcie sesji ciasteczko (cookie). Jeśli zatem przeglądarka blokuje akceptowanie ciasteczek, przekazywanie zmiennych za pomocą tak skonfigurowanej sesji jest niemożliwe. Z pomocą przychodzi tutaj możliwość przechowywania wartości SessionID poprzez adres URL. Zagadnienie bezpieczeństwa takiego rozwiązania pozostawiam do samodzielnego zgłębienia.
 

Sesję można zakończyć wcześniej niż zamknięciem przeglądarki, poprzez wykorzystanie metody Abandon(). Aby mieć jednak pewność, że nie będzie możliwe uzyskanie żadnych danych z poprzedniej sesji należy również wyzerować domyślne ciasteczko dla sesji. Przykładowy proces wylogowania użytkownika z aplikacji może mieć następującą postać:

protected void Button1_Click(object sender, EventArgs e)
{
    Session.Abandon();
    Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));

    //Needed because postback is executed earlier then Abandon
    Response.Redirect(Request.Url.ToString());
}
Jak powiedziałem na początku, przedstawiłem jedynie podstawowe zagadnienia dotyczące stanu sesji, które umożliwiają rozpoczęcie pracy z wykorzystaniem tego mechanizmu. Po nieco więcej informacji zapraszam do materiału źródłowego: http://msdn.microsoft.com/en-us/library/ms178581(v=VS.90).aspx i np. do odrobinę bardziej zaawansowanej literatury http://support.microsoft.com/kb/899918

Rich Hewlett’s WLW WordPress.com plugin

 

O zamieszczaniu kodu źródłowego na blogu WordPressa co prawda już pisałem, ale istnieje ku temu nowa okazja, bowiem powstała nowa wersja używanej przeze mnie wtyczki. Wtyczka jest przeznaczona dla edytora Windows Live Writer. Jej twórcą jest Rich Hewlett, który przy opracowywaniu nowej wersji starał się uwzględnić wszelkie przekazane mu sugestie. Widać zatem, że przekazywane w postaci feedbacku informacje przez niektórych traktowane są na serio. Post ten w ramach ukłonu dla autora będzie pierwszym postem dwujęzycznym na moim blogu. 

O zaletach nowej wersji wtyczki:

  • w dalszym ciągu, tak jak w przypadku poprzedniej wersji, największą jej zaletą jest prostota
  • dodana została możliwość ręcznego robienia wcięć przy użyciu klawisza Tab
  • istnieje możliwość podejrzenia jak będzie wyglądał kod po wstawieniu do szablonu poprzez przejście do zakładki Preview; po powrocie do zakładki Edit kod wyglądą tak jak w zakładce Preview

    Powyżej wymieniłem jedynie najistotniejsze dla mnie zalety, inne funkcje wtyczki zostały wymienione blogu autora.

    Niewielką wadą jest natomiast fakt, że w momencie gdy edytujemy wstawiany kod, po przełączeniu się przy pomocy kombinacji klawiszy alt+tab do innego programu, nie można w taki sam sposób powrócić do WLW. Błąd ten występuje u mnie w systemie Windows 7. 

Najczęściej wykorzystywana przeze mnie jest funkcja wstawiania kodu w języku C#, więc na jej poprawności zależy mi najbardziej. Okazyjnie jednak pojawia się kod w innych językach, stąd w tym poście zobaczymy jak on jest interpretowany przez wtyczkę. Jednocześnie zachęcam do przetestowania wtyczki na własnej skórze i informowanie autora o znalezionych błędach, aby mogła być ona jeszcze lepsza. Adres strony, z której można pobrać wspomnianą wtyczkę to http://richhewlett.com/wlwsourcecodeplugin/

Test dla języka C#:

protected void MyMethod()
{
	int variable = 3;
	string text = "this is text in string";
	string text2 = "this is far too long string. It should be wrapped properly in wordpress blog, at least after publishing";
}

Test dla HTML (XML?):

<p align="center"> To jest html </p>
<b>to jest teks napisany boldem </b>
This is indent
<table valign="middle">
	<tr>this is first row in table</tr>
</table>

Test dla T-SQL:

SELECT * FROM dbo.Addresses;
INSERT INTO dbo.Addresses (City, Street) VALUES ('miasto', 'ulica');

W przypadku pierwszego testu dla języka C# widać, że składnia jest poprawnie kolorowana. Ciekawą funkcjonalnością jest możliwość podświetlenia wybranych linii kodu. Wtyczka umożliwia również, aby zbyt szerowkie wiersze były zawijane, aczkolwiek spodziewam się, że działanie może być dość specyficzne dla różnych szablonów.

W przypadku kodu HTML nie jestem w stanie stwierdzić jednoznacznie jak był zamysł autora w tej kwestii. Przedstawiłem zatem wykorzystanie języka wtyczki XML dla kodu HTML. Cóż, nie tak chciałem aby wyglądała zamieszczona tabela 🙂

Zastanawiające jest natomiast działanie wtyczki dla języka SQL. Na etapie edytowania posta w Windows Live Writer składnia nie jest kolorowana.

EDIT:

Po umieszczeniu postu na blogu okazuje się, że formatowanie HTML wyszło tak, jak tego sobie życzyłem. Podobnie kod T-SQL został sformatowany w sposób poprawny, tak więc wielkie podziękowania dla autora za stworzenie świetnie działającej wtyczki 🙂

———————————————————————————————————————————————————–

I’ve already written about how to insert source code into WordPress’ blog posts. This time, there’s another opportunity to do this, because recently a new version of plugin, which I’ve been using for a while, came into being. The plugin is designed for Windows Live Writer editor. It’s author Rich Hewlett took under consideration many suggestions, which have been sent to him. We can notice than, how big impact can have all informations sent as feedback to the author on a final product. Some people take suggestions very seriously, which should be a standard for many others.

Let’s see some advantages of the plugin’s new version:

  • Still, like the previous version, the biggest advantage is it’s simplicity
  • Now we can do indentation using Tab key
  • There’s possibility to watch how our source code will look like after publishing, by switching to Preview tab. After return to Edit tab code in editing mode will look like in the Preview.
    These are only a few advantages of the plugin, which I’ve been using extensively, however there are many more, which you can of course find in author’s blog.
    However I’ve noticed a small bug too. During editing source code using the plugin, when I switch to another application by pressing alt+tab combination, it’s impossible to back to WLW in the same way. I’ve noticed this kind of behaviour under Windows 7.
    In most cases I’m using C# language to insert code into my blog, so I’m interested the most in this functionality to be efficient. However, sometimes I’m also inserting source code in other languages, so let’s make a simple test how it works. Simultaneously I encourage all users to test the plugin by themselves and send some feedback to the author in order to make this plugin even better. You can download the plugin from http://richhewlett.com/wlwsourcecodeplugin/
    Tests’ summary:
    In the first test, C# language was used. We can see that syntax is properly coloured. Interesting feature is possibility to highlight chosen lines of source code. The plugin allows also to wrap too wide lines, however after my experiences during the time I’ve been using WordPress, I can suppose that this feature can differ in many blog templates.
    In the second test, concerning HTML, I’m not sure if I’m trying to use it in proper way. I’ve chosen XML language in plugin in order to insert HTML code, but it doesn’t seem to work in case of table put in the code.
    In the third test I’m a bit surprised because inserting SQL code doesn’t seem to work at all. At the time I’ve been writing T-SQL code in WLW I can’t really see any syntax formating.
    —————-

Pomimo zauważonych wad, nie sposób nie docenić wkładu pracy autora wtyczki. Po wypróbowaniu kilku sposobów umieszczania kodu na blogu WordPress, stwierdzam, że jest to najlepsza wtyczka z jaką miałem do czynienia i na pewno będę z niej w dalszym ciągu korzystał.

I’ve spotted some small bugs in the plugin, but I appreciate a lot of work which author did in order to create it. After using a couple of similar plugins I can assert that it’s the best plugin I’ve encountered, so I will be using it all the time and I encourage other users to try it.

Sorry for my written english skills but it’s the first time since a couple of years when I’ve been using this language to write something by my own 🙂

————-

EDIT:

After publishing to WordPress blog it turns out the plugin works great! HTML formatting is as I wished for, so do T-SQL code. I render thanks to the author for creating this plugin. Finally everything works just perfect 🙂

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 🙂

ASP.NET Edycja nagłówka w GridView

 

Tytuł posta może być nieco mylący, chciałbym bowiem przedstawić w jaki sposób dodać dodatkowy wiersz do standardowej kontrolki GridView w celu zmiany wyglądu części nagłówkowej. Jeśli jednak mamy już header, to trzymając się ściśle terminologii dodana część powinna być określana jako subheader. Jakkolwiek brzmiałby polski odpowiednik tego słowa, zdecydowałem, że przynajmniej na potrzeby tego posta będę używał zwrotu “nagłówek”.

Jednym z podstawowych zadań aplikacji zezwalającej na interakcję z użytkownikiem jest przedstawianie mu danych. W przypadku ASP.NET zapewne najczęściej będą to dane pochodzące z przygotowanej wcześniej bazy danych. Zanim porwiemy się w wir tworzenia własnych kontrolek, dopasowanych do naszych wymagań, warto zastanowić się, co oferują kontrolki, które już zostały wbudowane przez twórców Visual Studio. Ze względu na możliwości prostego zarządzania danymi z pewnością jedną z częściej wykorzystywanych kontrolek będzie GridView.

Standardowo, poprzez przyjazny interfejs Visual Studio, po wstawieniu kontrolki GridView możemy zażyczyć sobie, aby oferowała ona sortowanie. Domyślne zachowanie jest takie, że po kliknięciu w nazwę kolumny następuje sortowanie względem niej. Po pierwszym kliknięciu jest to sortowanie rosnące, po drugim natomiast malejące. Z powodu mojego widzimisię takie rozwiązanie nie jest zadowalające. Chciałbym mieć możliwość:

  • od razu wybrania sortowania malejącego
  • patrząc na nagłówek widzieć w jakiej kolejności posortowana jest dana kolumna (to już tak z czystej ciekawości w jaki sposób można zrealizować tę funkcjonalność)

Do realizacji wymienionych funkcjonalności niezbędne będą dwa obrazki, pozwalające odróżnić kierunek sortowania. Dla moich potrzeb wybrałem trójkąty obrócone względem siebie o 180 stopni. Prościej mówiąc, z dziubkiem w górę i z dziubkiem w dół.

Do realizacji pierwszej funkcjonalności wykorzystane zostało zdarzenie RowDataBound dla kontrolki GridView. Cechuje je to, że występuje zanim kontrolka zostanie wyrenderowana, co umożliwia przeprowadzenie zmian w jej wyglądzie przed wyświetleniem jej użytkownikowi. W zdarzeniu tym musimy określić, że chodzi o wstawienie wiersza poniżej istniejącego już nagłówka. Z punktu widzenia tego, że być może będzie istniała kiedyś potrzeba wstawienia jakiegoś wiersza rozgraniczającego pewne grupy danych, proponuje dokonywanie zmian na wierszach z danymi, a nie z nagłówkiem. Szkielet takiego rozwiązania wygląda wtedy następująco:

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        if (e.Row.RowIndex == 1)
        {
            ...
        }
        ...
    }
    ...
}

Od tego momentu podawany kod powinien oczywiście znaleźć się wewnątrz drugiej klauzuli if.

ASP.NET oferuje kontrolkę ImageButton, czyli przycisk o wyglądzie obrazka. Zaletą tej kontrolki jest to, że po kliknięciu w obrazek otrzymujemy zachowanie takie, jakie posiada standardowy Button. Obrazki dla odpowiednich przycisków przechowywane są w katalogu Images znajdującym się w solucji. Do katalogu tego obrazki dodawane są po kliknięciu prawym przyciskiem myszy w Solution Explorer na nazwie katalogu i wybraniu opcji Add->Existing Item. Odpowiednie kontrolki mogą być utworzone w następujący sposób:

//Chosen cell to change in subheader
TableCell cell = new TableCell();

//Ascending sort Button
ImageButton imgBtn = new ImageButton();
imgBtn.ID = "NameAscImageButton";
imgBtn.ImageUrl = @"/Images/triangle.jpg";
imgBtn.Width = Unit.Pixel(50);
imgBtn.Click += new ImageClickEventHandler(imgBtn_Click);

//Descending sort Button
ImageButton imgBtn2 = new ImageButton();
imgBtn2.ID = "NameDescImageButton";
imgBtn2.ImageUrl = @"/Images/triangle_desc.jpg";
imgBtn2.Width = Unit.Pixel(50);
imgBtn2.Click += new ImageClickEventHandler(imgBtn2_Click);

//Add controls to cell in asc-desc order
cell.Controls.Add(imgBtn);
cell.Controls.Add(imgBtn2);

W pierwszej kolejności konieczne jest utworzenie komórki wchodzącej w skład przyszłego wiersza, w której znajdą się wybrane elementy. Utworzone przyciski są do niej dodawane w sposób podany w dwóch ostatnich liniach powyższego kodu. Mamy zatem komórkę zawierającą przyciski do sortowania (w tym opisie ograniczam się jedynie do zapewnienia możliwości sortowania jednej, wybranej przeze mnie kolumny). Każdy wiersz z tabeli składa się jednak z większej liczby komórek, z tego powodu pozostałe komórki również powinny być utworzone dla uzyskania jednakowej struktury.
 
//Create row containing cells
GridViewRow row = new GridViewRow(0, 0, DataControlRowType.DataRow, DataControlRowState.Normal);

for (int i = 0; i < e.Row.Cells.Count; i++)
{
  //Column named "Name"
  if (i == 1)
  {
      //Add earlier created TableCell with Buttons
      row.Cells.Add(cell);
  }
  else
  {
      row.Cells.Add(new TableCell());
  }
}

Na zakończenie utworzony wiersz musi zostać dodany w odpowiednim miejscu do już istniejącej tabeli:
 
Table tbl = (Table)e.Row.Parent;
tbl.Rows.AddAt(1, row);

Zmiana w ostatniej linii z wartości 1 na 0 spowoduje umieszczenie utworzonego wiersza powyżej standardowego nagłówka kontrolki GridView.

Czas na zaimplementowanie drugiej funkcjonalności, czyli wyświetlenie w nagłówku sposobu w jaki aktualnie sortowana jest kolumna. Tym razem wykorzystane zostanie zdarzenie RowCreated dla kontrolki GridView. Przede wszystkim potrzebujemy metody umożliwiającej odnalezienie kolumny według której dane są sortowane. Zadanie to jest realizowane poprzez przejrzenie wszystkich pól pośród kolumn znajdujących w GridView. Kolumny w tym kontekście to pola nagłówka. Następnie uzyskujemy indeks aktualnie sortowanej kolumny:

protected int GetSortColumnIndex()
{
    foreach (DataControlField field in GridView1.Columns)
    {
        if (field.SortExpression == GridView1.SortExpression)
        {
            return GridView1.Columns.IndexOf(field);
        }
    }

    return -1;
}

W następnej partii kodu dla wiersza, który jest nagłówkiem, dodajemy dla sortowanej aktualnie kolumny obrazek:
 
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.Header)
    {
        int sortIndexColumn = GetSortColumnIndex();

        if (sortIndexColumn > 0)
        {
            AddSortImage(sortIndexColumn, e.Row);
        }
    }
}

Należy przy tym zauważyć, że z konstrukcji wykorzystywanej w tym przykładzie kontrolki GridView wynika, że obrazek może się znaleźć najwcześniej w kolumnie oznaczonej indeksem równym 1. Metoda dodająca obrazek zaimplementowana może być następująco:
 
protected void AddSortImage(int columnIndex, GridViewRow headerRow)
{
    Image sortImage = new Image();
    sortImage.Width = Unit.Pixel(50);
    sortImage.ImageAlign = ImageAlign.Right;

    if (GridView1.SortDirection == SortDirection.Ascending)
    {
        sortImage.ImageUrl = @"Images\triangle.jpg";
    }
    else
    {
        sortImage.ImageUrl = @"Images\triangle_desc.jpg";
    }
    headerRow.Cells[columnIndex].Controls.Add(sortImage);
}

Efekt końcowy wszystkich działań wyglądą następująco (grafika nie powala 😉 ):

zrzut z ekranu

ASP.NET Walidacja danych

 

Dość sporo wprowadzających postów z zakresu ADO.NET może nieco znużyć osoby, które nie lubią zbytnio zagłębiać się w temat. Dla odmiany zatem, w kolejnym odcinku moich zmagań nad projektem, przedstawiam zagadnienie walidacji danych wprowadzanych przez użytkownika w ASP.NET. Wykorzystane przy tym będą wbudowane mechanizmy, które sprawiają, że walidacja może być prosta i przyjemna, a przynajmniej nie tak straszna jak może się wydawać.

Przeprowadzenie walidacji w ASP.NET możliwe jest zarówno za pomocą kontrolek dostępnych w Visual Studio Web Developer bez nakładu pracy w postaci pisania kodu, a także za pomocą standardowo tworzonego kodu przez programistę. Na potrzeby prezentacji zagadnienia załóżmy, że potrzebujemy dokonać walidacji wprowadzanego przez użytkownika adresu e-mail. Po umieszczeniu kontrolki TextBox w szablonie strony i nadaniu jej odpowiedniego dla potrzeb identyfikatora, można przystąpić do rozmieszczania kontrolek z grupy Validation znajdujących się w Toolboxie. Wypełniając w Internecie różne formularze np. rejestracyjne zauważyć można, że po wprowadzeniu błędnych danych obok pola tekstowego pojawia się np. czerwona gwiazdka wskazująca, że w tym miejscu wystąpił błąd. To zadanie w ASP.NET jest realizowane przez kontrolkę RequiredFieldValidator. Najlepszym miejscem na jej umieszczenie jest sąsiedztwo walidowanego pola. W przypadku wspomnianej kontrolki należy zwrócić uwagę na jej właściwości:

  • ControlToValidate – określa kontrolkę, dla której przeprowadzona ma być walidacja; w tym miejscu należy podać jej identyfikator
  • Display – ustawienie w tym polu wartości Dynamic powoduje, że kontrolka RequiredFieldValidator jest renderowana tylko jeśli istnieje potrzeba wyświetlenia informacji o błędzie
  • Text – napis, który pojawi się w momencie wystąpienia błędu, w miejscu w którym umieszczona została kontrolka RequiredFieldValidator
  • ValidationGroup – umożliwia określenie grupy kontrolek na której przeprowadzana będzie jednoczesna walidacja

W celu przeprowadzenia walidacji adresu e-mail należy dodatkowo umieścić kontrolkę RegularExpressionValidator. Posiada ona również podane powyżej właściwości, które należy tak samo wypełnić. Ponadto w tym przypadku należy zwrócić uwagę na właściwość ValidationExpression. Pozwala ona na wybranie jednego spośród standardowo wbudowanych wyrażeń. W przypadku Visual Studio Express są to w zależności od kraju wyrażenia pozwalające na walidację numeru telefonu, kodu pocztowego, adresu e-mail, URL, numer ubezpieczenia. Możliwe jest również oczywiście zdefiniowanie własnego wyrażenia. Dla potrzeb przykładu wybieramy opcję “Internet e-mail address”, która powoduje ustawienie wyrażenia walidacyjnego na:

\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
Jak widać, szczególnie dla osób nie mających na co dzień do czynienia z wyrażeniami regularnymi jest to znaczne uproszczenie 🙂 Efekt takiego wykorzystania wbudowanych kontrolek jest następujący:
  1. Walidacja następuje w momencie utracenia przez pole tekstowe focusa, czyli po przejściu do innej kontrolki. Nie jest przy tym wykonywane przeładowanie strony – od razu dostajemy informację o błędzie w tym miejscu.
  2. Zasada przy tym jest taka, że jeśli w polu tekstowym nie znalazły się żadne dane, to wyświetlana jest wiadomość ustawiona we właściwości Text kontrolki RequiredFieldValidator, typowo znak *.
  3. Jeśli natomiast w polu tekstowym znalazły się już jakieś dane, lecz nie spełniają one postawionych w wyrażeniu regularnym wymagań to pojawia się informacja ustawiona we właściwości Text kontrolki RegularExpressionValidator.

Właściwość ValidationGroup jest wykorzystywana w momencie, gdy chcemy po wykonaniu walidacji dokonać dalszych operacji, np. po naciśnięciu przycisku. W tym celu kontrolka Button również udostępnia właściwość ValidationGroup. Jak nietrudno się domyślić ustawienie dla niej wartości takiej jak dla opisywanych kontrolek zapewnia dopilnowanie tego, aby wszystkie pola w ramach danej grupy przeszły pozytywnie walidację. Umieszczenie w zdarzeniu Click dla kontrolki Button kodu:

if (Page.IsValid)
{
    //Further operations
    ...
}

sprawia, że kontynuacja nie jest możliwa dopóki wszystkie walidowane pola w ramach grupy nie zostaną wypełnione poprawnie.

 

W różnego rodzaju formularzach można również spotkać prośbę o powtórzenie np. adresu e-mail. Do tego celu, po zaimplementowaniu opisanej funkcjonalności, dodajmy drugie pole tekstowe oraz kontrolkę CustomValidator, która umożliwia stworzenie własnego kodu walidacyjnego. W tym celu wykorzystywane jest zdarzenie ServerValidate. Przykładowy kod realizujący to zadanie może wyglądać następująco:

protected void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
{
    try
    {
        if (TextEmailTextBox.Text == TextEmailTextBox2.Text)
        {
            args.IsValid = true;
        }
        else
        {
            args.IsValid = false;
        }
        
    }
    catch (Exception ex)
    {
        args.IsValid = false;
    }
}

Walidacja taka następuje po naciśnięciu przycisku, z tego powodu należy przypisać właściwości ValidationGroup kontrolki CustomValidator taką samą wartość jaką posiada dany Button.

ADO.NET SqlDataAdapter cz.3 – Primary key

 

W poprzedniej odsłonie pokazałem jak skorzystać z właściwości SqlDataAdapter do pobrania danych z bazy oraz ich wstawienia. Może się zdarzyć jednak tak, że przed dokonaniem aktualizacji danych w bazie chcemy wykonać na nich pewne operacje, jak np. wstawienie wielu wierszy. Jednocześnie miło byłoby zachować kontrolę nad wstawianymi danymi z poziomu aplikacji. Jedną z informacji przydatnych przy tej operacji z pewnością będzie znajomość klucza głównego. W przypadku stworzonej przeze mnie tabeli na potrzeby prezentacji zagadnienia co prawda kolumna ta, zawierająca identyfikator adresu (wartość int) jest automatycznie inkrementowana, ale warto wiedzieć jak poradzić sobie z tą sytuacją jeśli kolumna z kluczem głównym nie posiada takiego atrybutu. Ponadto pokażę jeszcze inną zaletę jaka wynika ze znajomości klucza głównego. Zatem do dzieła.

Przede wszystkim, warto zacząć od tego, że wypełniając zbiór danych DataSet za pomocą metody Fill(), obiekt typu SqlDataAdapter nie przekazuje temu zbiorowi danych prawie żadnych informacji o tabeli, z której pobierane są dane. Z tego punktu widzenia interesujące jest wykonanie takiego kodu:

...

//As before "da" is SqlDataAdapter object
//and "ds" is DataSet object
da.Fill(ds);
da.Fill(ds);
...

Jego wykonanie, np. poprzez dwukrotne naciśnięcie przycisku pobierania danych (jeśli obiekt DataSet jest przechowywany w pamięci), spowoduje pobranie danych z bazy, a następnie ponowne pobranie, które skutkuje dopisaniem na końcu zbioru tych samych danych. Dla przypomnienia w tabeli Addresses założone zostało ograniczenie unikalności, jak zatem łatwo wywnioskować próba zapisania tych samych danych w bazie zakończy się wyrzuceniem wyjątku SqlException. Z pomocą w takiej sytuacji przychodzi właśnie klucz główny. Informacja o tym, że tabela w zbiorze DataSet posiada informację o tym, w której kolumnie znajduje się klucz główny spowoduje, że podczas ponownego pobrania danych obiekt typu SqlDataAdapter dokona jego sprawdzenia i tym samym uniemożliwi ponowne wpisanie powtarzających się danych.

Jedną z metod pozwalającą na pobranie informacji o kluczu głównym jest właściwość MissingSchemaAction dla SqlDataAdapter-a. Jest ona typu wyliczeniowego, przy czym szczególnie interesująca jest wartość AddWithKey:

da.MissingSchemaAction = MissingSchemaAction.AddWithKey;

Dodanie takiej linii do kodu uniemożliwi powtórne pobranie istniejących już danych w DataSet. Ponadto pobierane są w ten sposób jeszcze dwa inne atrybuty: MaxLength oraz AllowDBNull. Co do tego punktu mam jednak dwie uwagi. Po pierwsze w książce “Programming Microsoft ADO.NET 2.0 Core Reference” widnieje adnotacja, że jeśli tabela DataTable w naszym zbiorze jeszcze nie istnieje albo nie zawiera żadnych kolumna to wtedy SqlDataAdapter odpytuje bazę danych o informację o kluczu głównym. Z moich obserwacji wynika jednak, że równie dobrze można wykonać powyższy kod już po wstawieniu danych a przed ponownym pobraniem i informacja o kluczu głównym również zostanie wtedy pobrana i odpowiednio zastosowana. Druga uwaga odnosi się do atrybutu AllowDBNull. W moim zastosowaniu dane wstawiane są do DataSet z poziomu formularza wypełnianego przez użytkownika.
 
row["Street"] = StreetTextBox.Text;

Inaczej jednak niż ja bym tego oczekiwał to, że w polu tekstowym nie znajdują się żadne dane nie oznacza, że posiada ono wartość null. Podane ograniczenie zadziała dopiero w momencie jawnej próby wpisania do odpowiedniego pola w wierszu tabeli wartości null. Po wykonaniu stosownego kodu otrzymujemy wówczas informację:
 
A first chance exception of type ‚System.Data.NoNullAllowedException’ occurred in System.Data.dll

Column ‚Street’ does not allow nulls.
 
Naturalnie można również sprawdzić, że po wykorzystaniu właściwości MissingSchemaAction z wartością AddWithKey nie można wpisać w kolumnie z kluczem głównym powtarzającej się wartości:
 
A first chance exception of type ‚System.Data.ConstraintException’ occurred in System.Data.dll

Column ‚Addr_Id’ is constrained to be unique.  Value ’17’ is already present.
 
Warto jednak zauważyć, że nawet dla domyślnej wartości, czyli Add, atrybut autoincrement dla kolumny dla nowo wstawianych danych jest przestrzegany.

Drugą metodą na pobranie informacji o kluczu głównym jest wykorzystanie metody FillSchema():

//Gets only schema
da.FillSchema(ds, SchemaType.Source);

//Gets data from database
ds.Fill(ds);

Jako drugi parametr przekazywana jest informacja czy mają być zastosowane ustawienia dla kolekcji TableMappings, co nie było jeszcze przeze mnie omawiane, zatem przyjmijmy, że musi być on ustawiony na SchemaType.Source. Metoda ta również ustawia właściwości (atrybuty) AutoIncrement, AllowDBNull oraz MaxLength na zbiorze do którego zwracane są wyniki, a ponadto pobiera informację o kluczu głównym. Samo wywołanie metody FillSchema() nie powoduje pobrania danych, a jedynie informacji o schemacie. Z tego powodu dane należy pobrać osobnym poleceniem, jak pokazane zostało to w przykładzie.