70-562: Using Caching to Improve Performance

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Często buforujemy dane na stronie bądź cała stronę co pozwala na szybszy dostęp do informacji niż z pliku czy bazy danych. Poprawia to oczywiście wydajność i skalowalność jeśli chodzi o liczbę użytkowników obsługiwanych na WWW. Nasz wspaniałomyślny ASP.NET bez większego nakładu pracy (czyt. pisania kodu) pozwala obsłużyć pamięć podręczną.  Wyróżniamy dwa rodzaje takiej pamięci:

Application caching- kolekcja ta może przechowywać dowolny obiekt, automatycznie zarządza pamięcią, limitami czasu przechowywania obiektu oraz innymi zależnościami.

Page output caching- pozwala przechowywać stronę, jej część lub wersje w pamięci co pozwala skrócić czas dostępu do niej.

Application Caching

Jest to proces przechowywania danych w którym mamy dostępny obiekt Cache który jest właściwością obiektu Page. Stanowi on zbiór klasy typu System.Web.Caching.Cache. Obiekt ten wykorzystuje całą pamięć podręczną co oznacza, że jest on jeden na cała aplikacje a nie na stronę. Poniższy rysunek pokazuje nam ten obiekt:

1

Praca z obiektem Cache jest bardzo podobna do pracy z sesją. Można przypisać element bezpośrednio nadając mu klucz i wartość. Przy pobieraniu wartość z pamięci podręcznej pamiętajmy aby sprawdzić czy nie jest ona null (mógł np. upłynąć czas przechowywania wartości). Poniższy przykład demonstruje jak pobrać obiekty string z pamięci:

   1: //C#
   2: Cache["Greeting"] = "Hello, world!";
   3:     if (Cache["Greeting"] != null)
   4:         value = (string)Cache["Greeting"];
   5:     else
   6:         value = "Hello, world!";

Oczywiście to jest najprostszy sposób i wydaje się mało rzeczywisty ale równie dobrze możemy przechowywać tam pliki, wyniki zapytań czy własne obiekty. Musimy tylko pamiętać aby zrzutować pobieraną wartość na odpowiedni typ.  W TK są opisane właściwości którymi możemy rozszerzyć o pewne właściwości nasze przechowywane dane i przykłady. Ja chciałbym przytoczyć jeden w którym jest ustawiany czas “trzymania” wartości ;)

   1: //C#
   2: Cache.Insert("FileCache", "CacheContents", null, DateTime.Now.AddMinutes(10),
   3: Cache.NoSlidingExpiration);

Page Output Caching

Często jest tak, że przeglądarka pobiera stronę, zapisuje ją na dysku i przy żądaniu sprawdza czy jest nowa wersja i jeżeli nie to ją wczytuje. Taki rozwiązanie m.in powoduje mniejsze obciążenie serwera. Aby zwiększyć wydajność i zmniejszyć czas renderowania, ASP.NET obsługuje tytułowy page output caching. Powoduje to, że np. serwer może trzymać w swojej pamięci zażądaną stronę i nawet kiedy inny użytkownik będzie chciał ją wczytać otrzyma ją w bardzo szybkim tempie. Jest to przydatne kiedy dana strona jest “ciężka”. Nie ma również problemu ze stornami tworzonymi dynamicznie wg. indywidualnych potrzeb użytkownika.

Możemy każdej ze stron ustawić buforowanie. Robimy to dodając do dyrektywy @ OutputCach. W TK są właściwości jakie możemy ustawić czyli np. czas, lokalizacje przechowywania oraz których nie możemy używać do kontrolek. Poniższy przykład pokazuje, jak ustawić “cachowanie” strony przez 15 minut, niezależnie od parametry przekazane do strony:

   1: <%@ OutputCache Duration="15" VaryByParam="location;count" %>

Nic nie stoi na przeszkodzie aby ustawić buforowanie dla całej aplikacji. Robimy sobie profile które potem możemy użyć na poszczególnych stronach. Oczywiście musimy umieścić odpowiednią sekcje w naszym web.config :

   1: <caching>
   2:     <outputCacheSettings>
   3:         <outputCacheProfiles>
   4:             <add name="OneMinuteProfile" enabled="true" duration="60"/>
   5:         </outputCacheProfiles>
   6:     </outputCacheSettings>
   7: </caching>

I aby nasza strona stosowała się do tego musimy przypisać jej ten profil:

   1: <%@ OutputCache CacheProfile="OneMinuteProfile" VaryByParam="none" %>

Na tym zakończę ten wpis. Wybaczcie, że jest on taki ogólnikowy ale jestem po nocnej podróży z Imagine Cup ;) W TK możecie doczytać sobie różne właściwości i np. jak sprawdzić czy skorzystać z zapisanej lokalnie strony czy nie ;) Jest to również ostatni wpis z serii przygotowań do egzaminu 70-562 ASP.NET. Wszystkim wiernym czytelnikom dziękuje w imieniu chłopaków jak i swoim :) Pamiętajcie, że artykuły nakreślają tematy zawarte w TK i mają tylko pomóc w zrozumieniu pewnych zagadnień a nie są kompendium wiedzy dotyczącej samego egzaminu i jego zawartości. Trzymajcie się, miłego weekendu! :D

Tagi: , , , ,

70-562: Securing Your Site

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Do tej pory powiedzieliśmy sobie o profilach użytkowników, narzędziu WSAT, kontrolce Login czy też o podstawach membershipa. ASP.NET wspiera co najmniej 4 rodzaje uwierzytelniania są to:

  • Windows authentication
  • Forms authentication (which ASP.NET membership uses)
  • Passport authentication
  • Anonymous access

Dzisiaj porozmawiamy w sposób ogólny jak można wykorzystać każdy z tych sposobów.

Konfiguracja aplikacji webowej która wymaga uwierzytelnienia poprzez Windows authentication

Jeśli aplikacja będzie wykorzystywana typowo wewnątrz firmy w której użytkownicy mają swoją konta które są w bazie lub jest cały Active Direcotry to wskazane jest korzystanie właśnie z Windows authentication. Możesz skonfigurować uwierzytelnianie Windows na dwa sposoby: w IIS oraz w aplikacji ASP.NET. Jeśli chcemy się ekstra zabezpieczyć to korzystamy z dwóch na raz. Kiedy aplikacja internetowa wymaga uwierzytelniania systemu Windows, aplikacja odrzuca wniosek, który nie zawiera prawidłowej nazwy użytkownika i hasła w nagłówku żądania. Niektóre przeglądarki np. IE ;) automatycznie dostarczają aktualną nazwę oraz hasło użytkownika który jest obecnie w intranecie. Nie trzeba chyba mówić, że to usprawnia prace i pozwala się płynnie uwierzytelniać użytkownikowi podczas każdych odwiedzin.

Dodatkowo, ponieważ użytkownicy są uwierzytelniani przez serwer w lokalnej bazie danych użytkownika lub
Domeny Active Directory, przy użyciu uwierzytelniania systemu Windows pozwala to uniknąć tworzenia bazy danych do przechowywania poświadczenia użytkownika. Także można stwierdzić, że Windows authentication jest najprostszym ze sposobów uwierzytelniania.

Aby dodać w prosty sposób Windows authentication do naszej aplikacji webowe w pliku web.config musimy umieścić sekcje <authentication> :

   1: <configuration>
   2: <system.web>
   3:     <authentication mode="Windows" />
   4:         <authorization>
   5:                 <deny users="?" />
   6:        </authorization>
   7: </system.web>
   8: </configuration>

 

Jeśli mamy jakiś formularz w który mielibyśmy możliwość wpisania loginu i hasła. Powiedzmy, że zrobiliśmy taki formularz i jest on na stornie Login.aspx. Musimy tam odesłać każdego niezalogowanego użytkownika ze storn do których nie ma dostępu. Aby to zrobić umieszczamy sekcje w web.config:

   1: <configuration>
   2:     <system.web>
   3:             <authentication mode="Forms">
   4:                 <forms loginURL="Login.aspx" />
   5:             </authentication>
   6:            <authorization>
   7:                <deny users="?" />
   8:            </authentication>
   9:     </system.web>
  10: </configuration>

Na stornie Login.aspx oczywiście musimy sprawdzić poprawność wprowadzonych danych. Aby to zrobić wystarczy prosty kod:

   1: if (FormsAuthentication.Authenticate(username.Text,
   2: password.Text))
   3: {
   4: //user is authenticated. Redirect user to the page requested.
   5: FormsAuthentication.RedirectFromLoginPage(usernameTextBox.Text, false);
   6: }

