Witryna ASP.NET należy do aplikacji działających w architekturze klient-serwer. Klientem jest przeglądarka, a serwerem – serwer WWW i uruchomiona na nim aplikacja. Do tego dochodzi jeszcze jedna warstwa, która odpowiada za przechowywanie danych. Co ciekawe, baza danych wcale nie musi znajdować się na tym samym komputerze, co serwer WWW.
W poprzedniej części tego minikursu, korzystając ze zintegrowanego środowiska programistycznego Visual Web Developer 2005 Express Edition (VWD), stworzyliśmy witrynę Galeria, która co prawda na razie zawiera tylko jedną stronę, ale za to korzysta ze wzorca (jej kod źródłowy znajduje się na CD). Strona pozwala na przesyłanie na serwer wybranych przez użytkownika zdjęć.
W tej części zbudujemy zaplecze witryny składające
się z bazy danych, w której przechowywać będziemy informacje o przesyłanych zdjęciach. Na ich podstawie będziemy je prezentować, sortować i filtrować.
Wybór bazy danych
Nasza aplikacja musi jakoś przechowywać informacje o przesłanych na serwer zdjęciach – opis, kategorię, ścieżkę do pliku itp. Wybór bazy danych zależy przede wszystkim od przewidywanej ilości tych informacji. W przypadku małych witryn za bazę danych mogą służyć pliki XML, większe wymagają przynajmniej bazy danych Access, natomiast rozwiązaniem optymalnym dla naprawdę dużych witryn jest SQL Server, MySQL, a z produktów komercyjnych np. Oracle.
My wykorzystamy Microsoft Access. Po części dlatego, że to rozsądny wybór dla średniej wielkości witryny, a po części dlatego, że mogę założyć, że wielu czytelników umie się z Accessem obchodzić. Przy tym Windows wyposażony jest w mechanizmy, które pozwalają korzystać z baz danych Accessa bez samej aplikacji Access, więc na komputerze, na którym działać będzie serwer WWW z naszą witryną, instalacja Accessa wcale nie będzie potrzebna.
Jakie dane będziemy przechowywać w bazie danych? W naszym prostym przykładzie będą to tylko: ścieżka do pliku zdjęcia, jego opis, kategoria, a także rozmiary obrazu. Warto jeszcze dodać datę umieszczenia zdjęcia na serwerze i może adres IP komputera, z którego zostało przesłane (np. gdyby pojawiły się wątpliwości dotyczące praw autorskich).
Danych nie ma wiele, więc do ich przechowywania wystarczy tylko jedna tabela z kilkoma polami. Oczywiście zwykle tak nie jest. W bazie przechowywane są zazwyczaj jeszcze dane użytkowników z ich ustawieniami personalizującymi witrynę, dane organizujące strukturę katalogów i wiele innych.
Zacznijmy od utworzenia bazy danych za pomocą aplikacji Access (jeżeli nie masz do niej dostępu, możesz utworzyć bazę na innym komputerze i przenieść tylko jej plik). Bazę nazwijmy Galeria.
mdb i zapiszmy ją do podkatalogu App_Data znajdującego się w katalogu witryny. W niej utwórzmy jedną tabelę o nazwie Pliki z polami widocznymi na rysunku 1.
Połączenie z bazą danych
Spróbujmy teraz połączyć się z bazą danych z poziomu aplikacji. Bardzo miłą własnością witryn ASP.NET, i platformy .NET w ogóle, jest \”naturalne\” traktowanie baz danych należących do Microsoftu, a więc m.in. Access i SQL Server.
Odpowiada za to ADO.NET – zarządzana technologia dostępu do baz danych. Jeżeli VWD znajdzie pliki \”znajomych\” baz danych w katalogach witryny, w szczególności w przeznaczonym na niej podkatalogu App_Data, to automatycznie konfiguruje połączenie z nimi. To już dużo, ale jeszcze nie wszystko. Należy bowiem umieścić na stronach komponenty, które będą reprezentować tabelę z naszej bazy danych.
Przejdźmy zatem do widoku projektowania pliku Upload.aspx i umieśćmy na podglądzie strony komponent {stala}AccessDataSource{/stala}. Po jego wstawieniu na stronę pojawi się tzw. podręczna lista zadań z jedną pozycją {stala}ConfigureDataSource{/stala}…
Kliknijmy na niej, a pojawi się kreator, w którym należy wskazać plik bazy danych, czyli {stala}App_Data/Galeria.mdb{/stala}, wybrać tabelę Pliki i jej pola. Zaznaczmy wszystkie pola, wybierając gwiazkę * (rysunek 2). Skonfigurowany komponent {stala}AccessDataSource{/stala}
będzie reprezentował bazę danych, a dokładniej jej tabelę Pliki, na poziomie aplikacji ASP.NET.
Teraz do metody {stala}Button1_Click{/stala}, którą zdefiniowaliśmy
w poprzedniej części kursu i której zadaniem było przesyłanie wybranego przez internautę zdjęcia na nasz serwer, musimy dodać polecenia zapisujące informacje o nowym pliku do bazy danych. To jednak wymaga przygotowania polecenia SQL.
Trzeba bowiem wiedzieć, że komunikacja aplikacji ASP.NET zarówno z bazami SQL Server, jak i bazami Access odbywa się za pomocą zapytań i poleceń SQL. Ma to tę ważną zaletę, że w przypadku wielkiej popularności naszej witryny migracja do bazy danych SQL Server, MySQL lub Oracle będzie w miarę prosta.
Filtrowanie zdjęć
Używając standardowych klawiszy obsługujących
schowek, tj. Ctrl+C i Ctrl+V, skopiujmy z widoku
projektowania strony {stala}Upload.aspx{/stala} rozwijane menu z listą kategorii i umieśćmy kopię na stronie {stala}Default.aspx{/stala}. Ustawmy własność {stala}AutoPostBack{/stala} kopii na true. Dzięki temu zmiana kategorii będzie wymuszała odświeżenie strony.
Teraz do wywoływanej przy każdym przeładowaniu strony metody {stala}Page_Load{/stala} z pliku {stala}Default.aspx.cs{/stala} dodajmy poniższe polecenia:
protected void Page_Load(object sender,EventArgs e)
{
AccessDataSource1.FilterExpression= \"[Kategoria]=\" + DropDownList1.SelectedIndex;
Repeater1.DataBind();
}
Pierwsze polecenie uzupełnia metodę SELECT zapisaną w komponencie {stala}AccessDataSource{/stala} o część ze słowem kluczowym WHERE.
Select
Instrukcja SELECT pozwala na tworzenie zapytań pobierających dane z bazy danych. Podobnie jak pozostałe polecenia SQL, jego składnia przypomina składnię języka naturalnego. Dla przykładu „wybierz [kolumnę] z [tabeli]” tłumaczy się na:
{stala}SELECT [kolumna1],[kolumna2] FROM [tabela]{/stala}
Nawiasy klamrowe otaczają nazwy kolumn i tabeli, co pozwala na swobodne używanie spacji. W bazie danych, którą stworzyliśmy, tabela ma nazwę Pliki, a przykładowe kolumny – Ścieżka, Opis i Kategoria dlatego zapytanie może mieć postać:
{stala}SELECT [Ścieżka],[Opis],[Kategoria] FROM [Pliki] {/stala}
Jeżeli, tak jak w naszym przykładzie, chcemy pobrać wszystkie kolumny tabeli, zamiast je wymieniać, możemy użyć gwiazdki:
{stala}SELECT * FROM [Pliki]{/stala}
Powyższą instrukcję można skomplikować, dodając warunek filtrujący zawartość pobieranych danych. Pobierzmy tylko te rekordy, w których kategoria równa jest Portrety (odpowiada jej wartość 1):
{stala}SELECT * FROM [Pliki] WHERE [Kategoria]=1 {/stala}
Dane mogą być nie tylko filtrowane,ale również sortowane. W tym celu do zapytania należy dodać klauzulę
ORDER BY z następującą po niej nazwą kolumny, według której dane mają być uporządkowane np.
{stala}SELECT * FROM [Pliki] WHERE [Kategoria]=1 ORDER BY [Data przesłania] {/stala}
Dzięki temu z bazy danych pobierane będą tylko te zdjęcia, których kategoria zgadza się z kategorią wybraną w rozwijanej liście. I tylko te zdjęcia zostaną przekazane do komponentu Repeater. Bardzo ważne jest wywołanie metody {stala}DataBind{/stala} tego ostatniego komponentu, bowiem bez tej zmiany w źródle danych nie będą widoczne na stronie (w ADO.NET połączenie z bazą danych nie jest utrzymywane non stop, jak było w oryginalnym ADO).
\">
\"alt=\"<%# DataBinder.Eval(Container.DataItem,\"Opis\") %>\"border=0 width=300 />
W tej chwili obie nasze strony to \”samotne wyspy\”. Warto zatem dodać do nich łącza, które ułatwią nawigowanie po witrynie. Wykorzystamy do tego wzorzec. Wczytajmy jego plik do edytora i nad komponentem rezerwującym miejsce na strony umieśćmy dwa zwykłe elementy HTML (łącza
widoczne są na rysunku 4). Te same elementy możemy potem skopiować do stopki wzorca. Zwróćmy uwagę, jak edytor pomaga w przygotowywaniu
kodu. Dodane znaczniki wyróżnione zostały pogrubieniem:
W ostatnim, trzecim odcinku naszego minikursu zadbamy o estetyczną stronę naszej witryny. Określimy wygląd stron za pomocą arkuszy stylów, wdrożymy mechanizm częściowej aktualizacji i dodamy kilka AJAX-owych komponentów, które umilą korzystanie z Galerii.
Na CD dodanym do magazynu znajduje się szczegółowa instrukcja instalacji dołączanego do Windows serwera IIS, rejestrowania w nim usługi ASP.NET i tworzenia wirtualnego katalogu dla witryny Galerii.
Tworzenie polecenia SQL Insert
Listing 1 przedstawia zmiany w metodzie {stala}Button1_Click{/stala} z pliku {stala}Upload.aspx.cs{/stala}. Nowe polecenia
umieszczają informacje o przesłanym zdjęciu w bazie danych, wykorzystując do tego komponent {stala}AccessDataSource{/stala} i tworzone osobno na potrzeby każdego nowego rekordu polecenie INSERT.
protected void Button1_Click(object sender, EventArgs e)
{
if (!FileUpload1.HasFile)
{
Label2.Text += \"Brak pliku do przesłania.
\";
return;
}
string data = Math.Abs(DateTime.Now.ToBinary()).ToString();
string adresURL = \"Pliki/\" + data + \"_\" + FileUpload1.FileName;
string sciezkaNaSerwerze = Server.MapPath(\"~/\" + adresURL);
FileUpload1.SaveAs(sciezkaNaSerwerze);
Image1.ToolTip = TextBox1.Text;
Image1.ImageUrl = adresURL;
Image1.Visible = true;
Label1.Visible = true;
Label1.Text = \"Ostatnio przesłany plik (\" + FileUpload1.FileName + \"):\";
System.Drawing.Bitmap obraz = new System.Drawing.Bitmap(sciezkaNaSerwerze);
string wartosci=\"\'\"+adresURL+\"\',\'\"+TextBox1.Text+\"\',\"+DropDownList1.SelectedValue+\",\"+obraz.Width+\",\"+obraz.Height+\",\'\" + DateTime.Now.ToShortDateString()+ \"\',\'\" + Request.UserHostAddress + \"\'\";
AccessDataSource1.InsertCommand = \"INSERT INTO Pliki (Ścieżka,Opis,Kategoria,Szerokość,Wysokość,[Data Przesłania],[Adres IP]) VALUES (\" + wartosci+ \")\";
try
{
AccessDataSource1.Insert();
Label2.Text += \"Dodany plik: \" + TextBox1.Text + \" (\" + FileUpload1.FileName + \"), \" + obraz.Width + \"x\" + obraz.Height + \"
\"; //komunikat o powodzeniu
TextBox1.Text = \"\";
}
catch (Exception exc)
{
Label2.Text+=\"Plik \"+FileUpload1.FileName+\" nie mógł być dodany do Galerii. Powód: \"+exc.Message+\"
\";
System.IO.File.Delete(sciezkaNaSerwerze);
}
}
Pierwsza zmiana to dodanie nowej zmiennej typu Bitmap. Jest to klasa pozwalająca na wczytywanie i badanie wielu typów rastrowych plików graficznych. Z jej instancji odczytamy rozmiar obrazu zapisywany do bazy danych. Następnie definiujemy łańcuch, który w odpowiedniej kolejności po przecinku wymienia wartości, jakie mają być umieszczone w rekordzie dodawanym do bazy danych. Dane typu tekstowego otaczane są pojedynczymi cudzysłowami.
Korzystając z listy własności, tworzymy polecenie SQL INSERT, a po jego przygotowaniu wywołujemy metodę Insert komponentu {stala}AccesDataSource1{/stala}. Jej skutkiem powinno być dodanie nowego rekordu do bazy danych.
Insert
Instrukcja INSERT pozwala na dodanie do tabeli nowego rekordu z danymi. Oto jego podstawowa składnia w T-SQL:
{stala}INSERT INTO tabela ([kolumna1],[kolumna2]) VALUES (wartość1,wartość2){/stala}
Odpowiada to prostej składni języka naturalnego: wstaw do kolumn tabeli (kolumna1,kolumna2) wartości:
wartość1, wartość2. W naszym przykładzie instrukcja INSERT może wyglądać następująco:
{stala}INSERT INTO Pliki ([Ścieżka],[Kategoria]) VALUES (\’~/Pliki/dzieciaki.jpg\’,1){/stala}
Zwróćmy uwagę, że łańcuchy wyróżniane są użyciem pojedynczych cudzysłowów.
Wystarczy jednak, że jedna z wartości będzie pustym łańcuchem (tabela skonfigurowana jest tak,że wszystkie pola są wymagane) i pojawi się błąd. Zmniejszamy jego wpływ, korzystając z konstrukcji try..catch. Dzięki niej informacje o błędzie przekazywane są do sekcji catch, gdzie informujemy o nich użytkownika, formułując zgrabny komunikat.
W razie niepowodzenia dodania rekordu do bazy danych usuwamy przesłany plik, aby nie zaśmiecać serwera.
Walidacja
Pole Opis jest wymagane w bazie danych. To oznacza, że próba dodania do tabeli rekordu z pustym łańcuchem w tym polu skończy się wyjątkiem. Dzięki obsłudze wyjątków w metodzie z listingu 1 cała afera skończy się tylko komunikatem na stronie, ale zwróć uwagę na to, że kosztować to będzie całkiem sporo czasu.
W końcu metoda wykonywana jest na serwerze, co wymaga wysłania żądania, reakcji aplikacji i odesłania kodu HTML z nową wersją strony, która różni się jedynie dodanym komunikatem o błędzie.
Na szczęście istnieje lepszy sposób, żeby sprawdzić poprawność umieszczonych w formularzu
danych jeszcze przed wysłaniem ich na serwer. Służy do tego rodzina komponentów z zakładki Validation.
Przejdźmy do widoku projektowania strony {stala}Upload.aspx{/stala}. Obok komponentu {stala}TextBox1{/stala}, w którym
użytkownik witryny ma wpisać opis zdjęcia, umieśćmy komponent {stala}RequiredFieldValidator{/stala} z zakładki Validation podokna Toolbox. Jego własność {stala}ErrorMessage{/stala} ustawmy np. na \”Brak opisu zdjęcia\”, a we własności {stala}ControlToValidate{/stala} wskażmy pole edycyjne TextBox1. Teraz spróbujmy wysłać zdjęcie bez opisu. Walidator wyświetli komunikat o braku opisu bez przeładowania strony.
Jak to działa? Do kodu HTML przesyłanego do przeglądarki dodany został kod JavaScript (możemy go przeanalizować, oglądając źródło strony w przeglądarce), który weryfikuje dane z formularza. Skrypty dodane na skutek użycia powyższego walidatora sprawdzają następnie, czy zawartość pole edycyjnego nie jest pusta.
Przeglądanie zdjęć
Możemy już wysyłać zdjęcia. Teraz przydałoby się móc je także oglądać. Najprostszym rozwiązaniem byłoby skierowanie przeglądarki do katalogu Pliki. Ale to nie wyglądałoby elegancko. Dlatego musimy zrobić coś więcej. Dodajmy do projektu witryny jeszcze jedną stronę.
W tym celu stwórzmy plik typu Web Form o nazwie {stala}Default.aspx{/stala} korzystający ze wzorca Galeria.master. W ramach \”powtórzenia materiału\” proponuję, abyś sam skonfigurował
teraz komponent {stala}AccessDataSource{/stala} w taki sposób, aby ten reprezentował tabelę Pliki z bazy danych {stala}Galeria.mdb{/stala}. Warto tym razem dodać sortowanie, które uporządkuje zdjęcia na przykład zgodnie z datą ich przesłania na serwer.
Do pokazania zawartości tabeli można użyć komponentu GridView – wystarczy tylko wskazać mu źródło danych (proponuję to przećwiczyć samodzielnie), ale my użyjemy komponentu Repeater, który daje programiście większą kontrolę nad prezentowanymi danymi, chociaż wymaga także więcej wysiłku przy konfiguracji. Zasada jego działania jest prosta: komponent ten na podstawie zdefiniowanego przez nas szablonu powtarza fragment kodu HTML dla każdego rekordu w bazie danych, wypełniając go danymi z tego rekordu.
Umieśćmy na stronie egzemplarz tego komponentu. Następnie przejdźmy do edytora kodu i wstawmy tam szablon otoczony znacznikami {html}
Szablon składa się z łącza (element A), w którym jest obraz pokazujący zdjęcie o szerokości 300 pikseli (element IMG).
Łącze wskazuje na to samo zdjęcie (warto rozważyć tworzenie miniatur przesyłanych na serwer zdjęć i wykorzystanie ich przy przeglądaniu galerii, ich brak to jedno z naszych uproszczeń), które jest widoczne w elemencie IMG, więc klikając, można je obejrzeć w pełnym rozmiarze. Opis zdjęcia widoczny będzie w tzw. okienku podpowiedzi.