False oznacza, że dane nie będą pamiętane w ciasteczku stąd po wyłączeniu przeglądarki na nowo będziemy musieli się zalogować.

Jest możliwość skonfigurowania konta użytkownika wewnątrz web.config jeśli nie chcemy trzymać tych danych np. w bazie. Są trzy możliwości przechowywania hasła takiego użytkownika: jawny tekst, MD5 lub SHA1. Pozwala to uniknąć kradzieży konta przez użytkownika który ma dostęp do naszego pliku web.config. Spójrzmy jak przykładowo wygląda to właśnie w pliku web.config :

   1: <authentication mode="Forms">
   2:     <forms loginUrl="login.aspx" protection="Encryption" timeout="30" >
   3:         <credentials passwordFormat="SHA1" >
   4:             <user name="Eric" password="07B7F3EE06F278DB966BE960E7CBBD103DF30CA6"/>
   5:             <user name="Sam" password="5753A498F025464D72E088A9D5D6E872592D5F91"/>
   6:         </credentials>
   7:     </forms>
   8: </authentication>

Jednak aby pozwolić nam na korzystanie z takich zakodowanych haseł musimy mieć możliwość ich stworzenia. Przydałaby się program który by kodował hasło które później moglibyśmy używać w web.configu . Do tego celu możemy wykorzystać przestrzeń nazw System.Security.Cryptography. Spójrzmy na przykładowy kod generowania hash’a.

   1: namespace HashExample
   2: {
   3: class Program
   4: {
   5: static void Main(string[] args)
   6: {
   7: SHA1CryptoServiceProvider myHash=new SHA1CryptoServiceProvider();
   8: byte[] password = Encoding.ASCII.GetBytes(args[0]);
   9: myHash.ComputeHash(password);
  10: foreach (byte thisByte in myHash.Hash)
  11: Console.Write(thisByte.ToString("X2"));
  12: Console.WriteLine();
  13: }
  14: }
  15: }

 

Uwierzytelnianie za pomocą Paszportów

Można korzystać z usługi paszportów oferowaną przez Microsoft. Paszport pozwala odpłatnie korzystać z zcentralizowanej bazy danych. Przechowywanie danych o użytkownikach w ramach tej usługi uwalnia ich od tworzenia nowych profili, pamiętania kolejnego hasła do kolejnej witryny itd. Jest to również zaoszczędzenie czasu i co tu ukrywać przyjemne dla naszego odbiorcy.

Konfigurowanie aplikacji dla anonimowych użytkowników

Możemy wyraźniej wyłączyć autoryzowanie użytkowników w naszej aplikacji. Jednak zaleca się aby wtedy korzystać z IIS. Aby to zrobić wystarczy następująca sekcja w web.config:

   1: <configuration>
   2:     <system.web>
   3:         <authentication mode="None" />
   4:     </system.web>
   5: </configuration>

Możemy ograniczyć dostęp do poszczególnych folderów, plików naszej aplikacji na podstawie np. grupy czy konkretnych użytkowników. Domyślnie machine.config zawiera:

   1: <authorization>
   2: <allow users="*"/>
   3: </authorization>

Sekcja ta pozwala na dostęp wszystkim użytkownikom. Jeśli chcielibyśmy ograniczyć dostęp do poszczególnych użytkowników należałoby zastosować tą przykładowa sekcje:

   1: <authorization>
   2:     <allow users="Eric, Sam"/>
   3:     <deny users="*"/>
   4: </authorization>

 

Sekcja ta jednak ogranicza nam dostęp dla całej aplikacji. Jeśli chcielibyśmy użyć ograniczeń do konkretnej strony wyglądałoby to następująco:

   1: <location path="ListUsers.aspx">
   2:     <system.web>
   3:         <authentication mode="forms">
   4:             <forms loginUrl="AdminLogin.aspx" protection="All"/>
   5:         </authentication>
   6: <authorization>
   7:     <allow users="admin"/>
   8:         <deny users="*"/>
   9: </authorization>
  10: </system.web>
  11: </location>

 

To tyle na dzisiaj. Miłej zabawy na Kortowiadzie ;)

Tagi: , , ,

70-562: Configuring Accessibility

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Czy tworzymy stronę dla milionów użytkowników, czy dla setek musimy mieć świadomość różnych odbiorców. Mówiąc różnych w dzisiejszym artykule mam na myśli użytkowników np. z niestandardowymi urządzeniami wejściowymi, potrzebującymi większej czcionki itp. Bo trzeba mieć świadomość, że wielu użytkowników nie korzysta z tradycyjnych myszek a i nie każdy użytkownik wyświetla stronę internetową na zwykłym monitorze.

Jak kontrolki ASP.NET wspierają dostępność

Kontrolki w ASP.NET są zaprojektowane tak aby w domyśle być “dostępnymi”. Dla przykładu takie kontrolki jak Login, ChangePassword, PasswordRecovery, i CreateUserWizard używają pól tekstowych w powiązaniu z etykietami które mają pomóc użytkownikom korzystająca z jakiś czytników ekranu czy nie używających myszki.

Innym sposobem umożliwienia większej dostępności są tkz. SkipLinkText. Zazwyczaj mając duży monitor możemy sobie w wygodny sposób czytać powiedzmy jakiś artykuł. SkipLinkText pozwala przeskoczyć do danego miejsca w artykule które odpowiednio oznaczymy. Kontrolki CreateUserWizard, Menu, SiteMapPath, TreeView, czy Wizard mocno to wspierają. Warto zauważyć, że linki te nie będą dostępne dla użytkowników korzystających ze standardowych przeglądarek. Na przykład, następujący kod źródłowy HTML (który został nieco uproszczony) jest domyślnie generowany podczas dodawania Menu do strony sieci Web:

   1: <a href="#Menu1_SkipLink">
   2: <img alt="Skip Navigation Links" src="/WebResource.axd?d=_9Q2Lm" width="0" height="0"/></a>
   3: … menu links …
   4: <a id="Menu1_SkipLink"></a>

Poprawa wizualnej dostępności

Kilka przydatnych uwag:

  • Zawsze opisuj obrazek we właściwości AlternateText. Jest to ważne ponieważ musimy założyć, że użytkownik może mieć wyłączone wczytywanie obrazków bądź z innego powodu nie może ich wyświetlić. Również użytkownikiem może być osoba niewidoma, wtedy syntezator mowy może odczytać “alt” z naszego obrazka. Jeśli obraz w ogóle nie jest ważny (powiedzmy stanowi obramowanie naszej strony) możemy ustawić właściwość GenerateEmpty-AlternateText na True co spowoduje zignorowanie go przez czytnik ekranu.
  • Stosuj jednolite kolory tła i kontrastujące z nim kolory tekstu. Tutaj chyba nie trzeba tłumacz, że każdy z nas lubi dobrze dobraną kolorystykę, kontrast aby móc komfortowo czytać tekst.
  • Stwórz elastyczny layout który skaluje się odpowiednio wraz ze wzrostem czcionki. Przeglądarki oferują nam zwiększenie rozmiaru czcionki dla użytkowników którzy tego potrzebują dlatego musimy o tym pamiętać.
  • Ustaw właściwość Table.Caption do opisu w tabeli. W tej właściwości powinien znaleźć się opis danych zawartych w tabeli. Pozwoli to określić użytkownikowi czy chce je zobaczyć czy przejść dalej.
  • Unikaj szczegółowego określania czcionek. Do rozmiarów czcionek używaj tagów np. (<H1> lub <H3>).
  • Nie wymagaj używania skryptów klienckich. Używaj skryptów tylko do niezbędnych czynności tak aby Twoja storna była funkcjonalna również bez nich.

Zwiększenie dostępności dla form do wprowadzania danych

Wielu użytkowników najchętniej posługiwało by się samo klawiaturą. Myszkę używają w krytycznych momentach. Warto i o nich pomyśleć tworząc naszą witrynę. W Windows Forms popularne są skróty klawiaturowe, tutaj może być ciężej ;)

  • Umieść właściwość DefaultFocus w formularzu aby umieść kursor w miejscu logicznym od którego zaczyna się wprowadzanie danych.
  • Definiuj “tab order” w logiczny sposób aby użytkownik mógł przechodzić w logiczny sposób po formularzu wypełniając dane bez użycia myszki.
  • Określ przez właściwość DefaultButton domyślne przyciski dla formularza. Jest on dostępny po wciśnięciu klawisza Enter. Jest to wygodne i przyśpiesza znacząco czas.
  • Określ właściwości AccessKey. Po wciśnięciu alt + określonych znaków  będzie można uzyskać dostęp do kontrolki.
  • Określ istotne komunikaty o błędach w tekście i właściwości ErrorMessage z walidatora
    kontroli.
  • Używaj etykiet do definiowania AccessKey dla pól tekstowych. TextBox nie ma właściwości która by go opisywała i byłaby łatwo dostępna dla czytników ekranu. Dlatego należy je powiązać z etykietami co realizuje poniższy kod:
   1: <asp:Label
   2: AccessKey="N"
   3: AssociatedControlID="TextBox1"
   4: ID="Label1"
   5: runat="server"
   6: Text="&lt;u>N</u>ame:">
   7: </asp:Label>
   8: &nbsp;
   9: <asp:TextBox ID="TextBox1" runat="server" />

To tyle na dzisiaj. Miłego i słonecznego weekendu życzę ;)

Tagi: , , , ,

70-562 Debugging an ASP.NET Application

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Debugowanie witryny sieci Web może być trudnym procesem ze względu choćby na to, że zazwyczaj klient i serwer są na różnych maszynach. Ponad to może być również rozdzielona baza danych, dane w sesji, ciasteczkach itd. No i po tak strasznym wstępie można w końcu powiedzieć, że debugowanie dla Visual Studio a raczej narzędzi które udostępnia nie jest jakimś większym problem ;)

Konfiguracja debugowania w ASP.NET

Standardowo w Visual Studio do debugowania możemy użyć breakpoints, watch window, error information itd. Najpierw jednak wypadałoby skonfigurować nasze środowisko do tego procesu. Istnieją dwa miejsca gdzie możemy to zrobić: właściwości projektu strony lub web.config. 

Aktywacja debugera ASP.NET

Pierwszym krokiem jest umożliwienie debugowania w ASP.NET we właściwościach strony. Jest to domyślnie włączone ale może się zdarzyć, że z jakiś przyczyn np. chcielibyśmy to zmienić. Aby to zrobić musimy wykonać następujące czynności:

  1. Kliknij PPM na projekt strony w Solution Explorer
  2. Wybieramy Property Pages
  3. Następnie z listy wybieramy Start Option
  4. I widzimy dostępne okno (jak poniżej) w którym możemy np. wyłączyć debugowanie dla ASP.NET

111

Konfiguracja debugera

Drugim krokiem może być ograniczanie debugowania np. tylko dla poszczególnych stron. Domyślnie jest to wyłączone. Włączając debugowanie oczywiście tracimy na wydajności strony. Ponad to podczas wystąpienia błędy dostaniemy o nim pełna informacje poza środowiskiem Visual Studio co może być niebezpieczne i potencjalnie wykorzystane. Dlatego debugowanie możemy sobie włączyć na poszczególnych stornach ale jest to zalecane tylko w czasie tworzenia i rozwoju projektu. Aby włączyć debugowanie dla całej witryny należy umieścić w web.configu następującą sekcje:

   1: <configuration>
   2:     <system.web>
   3:         <compilation debug="true">
   4:         </compilation>
   5:     <system.web>
   6: </configuration>

 

Ale czasami jest chcielibyśmy ograniczyć debugowanie indywidualnie dla poszczególnych stron. W tym wypadku musimy umieścić następujący kod w dyrektywie @Page

   1: <%@ Page Debug="true" ... %>

 

Definiowanie własnych błędów

Jest wielce prawdopodobne, że przy wysypaniu się na czymś naszej strony chcielibyśmy pokazać jakiś swój własny komunikat niż ten domyślnie ustawiony w środowisku. Również gdzie ewentualnie można znaleźć pomoc itd.

Konfiguracja niestandardowej strony błędu na poziomie witryny

Możemy ustawić naszą niestandardową stronę błędu umieszczając w web.congifu sekcję <customErrors>. Element ten ma dwa atrybuty mode i defaultRedirect

   1: <configuration>
   2:     <system.web>
   3:             <customErrors defaultRedirect="SiteErrorPage.aspx" mode="RemoteOnly">
   4:             </customErrors>
   5:     <system.web>
   6: </configuration>

 

mode=”RemoteOnly” oznacza, włączenie custom error tylko dla zdalnych klientów.  Atrybut DefaultRedirect jest używany do wskazania ścieżki do domyślnej strony błędu.

Konfiguracja strony błędu dla specyficznego błędu

Możliwe jest ustawienie różnych storn dla specyficznego rodzaju kodu błędu. Zapewnia to większą komunikacje z użytkownikiem. Np. użytkownik dostaje kod 403 i my przekierowujemy go na stronę gdzie jest ładnie i zgrabnie wyjaśnione użytkownikowi, że np. nie ma dostępu do danego zasobu bądź strony. Chcąc to zrobić musimy umieść w web.configu następującą sekcje:

   1: <configuration>
   2:     <system.web>
   3:         <customErrors defaultRedirect="SiteErrorPage.aspx" mode="RemoteOnly">
   4:             <error statusCode="403" redirect="RestrictedAccess.aspx" />
   5:         </customErrors>
   6:     <system.web>
   7: </configuration>

 

Zdalne debugowanie

Nie zawsze jest tak, że chcemy debugować kod na lokalnej stacji itd. Czasami może zaistnieć potrzeba zrobienia tego zdalnie. Proces uruchomiania zdalnego debugowania ułatwia nam Remote Debugging Monitor (Msvsmon.exe). Musimy uruchomić to narzędzie jeśli mamy zamiar debugować. U nas na dysku mając domyślnie zainstalowanego VS możemy szukać tego narzędzia w:

Program Files\Microsoft Visual Studio9.0\Common7\IDE\Remote Debugger\x64

Po włączeniu go mamy cały interfejs który m.in informuje nas o zdarzeniach podczas debugowania.

222

 

 

W TK jest opisane o autoryzacji takiego zdalnego debugowania, i jego konfiguracji. Odsyłam do tego chętnych.

Debugowanie skryptów po stronie klienta

Na koniec chciałbym jeszcze wspomnieć o tym, że jest możliwe debugowanie skryptów. Jest to przydatne jeżeli dużo piszemy w javascript. Na początek musimy włączyć w przeglądarce możliwość debugowania. W tym celu odpalamy przeglądarkę IE, wybieramy Narzędzia--->Opcje internetowe---->Zaawansowane i tam musimy “odhaczyć” wyłącz debugowanie skryptu.

333

I teraz możemy normalnie korzystać z debugowania w skryptach.

To tyle na dzisiaj. Jak co tydzień życzę Wam miłego weekendu i zapraszam w poniedziałek ;)

Tagi: , , , ,

70-562: Working with Custom Web Server Controls

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Tworzenie własnej Web Server Control

Niestandardowa kontrolka serwerowa może dziedziczyć z kontrolki WebServer. Musi zawierać kod który pozwoli ją renderować bądź może podziedziczyć to z innej kontrolki. Zazwyczaj są dwa podejścia do tworzenia takich kontrolek. Pierwsze z nich to podziedziczyć po WebControl która da nam podstawowy zestaw funkcjonalności. Obejmuje to również takie właściwości jak np. BackColor, ForeColor, Font, Height, i Width. Drugi częściej stosowany sposób to podziedziczyć po istniejącej już kontrolce i rozwijać jej funkcjonalność. Niezależnie od podejścia należy wziąć pod uwagę wielokrotne/ponowne użycie kontrolki. Jeśli nasza kontrolka będzie wykorzystywana w wielu witrynach należałoby ją utworzyć jako osobną .dll .

Dziedziczenie z istniejącej kontrolki

Jak przed chwilą wspomniałem typowym sposobem na tworzenie własnej kontrolki jest podziedziczenie po istniejącej już i rozszerzenie jej funkcjonalności. Powiedzmy, że chcemy stworzyć kontrolkę która będzie nam udostępniała TextBox i będziemy mogli ustawiać szerokość. W kodzie byśmy realizowali to następująco:

   1: //C#
   2: using System;
   3: using System.Web.UI;
   4: using System.Web.UI.WebControls;
   5: public class LabeledTextBox : TextBox
   6: {
   7: public string PromptText { get; set; }
   8: public int PromptWidth { get; set; }
   9: protected override void Render(HtmlTextWriter writer)
  10: {
  11: writer.Write(
  12: @"<span style="”display:inline-block;width:{0}px”">{1}&nbsp;</span>",
  13: PromptWidth, PromptText);
  14: base.Render(writer);
  15: }
  16: }

 

Potem żeby wykorzystać naszą kontrolkę postępujemy tak jak poniżej:

   1: //C#
   2: protected void Page_Init(object sender, EventArgs e)
   3: {
   4: int width = 150;
   5: MyUserControls.LabeledTextBox prompt1 = new
   6: MyUserControls.LabeledTextBox();
   7: prompt1.PromptText = “Enter Name:";
   8: prompt1.PromptWidth = width;
   9: form1.Controls.Add(prompt1);
  10: LiteralControl br = new LiteralControl("<br />");
  11: form1.Controls.Add(br);
  12: MyUserControls.LabeledTextBox prompt2 = new
  13: MyUserControls.LabeledTextBox();
  14: prompt2.PromptText = “Enter Address:";
  15: prompt2.PromptWidth = width;
  16: form1.Controls.Add(prompt2);
  17: }

 

W magiczny sposób otrzymamy:

11

Dziedziczenie bezpośrednio z klasy WebControl

Tutaj musimy pamiętać aby koniecznie aby nadpisać metodę Render. Na potrzeby tego działu powiedzmy, że chcemy mieć kontrolkę która wyświetla logo oraz nazwę firmy. Realizacje tego przedsięwzięcia ukazuje poniższy kod:

   1: //C#
   2: using System;
   3: using System.Web.UI;
   4: using System.Web.UI.WebControls;
   5: public class LogoControl : WebControl
   6: {
   7: public LogoControl()
   8: {
   9: }
  10: public string LogoUrl
  11: {
  12: get { return _logoUrl; }
  13: set { _logoUrl = value; }
  14: }
  15: private string _logoUrl;
  16: public string CompanyName
  17: {
  18: get { return _companyName; }
  19: set { _companyName = value; }
  20: }
  21: private string _companyName;
  22: protected override void Render(HtmlTextWriter writer)
  23: {
  24: writer.WriteFullBeginTag(“div”);
  25: writer.Write(@"<img src=""{0}"" /><br />", LogoUrl);
  26: writer.Write(CompanyName + "<br />");
  27: writer.WriteEndTag(“div”);
  28: }
  29: }

 

Dodawanie kontrolki do ToolBox’a

W poprzednim przykładzie dodanie kontrolki odbywało się dynamicznie co odpowiednio zapisaliśmy w kodzie. My jednak jesteśmy przyzwyczajeni, że mamy zbiór gotowych kontrolek, przeciągamy je na formatkę i gotowe ;) Nic nie stoi na przeszkodzie aby dodać naszą kontrolkę do ToolBox’a . Jednak aby można było to zrobić nasza kontrolka musi być w dll. Kiedy mam tak przygotowaną kontrolkę klikamy w toolboxie PPM i wybieramy Choose Items i dalej jasne ;) Powiedzmy, że mieliśmy wcześniej przygotowaną dll MyUserControls, to po dodaniu jej widzimy zawarte w niej i wcześniej opisywane kontrolki:

22

Widzimy, że obok nazwy mamy dodaną jakąś domyślną ikonkę. Nic nie stoi na przeszkodzie aby ustawić własną. Do tego należy wykorzystać atrybut ToolboxBitmap z przestrzeni nazw System.Drawing . Ten atrybut określa mapę bitową 16 × 16 pikseli.

   1: //C#
   2: [ToolboxBitmap(typeof(LabeledTextBox),
   3: “MyUserControls.LabeledTextBox.bmp”)]
   4: public class LabeledTextBox : TextBox

 

Aby jeszcze dopracować naszą kontrolkę przydałaby sie domyślna właściwość. Tą możemy określić atrybutem DefaultProperty z przestrzeni nazw System.ComponentModel .

   1: //C#
   2: [DefaultProperty(“PromptText”)]
   3: public class LabeledTextBox : TextBox

Tworzenie własnego wyglądu dla kontrolki

Naturalnie kontrolki z ToolBoxa mają już jakiś domyślny wygląd. Możemy podejrzeć je w Design View i pracować z nimi za pomocą okna Properties. Może zaistnieć naturalna potrzeba, że chcielibyśmy zmienić domyślny wygląd naszej kontrolki, ba nawet może się zdarzyć, że musimy pewne specyficzne rzeczy poustawiać  żeby kontrolka była widoczna.

Aby to zrobić musimy zacząć od dodania do naszej kontrolki referencji do System.Design.dll . Następnie tworzymy nową klasę która dziedziczy po klasie ControlDesigner. W niej nadpisujemy metodę GetDesignTimeHtml w której jest HTML użyty później jako wygląd naszej kontrolki. Jako przykład, pomyślmy, że mamy kontrolkę która jest “pochodną” LabelTextBoxa i przypomina nam o ustawieniu tekstu do niej. Jeśli  jest ustawiony “PromptText” to zostanie wyświetlony domyślny (bazowy) widok. Oto kod:

   1: namespace MyUserControls
   2: {
   3: public class LabeledTextBoxDesigner : ControlDesigner
   4: {
   5: private LabeledTextBox _labeledTextBoxControl;
   6: public override string GetDesignTimeHtml()
   7: {
   8: if (_labeledTextBoxControl.PromptText.Trim().Length == 0)
   9: return "<div style='color: Gray'>[Define PromptText]</div>";
  10: else
  11: return base.GetDesignTimeHtml();
  12: }
  13: public override void Initialize(IComponent component)
  14: {
  15: _labeledTextBoxControl = (LabeledTextBox)component;
  16: base.Initialize(component);
  17: return;
  18: }
  19: }
  20: }

Następnie musimy dodać do naszej klasy z kontrolką atrybut DesignerAttribute :

   1: //C#
   2: [Designer(“MyUserControls.LabeledTextBoxDesigner, MyUserControls”)]
   3: public class LabeledTextBox : TextBox

I teraz efekt pracy, jeśli PromptyText nie jest ustawiony kontrolka będzie wyglądała następująco:

33

Tworzenie kontrolki złożonej

Możemy stworzyć własną tkz. kontrolkę złożona. Aby to uczynić musimy stworzyć klasę która podziedziczy po klasie CompositeControl. W nowej klasie konieczne jest przysłonięcie metody CreateChildControls. W niej m.in ustawiamy wszystkie potrzebne właściwości. Ponieważ każda “child controls” wie jak radzić sobie z takimi mechanizmami jak ViewState, PostBack my oszczędzamy sobie czasu i nerwy na pisanie dodatkowego kodu. Jak zrobić naszą własną kontrolkę gdzie mielibyśmy nazwę użytkownika, hasło oraz przycisk Submit? Kod oraz wygląd przykładowy poniżej:

44

   1: //C#
   2: using System;
   3: using System.ComponentModel;
   4: using System.Web.UI;
   5: using System.Web.UI.WebControls;
   6: using System.Drawing;
   7: public class UserPasswordControl : CompositeControl
   8: {
   9: public event System.EventHandler Submitted;
  10: public string UserName
  11: {
  12: get
  13: {
  14: TextBox txt = (TextBox)FindControl(“UserName”);
  15: return txt.Text;
  16: }
  17: set
  18: {
  19: TextBox txt = (TextBox)FindControl(“UserName”);
  20: txt.Text = value;
  21: }
  22: }
  23: public string Password
  24: {
  25: get
  26: {
  27: TextBox pwd = (TextBox)FindControl(“Password”);
  28: return pwd.Text;
  29: }
  30: set
  31: {
  32: TextBox pwd = (TextBox)FindControl(“Password”);
  33: pwd.Text = value;
  34: }
  35: }
  36: protected override void CreateChildControls()
  37: {
  38: Panel pnl = new Panel();
  39: TextBox txtUserName = new TextBox();
  40: TextBox txtPassword = new TextBox();
  41: Button btnSubmit = new Button();
  42: btnSubmit.Click += new EventHandler(btnSubmit_Click);
  43: //start control buildup
  44: Controls.Add(pnl);
  45: //add user name row
  46: pnl.Controls.Add(new LiteralControl("<table><tr><td>"));
  47: pnl.Controls.Add(new LiteralControl(“User Name:"));
  48: pnl.Controls.Add(new LiteralControl("</td><td>"));
  49: pnl.Controls.Add(txtUserName);
  50: pnl.Controls.Add(new LiteralControl("</td></tr>"));
  51: //add password row
  52: pnl.Controls.Add(new LiteralControl("<tr><td>"));
  53: pnl.Controls.Add(new LiteralControl(“Password:"));
  54: pnl.Controls.Add(new LiteralControl("</td><td>"));
  55: pnl.Controls.Add(txtPassword);
  56: pnl.Controls.Add(new LiteralControl("</td></tr>"));
  57: //add submit button row
  58: pnl.Controls.Add(new LiteralControl(
  59: @"<tr><td colspan="”2”" align="”center”" >"));
  60: pnl.Controls.Add(btnSubmit);
  61: pnl.Controls.Add(new LiteralControl("</td></tr></table>"));
  62: //set up control properties
  63: pnl.Style.Add(“background-color”, “silver”);
  64: pnl.Style.Add(“width”, “275px”);
  65: txtUserName.ID = “UserName”;
  66: txtUserName.Style.Add(“width”, “170px”);
  67: txtPassword.ID = “Password”;
  68: txtPassword.TextMode = TextBoxMode.Password;
  69: txtPassword.Style.Add(“width”, “170px”);
  70: btnSubmit.Text = “Submit”;
  71: }
  72: void btnSubmit_Click(object sender, EventArgs e)
  73: {
  74: if (Submitted != null) Submitted(this, e);
  75: }
  76: }
  77: //C#
  78: using System;
  79: using System.Web.UI;
  80: using System.Web.UI.WebControls;
  81: public partial class UserPasswordControlTest : System.Web.UI.Page
  82: {
  83: protected void Page_Init(object sender, EventArgs e)
  84: {
  85: UserPasswordControl p = new MyUserControls.UserPasswordControl();
  86: p.Style.Add(“position”, “absolute”);
  87: p.Style.Add(“left”, “25px”);
  88: p.Style.Add(“top”, “50px”);
  89: form1.Controls.Add(p);
  90: p.Submitted += new EventHandler(p_Submitted);
  91: }
  92: void p_Submitted(object sender, EventArgs e)
  93: {
  94: UserPasswordControl p = (UserPasswordControl)sender;
  95: Response.Write(“User: " + p.UserName + " Pass: " + p.Password);
  96: }
  97: }

To tyle na dzisiaj. Myślę, że tworzenie własnych kontrolek to bardzo ciekawy temat. W tym artykule są oczywiście podstawy podstaw ale warto rozszerzyć wiedzę własną w tym zakresie czego sobie i Wam życzę ;) Miłego weekendu!

Tagi: , , ,

70-562: Creating and Consuming XML Web Services

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

ASP.NET udostępnia prosty model dla klienta używającego Web Services. Generowany jest obiekt proxy kiedy wykorzystujemy referencje do Web Services. Obiekt proxy zajmuje się serializacją, wiadomościami SOAP i związanymi z nimi procesami. Poniżej rysunek z TK modelu XML Web Service w ASP.NET :

1

Tworzenie Web Service w ASP.NET

Tworząc XML Web Services w ASP.NET dziedziczymy po klasie System.Web.Services.WebService. Klasa ta zapewnia wrapper dla kodu usługi. Spójrzmy na schemat relacji klas przy tworzeniu Web Service:

2

Każda z tych klas kontroluje prace naszej usługi webowej. Jak widzimy aby uzyskać dostęp do standardowych funkcji ASP.NET musimy podziedziczyć po klasie WebService. Atrybuty klasa pozwalają oznaczyć część naszego kodu który jest związany z XML Web Service. Elementy oznaczone jako te przeznaczone do stosowania przez usługę są identyfikowane automatycznie przez ASP.NET. Również klasa ta “umie” deserializować wnioski, wywoływać usługę czy też serializować odpowiedzi. Obsługuje również prace z SOAP, XML, Web Services Description Language (WSDL) i związanych z nimi standardów usług internetowych.

Projekt Usługi Web ASP.NET

Web Service definiujemy w pliku .asmx. Plik ten może być dodawany bezpośrednio do istniejącej witryny sieci Web. Jest to przydatne kiedy nasze witryna oprócz bycia samą w sobie stroną www będzie potrzebowała korzystać z usług. Oczywiście możemy stworzyć stronę www która składa się z samych usług.

Tak jak strony www, web service jest wyrażony przy pomocy URL. Co w praktyce oznacza adres w stylu http://mojadomena/mojausluga.asmx. W samym pliku .asmx jest tylko informacja, że kod obsługujący stronę znajduje się w oddzielnym pliku. Załóżmy, że chcemy dodać usługę w której mamy metody, udostępniające nam prace z Autorami na jakiejś przykładowej bazie danych. Możemy zacząć od stworzenia pliku Authors.asmx. W pliku tym zawieramy dyrektywę @ WebService która wskazuje rzeczywiste miejsce kodu usługi. W kodzie wygląda to dokładnie tak:

   1: //C#
   2: <%@ WebService Language="C#" CodeBehind="Authors.asmx.cs" Class="PubsServices.Authors"
   3: %>

 

Klasa WebServiceAttribute

Stworzenie pliku .asmx plus udostępnienie publicznej metody oznaczonej jako [WebMethod] (o czym za chwile) jest wystarczające do określenia usługi w środowisku ASP.NET. Istnieje jednak wiele innych klas które mogą zapewnić dodatkową funkcjonalność. Jedną z nich jest tytułowa WebServiceAttribute. Może ona być wykorzystana do dostarczenia informacji o usłudze sieci Web. Informacje te są wykorzystywane po stronie klienckiej, podczas odwołania się do usługi.

Możemy podawać zarówno przestrzeń nazw i opis naszej usługi sieciowej przez stosowanie atrybutu WebService do naszej klasy. Przestrzeń nazw określona dla naszej usługi, natomiast opisem jest zwykły tekst. Visual Studio do czasu określenia właściwej domeny używa tempuri.org .Przykład dodawania takiego atrybutu dla klasy Authors poniżej:

   1: //C#
   2: [WebService(Description = "Services related to published authors",
   3: Namespace = "http://tempuri.org/")]
   4: public class Authors

 

Klasa WebService

Klasa WebService może stanowić klasę bazową do stworzenia XML Web Services w ASP.NET. Klasa ta jest podobna do klasy Page w stronach www. Umożliwia ona dostęp do takich obiektów jak Application czy Session. Na początku podkreśliłem, że klasa WebService może stanowić klasę bazową…może ale nie musi ;) Tak naprawdę powinniśmy po niej dziedziczyć wtedy kiedy jest nam potrzebna funkcjonalność aplikacji ASP.NET. Poniżej kod który pokazuje zapis klasy z dziedziczeniem:

   1: //C#
   2: [WebService(Description = "Services related to published authors",
   3: Namespace = "http://tempuri.org/")]
   4: public class Authors : System.Web.Services.WebService

 

Klasa WebMethodAttribute

Nasza usługa udostępnia metody a każda z tych metod stanowi pewną obudowaną funkcjonalność. W klasie metody te oznaczamy atrybutem [WebMethod]. Spowoduje to, że daną metodę chcemy “udostępnić światu”. Można przypisać atrybut [WebMethod] bez żadnych dodatkowych parametrów i to po prostu nam określi metodę jako metodę usługi sieci Web. Jednak mamy szereg dostępnych parametrów które potrafią dodać pewną funkcjonalność. Na przykład enableSessionState (określa czy metoda ma w jakiś sposób współpracować z session state, do wyboru true/false) czy bufferResponse (określa czy odpowiedź ma być buforowana z powrotem do klienta). Poniżej jest kod który wykorzystuje parametr cacheDuration (określa ile czasu mają być przechowywane dane w pamięci podręcznej) :

   1: //C#
   2: [WebMethod(CacheDuration=300)]
   3: public DataTable GetAuthorTitles(string authorId)
   4: { ... }

 

Używanie Web Service w ASP.NET

Usługę Webową możemy uruchomić praktycznie w każdej aplikacji która jest w stanie stworzyć połączenie HTTP. Nawet w aplikacji konsolowej ;)  Aby rozpocząć korzystanie z usługi, takowa musi już gdzieś istnieć. Może ona istnieć gdzieś na serwerze, w naszym projekcie, gdziekolwiek. W projekcie który mam wybieramy w solution explorer PPM na projekt i wybieramy Add Web Reference. Otworzy się nam okno gdzie ustawiamy m.in adres URL, wybieramy pliki typu .asmx i ustawiamy nazwę dla naszej referencji. Poniżej zrzut ekranu okna w którym jest pokazane przykładowe dodanie usługi Authors.asmx :

3 

Widzimy albo i nie ;) , że usługa ta udostępnia nam dwie metody GetAuthor(); i GetAuthorTitles(); Co one miały robić w domyśle autora nie ważne ;)

Wywoływanie Web Service

Jest to tak proste, jak pisanie kodu w celu wywołania metody. Na przykład, następujący kod pokazuje jak wywołać metodę GetAuthor() :

   1: //C#
   2: PubServices.Authors pubs = new PubServices.Authors();
   3: PubServices.Author auth = pubs.GetAuthor();
   4: Label1.Text = auth.FirstName + " " + auth.LastName;

 

Można również wiązać wywołania usługi. Jako przykład użyjemy wcześniej wspomnianej metody GetAuthorsTitles() która zwracała DataTable dlatego aby powiązać dane np. z GridView wystarczy odpowiednio go zdefiniować:

   1: <asp:ObjectDataSource runat="server"
   2: ID="ObjectDataSourceAuthors"
   3: TypeName="PubsServices.Authors">
   4: SelectMethod="GetAuthorTitles"
   5: <SelectParameters>
   6: <asp:QueryStringParameter
   7: Name="authorId"
   8: QueryStringField="auId"
   9: Type="String" />
  10: </SelectParameters>
  11: </asp:ObjectDataSource>
  12: <asp:GridView ID="GridView1" runat="server"
  13: DataSourceID="ObjectDataSourceAuthors">
  14: </asp:GridView>

 

Łączenie się z usługa przy pomocy skryptów z wykorzystaniem AJAX

Możemy użyć wbudowany w ASP.NET AJAX funkcjonalności aby wywołać usługę webową przy użyciu javascript po stronie klienta. Jednak trzeba pamiętać o pewnych rzeczach przy konfiguracji takowego rozwiązania. Ważne jest np. aby nasza “strona kliencka” była w tej samej domenie co pliki .asmx! Poniższe kroki powinny pomóc nam przy konfiguracji:

  • oznacz metody których będziesz chciał w taki sposób używać parametrem ScriptServiceAttribute.
  • “zarejestruj” na stronie ScriptHandlerFactory. Możesz to zrobić wewnątrz elementu <system.web> <httpHandlers> w pliku Web.config.
  • dodaj ScirptManager do naszej strony. Jest on wymagany dla każdych stron używających AJAX, tutaj dodatkowo wewnątrz jego trzeba dodać referencje do usług (ServiceReference).
  • dodaj metody javascript do strony.
  • można również dodać inne metody javascript którę obsłużą callback

Dla większego zobrazowania przykład- otóż wyobraź sobie, że masz usługę która pobiera temperaturę w stopniach Farenheita a zwraca w Celsjuszach. Kod takiej metody w usłudze wyglądałby następująco:

   1: //C#
   2: [System.Web.Script.Services.ScriptService]
   3: [WebService(Namespace = "http://tempuri.org/")]
   4: public class TempConversion : System.Web.Services.WebService
   5: {
   6: [WebMethod]
   7: public float GetCelsius(float temperature)
   8: {
   9: return (5 / 9) * (temperature - 32);
  10: }
  11: }

 

Następnie musimy się upewnić, że jest dodany w web.configu ScriptHandlerFactory. Oto kod:

   1: <httpHandlers>
   2: <remove verb="*" path="*.asmx" />
   3: <add verb="*" path="*.asmx" validate="false"
   4: type="System.Web.Script.Services.ScriptHandlerFactory" />
   5: </httpHandlers>

 

Pozostaje nam stworzyć stronę po stronie klienckiej. Pamiętajmy o dodaniu ScriptManagera i dodaniu do niego referencji:

   1: <asp:ScriptManager runat="server" ID="ScriptManager1">
   2: <Services>
   3: <asp:ServiceReference
   4: path="TempConversion.asmx" />
   5: </Services>
   6: </asp:ScriptManager>

 

Następnym krokiem jest dodanie metod javascript:

   1: <script type="text/javascript">
   2: function GetCelsius()
   3: {
   4: var val = document.getElementById("TextBoxTemp");
   5: TempConversion.GetCelsius(val.value, FinishCallback);
   6: }
   7: function FinishCallback(result)
   8: {
   9: var results = document.getElementById("LabelResults");
  10: results.innerHTML = result;
  11: }
  12: </script>

 

I na końcu oczywiście pola tekstowe do wpisywania wartości i wyniku oraz przycisk wywołujący metodę:

   1: <asp:TextBox ID="TextBoxTemp" runat="server"></asp:TextBox>&nbsp;
   2: <asp:Label ID="LabelResults" runat="server" Text=""></asp:Label>
   3: <br />
   4: <input id="Button1" type="button" value="Calculate" onclick="GetCelsius()" />

 

To tyle w tym temacie. Traning Kit porusza również ważny temat zabezpieczenia naszej usługi. Ale to pozostawiam Wam jako prace domową na weekend. Moment na doczytanie ;) Do poniedziałku! :)

Tagi: , , , ,

70-562: Working with XML data

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Jak wiadomo .NET ma szeroko pojęte wsparcie dla XML. Implementacja XML to wydajność, niezawodności i skalowalność a w połączeniu z ADO.NET możliwość korzystania z XML jako źródła danych.

Klasy XML

Klasy XML są dostępne w System.Xml.dll czyli potrzebujemy dyrektywy using System.Xml. System.Data.dll rozszerza wspomnianą przestrzeń o chociażby klasę XmlDataDocument. Chciałbym teraz po krótce przyjrzeć się podstawowym klasom XML w .NET Frameworku. Każda z tych klas oferuje różne stopnie funkcjonalności, dlatego ważne jest aby odpowiednio przeanalizować swój problem i wybrać odpowiednią z nich. Poniższy rysunek pokazuje ogólnie obiekty o których będziemy mówić:

1

XmlDocument i XmlDataDocument

Klasy te możemy wykorzystywać np. do nawigacji i edycji węzłów XML. XmlDataDocument dziedziczy po XmlDocument i reprezentuje relacyjne dane. XmlDataDocument może przedstawiać swoje dane jako DataSet zarówno jako relacyjny i nierelacyjny widok danych. Klasy te dostarczają wiele metod które są opisane i ładnie zebrane w TK w ładnej tabelce. Większość z nich jest intuicyjna i po samej nazwie możemy wnioskować do czego służą.

XPathDocument

Klasa służy tylko do odczytu z pamięci podręcznej XMLDocument, wykorzystywana do bardzo szybkich zapytań XPath.

XmlConvert

Klasa ta zawiera wiele statycznych metod konwersji pomiędzy typami XSD a zawartymi w CLR. Klasa te jest szczególnie ważna podczas pracy ze źródłami danych, które umożliwiają nazwy które nie są dozwolone w XML. Jeśli mamy kolumnę w bazie danych która nazywa się List Price to próba stworzenia np. atrybutu o takich nazwie wyrzuci nam wyjątek. XMLConnvert zakoduje nam spacje na _0x0020_ dzięki czemu otrzymamy nazwę List_x0020_Price którą jest już prawidłową nazwą i możemy ją odkodować używając metode XmlConvert.DecodeName. Również klasa ta posiada wiele metod statycznych które potrafią konwertować ciągi znaków na typy numeryczne.

XPathNavigator

Klasa ta zapewnia skuteczną nawigację w dokumencie xml używając do tego XPath. Klasa ta wspiera Extensible Stylesheet Language Transformations (XSLT).

XmlNodeReader

Klasa ta umożliwia dostęp do danych a dokładnie mówiąc wejście w dowolny węzeł pliku XML.

XmlTextReader

Klasa udostępnia podstawowy dostęp do danych. Nie zapisuje wyników swojej pracy w pamięci podręcznej. XmlTextReader nie wykonuje walidacji dokumentu, ale sprawdza dane XML pod kątem poprawności “uformowania” ich.

XmlTextWriter

Pozwala zapisywać dane do w postaci XML do pliku zapewniając przy tym, że będą zgodne ze standardem W3C XML 1.0. Klasa ta zwiera wsparcie dla przestrzeni nazw i rozwiązywania problemów z nimi związanymi.

XmlReader

Klasa służy również do odczytu i waliduje dane zgodnie z DTD, XDR lub XDS. Sam konstruktor oczekuje źródła pliku “sprawdzonych” danych.

XslTransform

Klasa ta umożliwia przekształcenie dokumentu XML z wykorzystaniem arkusza stylów XSL. Obsługuje ona składnie w wersji 1.0 i oferuje dwie metody Load i Transform.

Praca z dokumentem XML

Z pewnością istnieje wiele metod pracy z dokumentem XML w .NET Frameworku. Dzisiaj pokażemy sobie w jaki sposób można wykonać podstawowy zapis, odczyt, wyszukiwanie danych. Teraz multum przykładów, kodu zawartego w TK który pokazuję realizacje tych i wielu innych zadań.

Tworzenie nowego dokumentu

Aby stworzyć dokument XML musimy zacząć od utworzenia obiektu XMLDocument. Zawiera on m.in metodę typu CreateElement i CreateAttribute które pozwalają nam tworzyć poszczególne węzły. Spójrzmy na poniższy kod, który wraz z komentarzami mówi sam za siebie ;)

   1: //C#
   2: protected void Button1_Click(object sender, EventArgs e)
   3: {
   4: //Declare and create new XmlDocument
   5: XmlDocument xmlDoc = new XmlDocument();
   6: XmlElement el;
   7: int childCounter;
   8: int grandChildCounter;
   9: //Create the xml declaration first
  10: xmlDoc.AppendChild(
  11: xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null));
  12: //Create the root node and append into doc
  13: el = xmlDoc.CreateElement("myRoot");
  14: xmlDoc.AppendChild(el);
  15: //Child Loop
  16: for (childCounter = 1; childCounter <= 4; childCounter++)
  17: {
  18: XmlElement childelmt;
  19: XmlAttribute childattr;
  20: //Create child with ID attribute
  21: childelmt = xmlDoc.CreateElement("myChild");
  22: childattr = xmlDoc.CreateAttribute("ID");
  23: childattr.Value = childCounter.ToString();
  24: childelmt.Attributes.Append(childattr);
  25: //Append element into the root element
  26: el.AppendChild(childelmt);
  27: for (grandChildCounter = 1; grandChildCounter <= 3; grandChildCounter++)
  28: {
  29: //Create grandchildren
  30: childelmt.AppendChild(xmlDoc.CreateElement("GrandChild"));
  31: }
  32: }
  33: //Save to file
  34: xmlDoc.Save(MapPath("XmlDocumentTest.xml"));
  35: Label lbl = GetLabel(275, 20);
  36: lbl.Text = "XmlDocumentTest.xml Created";
  37: }

 

Kod ten wygeneruje następujący dokument XML:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <myRoot>
   3: <myChild ID="1">
   4: <GrandChild />
   5: <GrandChild />
   6: <GrandChild />
   7: </myChild>
   8: <myChild ID="2">
   9: <GrandChild />
  10: <GrandChild />
  11: <GrandChild />
  12: </myChild>
  13: <myChild ID="3">
  14: <GrandChild />
  15: <GrandChild />
  16: <GrandChild />
  17: </myChild>
  18: <myChild ID="4">
  19: <GrandChild />
  20: <GrandChild />
  21: <GrandChild />
  22: </myChild>
  23: </myRoot>
Parsowanie dokumentu przy użyciu DOM i XPathNavigator

Oba przykłady pokazują jak rekurencyjnie analizować dokument XML. Jednak trzeba pamiętać, że XPathNavigator udostępnia nam szereg metod z których możemy dodatkowo korzystać.

   1: //C#
   2: Label lbl = new Label();
   3: protected void Button2_Click(object sender, EventArgs e)
   4: {
   5: lbl = GetLabel(275, 20);
   6: XmlDocument xmlDoc = new XmlDocument();
   7: xmlDoc.Load(MapPath("XmlDocumentTest.xml"));
   8: RecurseNodes(xmlDoc.DocumentElement);
   9: }
  10: public void RecurseNodes(XmlNode node)
  11: {
  12: //start recursive loop with level 0
  13: RecurseNodes(node, 0);
  14: }
  15: public void RecurseNodes(XmlNode node, int level)
  16: {
  17: string s;
  18: s = string.Format("{0} <b>Type:</b>{1} <b>Name:</b>{2} <b>Attr:</b> ",
  19: new string('-', level), node.NodeType, node.Name);
  20: foreach (XmlAttribute attr in node.Attributes)
  21: {
  22: s += string.Format("{0}={1} ", attr.Name, attr.Value);
  23: }
  24: lbl.Text += s + "<br>";
  25: foreach (XmlNode n in node.ChildNodes)
  26: {
  27: RecurseNodes(n, level + 1);
  28: }
  29: }
   1: //C#
   2: protected void Button3_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: XmlDocument xmlDoc = new XmlDocument();
   6: xmlDoc.Load(MapPath("XmlDocumentTest.xml"));
   7: XPathNavigator xpathNav = xmlDoc.CreateNavigator();
   8: xpathNav.MoveToRoot();
   9: RecurseNavNodes(xpathNav);
  10: }
  11: public void RecurseNavNodes(XPathNavigator node)
  12: {
  13: //start recursive loop with level 0
  14: RecurseNavNodes(node, 0);
  15: }
  16: public void RecurseNavNodes(XPathNavigator node, int level)
  17: {
  18: string s = null;
  19: s = string.Format("{0} <b>Type:</b>{1} <b>Name:</b>{2} <b>Attr:</b> ",
  20: new string('-', level), node.NodeType, node.Name);
  21: if (node.HasAttributes)
  22: {
  23: node.MoveToFirstAttribute();
  24: do
  25: {
  26: s += string.Format("{0}={1} ", node.Name, node.Value);
  27: } while (node.MoveToNextAttribute());
  28: node.MoveToParent();
  29: }
  30: lbl.Text += s + "<br>";
  31: if (node.HasChildren)
  32: {
  33: node.MoveToFirstChild();
  34: do
  35: {
  36: RecurseNavNodes(node, level + 1);
  37: } while (node.MoveToNext());
  38: node.MoveToParent();
  39: }
  40: }

 

Wyszukiwanie danych przy użyciu DOM

DOM wspiera metody GetElementByID i the GetElementsByTagName do wyszukiwania danych w dokumencie XML. GetElementByID lokalizuje element na podstawie jego unikatowego ID. Spójrzmy na plik DTD zdefiniowany następująco:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <!DOCTYPE myRoot [
   3: &lt;!ELEMENT myRoot ANY>
   4: <!ELEMENT myChild ANY>
   5: <!ELEMENT myGrandChild EMPTY>
   6: <!ATTLIST myChild
   7: ChildID ID #REQUIRED
   8: >
   9: ]>
  10: <myRoot>
  11: <myChild ChildID="ref-1">
  12: <myGrandChild/>
  13: <myGrandChild/>
  14: <myGrandChild/>
  15: </myChild>
  16: <myChild ChildID="ref-2">
  17: <myGrandChild/>
  18: <myGrandChild/>
  19: <myGrandChild/>
  20: </myChild>
  21: <myChild ChildID="ref-3">
  22: <myGrandChild/>
  23: <myGrandChild/>
  24: <myGrandChild/>
  25: </myChild>
  26: <myChild ChildID="ref-4">
  27: <myGrandChild/>
  28: <myGrandChild/>
  29: <myGrandChild/>
  30: </myChild>
  31: </myRoot>

 

Każde “dziecko” ma swoje Id i powiedzmy że chcemy znaleźć element o ID=ref-3. Realizuje to poniższy kod:

   1: //C#
   2: protected void Button4_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: string s;
   6: //Declare and create new XmlDocument
   7: XmlDocument xmlDoc = new XmlDocument();
   8: xmlDoc.Load(MapPath("XmlSample.xml"));
   9: XmlNode node;
  10: node = xmlDoc.GetElementById("ref-3");
  11: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1} <b>Attr:</b>",
  12: node.NodeType, node.Name);
  13: foreach (XmlAttribute a in node.Attributes)
  14: {
  15: s += string.Format("{0}={1} ", a.Name, a.Value);
  16: }
  17: lbl.Text = s + "<br>";
  18: }

Do powyższego zadania również użyteczna może być metoda SelectSingleNode. Świetnie się nadaje do tego celu i spójrzmy na realizacje tego samego problemy z wykorzystaniem wspomnianej metody:

   1: //C#
   2: protected void Button5_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: string s;
   6: //Declare and create new XmlDocument
   7: XmlDocument xmlDoc = new XmlDocument();
   8: xmlDoc.Load(MapPath("XmlSample.xml"));
   9: XmlNode node;
  10: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-3']");
  11: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1} <b>Attr:</b>",
  12: node.NodeType, node.Name);
  13: foreach (XmlAttribute a in node.Attributes)
  14: {
  15: s += string.Format("{0}={1} ", a.Name, a.Value);
  16: }
  17: lbl.Text = s + "<br>";
  18: }

 

Aby zwrócić węzły o danych nazwach możemy wykorzystać metodę GetElementsByTagName. Powiedzmy, że chcemy zwrócić dane z węzłów o nazwie myGrandChild:

   1: //C#
   2: protected void Button6_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: string s;
   6: //Declare and create new XmlDocument
   7: XmlDocument xmlDoc = new XmlDocument();
   8: xmlDoc.Load(MapPath("XmlSample.xml"));
   9: XmlNodeList elmts;
  10: elmts = xmlDoc.GetElementsByTagName("myGrandChild");
  11: foreach (XmlNode node in elmts)
  12: {
  13: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1}",
  14: node.NodeType, node.Name);
  15: lbl.Text += s + "<br>";
  16: }
  17: }

 

Ale nie jest to jedyny sposób realizacji tego zdania. Równie dobrze możemy wykorzystać do tego celu SelectNodes w następujący sposób (w sumie różnica w jednej linijce):

   1: //C#
   2: protected void Button7_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: string s;
   6: //Declare and create new XmlDocument
   7: XmlDocument xmlDoc = new XmlDocument();
   8: xmlDoc.Load(MapPath("XmlSample.xml"));
   9: XmlNodeList elmts;
  10: elmts = xmlDoc.SelectNodes("//myGrandChild");
  11: foreach (XmlNode node in elmts)
  12: {
  13: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1}",
  14: node.NodeType, node.Name);
  15: lbl.Text += s + "<br>";
  16: }
  17: }

 

GetElementByTag ogranicza się do nazwy, natomiast SelectNodes pozwala nam na większą elastyczność w poszukiwaniu węzła.

Zapisywanie do pliku przy użyciu XmlTextWriter

Co tu się dużo rozpisywać. Poniższy przykład tworzy listę pracowników i zapisuje dwóch z nich na ta listę. Kod realizujący to zadanie  efekt poniżej:

   1: //C#
   2: protected void Button10_Click(object sender, EventArgs e)
   3: {
   4: XmlTextWriter xmlWriter = new
   5: XmlTextWriter(MapPath("EmployeeList.xml"),
   6: System.Text.Encoding.UTF8);
   7: xmlWriter.Formatting = Formatting.Indented;
   8: xmlWriter.Indentation = 5;
   9: xmlWriter.WriteStartDocument();
  10: xmlWriter.WriteComment("XmlTextWriter Test Date: " +
  11: DateTime.Now.ToShortDateString());
  12: xmlWriter.WriteStartElement("EmployeeList");
  13: //New Employee
  14: xmlWriter.WriteStartElement("Employee");
  15: xmlWriter.WriteAttributeString("EmpID", "1");
  16: xmlWriter.WriteAttributeString("LastName", "JoeLast");
  17: xmlWriter.WriteAttributeString("FirstName", "Joe");
  18: xmlWriter.WriteAttributeString("Salary", XmlConvert.ToString(50000));
  19: xmlWriter.WriteElementString("HireDate",
  20: XmlConvert.ToString(DateTime.Parse("1/1/2003"),
  21: XmlDateTimeSerializationMode.Unspecified));
  22: xmlWriter.WriteStartElement("Address");
  23: xmlWriter.WriteElementString("Street1", "123 MyStreet");
  24: xmlWriter.WriteElementString("Street2", "");
  25: xmlWriter.WriteElementString("City", "MyCity");
  26: xmlWriter.WriteElementString("State", "OH");
  27: xmlWriter.WriteElementString("ZipCode", "12345");
  28: //Address
  29: xmlWriter.WriteEndElement();
  30: //Employee
  31: xmlWriter.WriteEndElement();
  32: //New Employee
  33: xmlWriter.WriteStartElement("Employee");
  34: xmlWriter.WriteAttributeString("EmpID", "2");
  35: xmlWriter.WriteAttributeString("LastName", "MaryLast");
  36: xmlWriter.WriteAttributeString("FirstName", "Mary");
  37: xmlWriter.WriteAttributeString("Salary", XmlConvert.ToString(40000));
  38: xmlWriter.WriteElementString("HireDate",
  39: XmlConvert.ToString(DateTime.Parse("1/2/2003"),
  40: XmlDateTimeSerializationMode.Unspecified));
  41: xmlWriter.WriteStartElement("Address");
  42: xmlWriter.WriteElementString("Street1", "234 MyStreet");
  43: xmlWriter.WriteElementString("Street2", "");
  44: xmlWriter.WriteElementString("City", "MyCity");
  45: xmlWriter.WriteElementString("State", "OH");
  46: xmlWriter.WriteElementString("ZipCode", "23456");
  47: //Address
  48: xmlWriter.WriteEndElement();
  49: //Employee
  50: xmlWriter.WriteEndElement();
  51: //EmployeeList
  52: xmlWriter.WriteEndElement();
  53: xmlWriter.Close();
  54: Response.Redirect("EmployeeList.xml");
  55: }

 

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <!--XmlTextWriter Test Date: 8/16/2006-->
   3: <EmployeeList>
   4: <Employee EmpID="1" LastName="JoeLast" FirstName="Joe" Salary="50000">
   5: <HireDate>2003-01-01T00:00:00</HireDate>
   6: <Address>
   7: <Street1>123 MyStreet</Street1>
   8: <Street2 />
   9: <City>MyCity</City>
  10: <State>OH</State>
  11: <ZipCode>12345</ZipCode>
  12: </Address>
  13: </Employee>
  14: <Employee EmpID="2" LastName="MaryLast" FirstName="Mary" Salary="40000">
  15: <HireDate>2003-01-02T00:00:00</HireDate>
  16: <Address>
  17: <Street1>234 MyStreet</Street1>
  18: <Street2 />
  19: <City>MyCity</City>
  20: <State>OH</State>
  21: <ZipCode>23456</ZipCode>
  22: </Address>
  23: </Employee>
  24: </EmployeeList>

 

Co jest fajne XMLTextWriter posiada właściwości opowiadające za formatowanie i wcięcia.

Odczytywanie z pliku przy pomocy XmlTextReader

Klasa XmlTextReader jest używana do odczytywania danych węzeł po węźle. Wadą używania tej klasy jest to, że odczyt jest typu “forward-only”. Poniższy kod pokazuje informacje o każdym węźle oczywiście na podstawie wcześniejszych danych:

   1: //C#
   2: protected void Button11_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: XmlTextReader xmlReader = new
   6: XmlTextReader(MapPath("EmployeeList.xml"));
   7: while (xmlReader.Read())
   8: {
   9: switch( xmlReader.NodeType)
  10: {
  11: case XmlNodeType.XmlDeclaration:
  12: case XmlNodeType.Element:
  13: case XmlNodeType.Comment:
  14: {
  15: string s;
  16: s = String.Format("{0}: {1} = {2}<br>",
  17: xmlReader.NodeType,
  18: xmlReader.Name,
  19: xmlReader.Value);
  20: lbl.Text += s;
  21: break;
  22: }
  23: case XmlNodeType.Text:
  24: {
  25: string s;
  26: s = String.Format(" - Value: {0}<br>",
  27: xmlReader.Value);
  28: lbl.Text += s;
  29: break;
  30: }
  31: }
  32: if (xmlReader.HasAttributes)
  33: {
  34: while (xmlReader.MoveToNextAttribute())
  35: {
  36: string s;
  37: s = String.Format(" - Attribute: {0} = {1}<br>",
  38: xmlReader.Name, xmlReader.Value);
  39: lbl.Text += s;
  40: }
  41: }
  42: }
  43: xmlReader.Close();
  44: }

 

Modyfikowanie dokumentu

Usunięcie węzła realizuje dwa zadania…znaleźć węzeł i go usunąć. Dodanie węzła to utworzenie go, znalezienie odpowiedniego miejsca i wstawienie go tam. Jak to zrealizować pokazuje poniższy przykład:

   1: //C#
   2: protected void Button12_Click(object sender, EventArgs e)
   3: {
   4: lbl = GetLabel(275, 20);
   5: //Declare and load new XmlDocument
   6: XmlDocument xmlDoc = new XmlDocument();
   7: xmlDoc.Load(MapPath("XmlSample.xml"));
   8: //delete a node
   9: XmlNode node;
  10: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-3']");
  11: node.ParentNode.RemoveChild(node);
  12: //create a node and add it
  13: XmlElement newElement =
  14: xmlDoc.CreateElement("myNewElement");
  15: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-1']");
  16: node.ParentNode.InsertAfter(newElement, node);
  17: xmlDoc.Save(MapPath("XmlSampleModified.xml"));
  18: Response.Redirect("XmlSampleModified.xml");
  19: }

Tyle na dzisiaj ;) Zasypałem Was kodem ale widocznie w TK uznali, że na przykładzie najlepiej to wszystko będzie widoczne. W TK również jest wspomniane o LINQ to XML. Możecie sobie doczytać o tych podstawach a i myślę, że warto rozszerzyć swoją wiedze o tym mechanizmie który wydaję mi się jest o wiele bardziej przejrzysty i “czystszy” w kodzie ;)

Tagi: , , , , ,