Hasło - aplikacja MVVM

Na prezentacji pokazałem sposób tworzenia aplikacji WPF z wykorzystaniem Visual Studio, Blend i wzorca projektowego MVVM. Kod i slajdy będą na SkyDrive grupy. Dzisiaj chcę wam pokazać jak można wykonać podobną aplikacje która może nam posłużyć do „wymyślania” haseł na konta ;p
To do dzieła.
1 Tworzymy nowy projekt.
Nowy projekt utworzymy z wykorzystaniem Blenda. Jeżeli ktoś nie ma Blenda może śmiało to samo wykonać w Visual Studio. Uruchamiamy Blenda po czym w okienku które nam wyskoczyło naciskamy New Project. Alternatywnie możemy zrobić to samo przez File-> New Project. Wybieramy typ projektu jako WPF Application. Nazywamy nasz projekt Haslo, zapamiętujemy gdzie go zapisujemy i klikamy OK.
2 Projektujemy widok
Powinno ukazać się nam okno domyślnie utworzone przez Blenda. Będziemy dążyć aby wygląd naszej aplikacji był taki jak niżej.
Tworzyć nasz widok możemy na dwa sposoby, poprzez przeciąganie kontrolek(niektórzy mogą preferować ten sposób) lub przez deklaracje bezpośrednio w kodzie. Ja osobiście wolę ta druga metodę wiec jej będziemy używać. Aby zobaczyć nasz kod klikamy na View-> Active Document View-> Split View ( będziemy widzieć kod i wygląd naszej aplikacji ).  Zabieramy się za tworzenie widoku.  Zaczynamy od zdefiniowania w naszym Gridzie dwóch wierszy do których będziemy przypisywać kontrolki. Uzyskujemy to przez następujący kod:
<Grid.RowDefinitions>
           <RowDefinition Height="Auto"/>
           <RowDefinition Height="auto"/>
</Grid.RowDefinitions>
 Następnie dodajemy dwa StackPanele. Jeden przypisujemy do pierwszego wiersza, drugi do drugiego.
W pierwszym tworzymy Labela który będzie nam wyświetlać losowane liczby. Wielkość czcionki ustawiamy na 40, wyśrodkowujemy w pionie i poziomie. Jego zawartość(Content) musimy połączyć z właściwością ViewModel który będziemy tworzyć w dalszej części. Drugi StackPanel będzie troche bardziej złożony.  W jego wnętrzu deklarujemy dwa kolejne StackPanele. W obu ustawiamy orientację na poziomą i wyrównujemy w poziomie na środek. Do tej pory nasz fragment powinien wyglądać następująco.

 <StackPanel Grid.Row="0">
      <Label Content="{Binding Path=Liczby,UpdateSourceTrigger=PropertyChanged}" FontSize="40" HorizontalContentAlignment="Center"
                  VerticalContentAlignment
="Center"/>
      </StackPanel>
      <StackPanel Grid.Row="1">
      <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
            <Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
      </StackPanel>
      <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
            <Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
      </StackPanel>
</StackPanel>

Postępujemy analogicznie jak w przypadku pierwszego. Wypełniamy drugi następującym kodem:
<StackPanel Grid.Row="1">
       <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
               <Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
               <Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
        </StackPanel>
        <StackPanel  HorizontalAlignment="Center" Orientation="Horizontal">
                <Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
                <Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
         </StackPanel>
 </StackPanel> 
 Przy przyciskach pojawila się nowa właściwość – Command.
 Nasz widok powinien być już gotowy. Później będziemy musieli na chwile do niego wrócić alby połaczyć nasz ViewModel z przed chwila utworzonym widokiem. Budujemy nasz projekt (Ctrl+Shift+B)
 
3 Visual Studio
W tej części zajmiemy się logika naszej aplikacji. Otwieramy w Visual Studio projekt który wczesniej utworzyliśmy.  Na początku zajmiemy się utworzeniem naszego ViewModelu później przystąpimy do klas które będą mu potrzebne. Tworzymy nowa klasę :
 
Nazywamy ją MainViewModel i zatwierdzamy.  Nowo utworzona klasa musi implementować interfejs INotifyPropertyChanged. Do tego potrzebne będzie nam dodanie przestrzeni nazw (using System.ComponentModel) . Aby przyśpieszyć sobie prace Visual Studio zaimplementuje go za nas. Klikamy prawym przyciskiem myszki na przed chwila zadeklarowanej implementacji, wybieramy Implement Interface -> Implement Interface.
 
 Dodajemy metodę która będzie wywoływana przy każdej zmianie jakiejkolwiek właściwości. Będzie nam to potrzebne aby nasz widok mógł się zorientować, że zmiana miała miejsce i należy jego dane zaktualizować.
public void PropertyChangedInvoker(string name)
{
    if (PropertyChanged != null)
    {
         PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

Teraz kolej nadeszła na utworzenie właściwości w naszej klasie, dodajemy następujący kod:

//wartosc suwaka w postaci double
private double _slider;
public double Slider
{
     get { return _slider; }
     set
     {
          _slider = value;
          SliderValue = Convert.ToInt32(_slider);
     }
}

//pozycja suwaka w postaci int
private int _sliderValue;
public int SliderValue
{
     get { return _sliderValue; }
     set
     {
          _sliderValue = value;
          PropertyChangedInvoker("SliderValue");
     }
}

//liczby które są wyświetlane w widoku
private string _liczby;
public string Liczby
{
     get { return _liczby; }
     set
     {
          _liczby = value;
          PropertyChangedInvoker("Liczby");
     }
}

//odpowiada za trzymanie informacji czy przcisk Stop został nacisniety
private bool _stopButtonClicked;
public bool StopButtonClicked
{
     get { return _stopButtonClicked; }
     set
     {
          _stopButtonClicked = value;
          PropertyChangedInvoker("StopButtonClicked");
     }
}

//odowiada za trzymanie informacji czy przycisk Stop jest aktywny
private bool _canStopButtonExecute;
public bool CanStopButtonExecute
{
     get { return _canStopButtonExecute; }
     set
     {
          _canStopButtonExecute = value;
          PropertyChangedInvoker("CanStopButtonExecute");
     }
}

Naszemu ViewModelowi dalej czegoś brakuje. W Widoku utworzyliśmy 2 przyciski. Teraz pokaże co zrobić żeby je trochę ożywić. Pierwszym zadaniem będzie utworzenie i klas które będą implementowały interface ICommand. Klasy będą nazywać się odpowiednio StartCommand i StopCommand. Sama implementacja wygląda dokładnie tak samo jak wcześniej pokazałem. Wcześniej jednak do każdej z nich musimy dodać następującą przestrzeń nazw - using System.Windows.Input; Po tym nasze klasy, w tym wypadku StartCommnad, powinny wyglądać tak:

class
StartCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        throw new NotImplementedException();
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        throw new NotImplementedException();
    }
}

Klasa posiada dwie metody i jeden event. Pierwsza metoda(CanExecute) jest wywoływana gdy event (CanExecuteChanged) zostanie „wzniesiony”. Tak nasz widok sprawdza czy dana komenda może być wykonana.
Druga metoda (Execute) zawiera kod który będzie wywołany w przypadku gdy komenda ma zostać wykonana ( w naszym przypadku przez naciśniecie przycisku).
Musimy trochę rozbudować nasze klasy gdyż puste metody niewiele nam pomogą J. Pierwszym krokiem będzie utworzenie konstruktora i dodanie referencji do naszego ViewModelu dla naszych klas. Pozwoli nam to odwoływać się do jego właściwości.
private readonly MainViewModel _vm;
public StartCommand(MainViewModel viewModel)
{
    _vm = viewModel;
}
Dodamy teraz trochę logiki do naszych metod. Rozbudujemy tez nasz event.

public bool CanExecute(object parameter)
{
    return _vm.CanStopButtonExecute == false && _vm.SliderValue != 10 && _vm.SliderValue != 0;
}

public
event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}

public
void Execute(object parameter)
{
    _vm.CanStopButtonExecute = true;
    System.Threading.ThreadPool.QueueUserWorkItem(_vm.Losowanie);
}
Wytłumaczenia wymagają dwie rzeczy. Pierwsza to co to jest losowanie. Losowanie to będzie metoda która będziemy jeszcze implementować w ViewModelu. Będzie ona odpowiedzialna za losowanie liczb. Drugą rzeczą dla niektórych może być tajemnicze wywołanie metody -System.Threading.ThreadPool.QueueUserWorkItem. Pozwala nam ono wykonać dana metodę w osobnym wątku które będzie „obliczać” swoje rzeczy niezależnie od głównego wątku naszej aplikacji. Więcej informacji znajdziecie we wcześniejszych wpisach z tamtego roku. Dlaczego tak ? Nie chcemy aby nad naszą aplikacja pojawiła się klepsydra :P Nie odpowiadałaby ona na nasze żądania.Teraz do rzeczy bo zostało nam trochę roboty jeszcze. Druga nasza klasa powinna wyglądać podobnie:

class
StopCommand : ICommand
{
    private readonly MainViewModel _vm;
    public StopCommand(MainViewModel viewModel)
    {
         _vm = viewModel;
    }

    public bool CanExecute(object parameter)
    {
         return _vm.CanStopButtonExecute;
    }

   
public event EventHandler CanExecuteChanged
    {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
    }

   
public void Execute(object parameter)
    {
         _vm.StopButtonClicked = true;
    }
}

Musimy teraz „podpiąć” dopiero co stworzone klasy do ViewModelu. Przechodzimy do niego i  tworzymy kolejne dwie właściwości i w konstruktorze przypisujemy do nich nowe obiekty:

public
ICommand Start { get; set; }
public ICommand Stop { get; set; }
public MainViewModel()
{
     Start = new StartCommand(this);
     Stop = new StopCommand(this);
}

Dokładnie do tych dwóch właściwości bindowaliśmy nasze przyciski poprzez Command.
Kolejnym krokiem ( obiecuje ze już nie wiele tego zostało ) jest utworzenie klasy która będzie nam losować liczby o danej długości jak i metoda o której wcześniej wspomniałem. Zaczniemy od klasy.
Tworzymy i nazywamy ja Losomat. Oto logika która jest w niej zawarta:

public static class Losomat
{
    private const int max = 999999999;
    private const int min = 100000000;
    private static readonly Random Random = new Random();

   
public static string Losuj(int ilosc)
    {
       return
       Random.Next(Convert.ToInt32(min / Math.Pow(10, 9 - ilosc)), Convert.ToInt32(max / Math.Pow(10, 9 - ilosc))).ToString();
    }
}

Mam nadzieje że wszytko w niej jest jasne.
Weźmiemy się teraz za metodę. Przechodzimy do ViewModelu i dodajemy.

public
void Losowanie(object param)
{
    for (int i = 0; i < SliderValue; i++)
    {
        Liczby += "0";
    }
    for (int i = SliderValue; i > 0; i--)
    {
        while (!StopButtonClicked)
        {
            var temp = Losomat.Losuj(i);
            Liczby = Liczby.Substring(0, SliderValue - i) + temp;
            System.Threading.Thread.Sleep(10);
        }
        StopButtonClicked = false;
     }
     CanStopButtonExecute = false;
}

Rzeczą która może się przydać z tego kawałka kodu może być System.Threading.Thread.Sleep. Jest to metoda która usypia dany watek na podana ilość czasu. Czas podajemy w milisekundach.
Ostatnia rzeczą jaka musimy wykonać jest złączenie Widoku z ViewModelem. Przechodzimy do Widoku, przed Gridem dodajemy przestrzeń nazw i definiujemy zasób dla naszego okna.

xmlns:Haslo="clr-namespace:Haslo"
<Window.Resources>
     <Haslo:MainViewModel x:Key="ViewModel"/>
</Window.Resources>

 Łączymy nasz nowy zasób z Gridem poprzez DataContext:

<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >

W ostateczność nasz początek kodu Widoku powinien wyglądać następująco :
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Haslo="clr-namespace:Haslo"
    x:Class="Haslo.MainWindow"
    x:Name="Window"
    Title="Haslo"
    Width="459" Height="204">    
    <Window.Resources>
           <Haslo:MainViewModel x:Key="ViewModel"/>
   </Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >
Po tych wszystkich bojach możemy przystąpić do kompilacji naszego programu i cieszyć się jego możliwościami :D Kod z komentarzami będzie można pobrać z linku niżej.

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: 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: , , , , ,

70-536 Globalization

Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.

Globalizacja to proces tworzenia aplikacji która wspomaga nasz “lokalny” interfejs. Przez lokalny należy rozumieć kulturę wyświetlania liczb, dat czy innych informacji. .NET Framework jest w stanie zrobić większość rzeczy za użytkownika, my jednak musimy zrozumieć jak korzystać z przewidzianych narzędzi. Na przykład w Stanach Zjednoczonych separatorem “miejsc po przecinku” (mówiąc po polsku ;) )jest kropka.

Ustawienia kultury

Możemy użyć dwóch właściwości do ustawienia kultury z jakiej chcemy skorzystać w naszym programie.

Thread.CurrentThread.CurrentCulture – nie można tutaj ustawić neutralnej kultury, określić samego języka w obiekcie CurrentCulture typu fr, en tylko trzeba wybrać ze zdefiniowanych kultur zarówno język jak i regionalne formatowanie typu fr-FR.

oraz

Thread.CurrentThread.CurrentUICulture - Określa, jakie zasoby są ładowane przez Resource Manager, jeśli przewidziane są środki w wielu językach. Ponieważ ten kontroluje tylko w jakim języku są używane, można zdefiniować CurrentUICulture z obu kultur neutralnie lub wyszczególnione.

Rozważmy następujący kod:

   1: //C#
   2: // Change the current culture
   3: Thread.CurrentThread.CurrentCulture = new CultureInfo("es-ES");
   4: Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-ES");
   5: MessageBox.Show(Thread.CurrentThread.CurrentCulture.ToString());
   6: double d = 1234567.89;
   7: // Show the figure as currency using the current culture
   8: MessageBox.Show(d.ToString("C"));
   9: // Show the current time using the current culture
  10: MessageBox.Show(DateTime.Now.ToString());

 

Ten kod przykładowy tworzy następujące dane wyjściowe dla kultury es-ES:

es-ES
1.234.567,89 €
17/08/2008 11:07:46

W poprzednim przykładzie na sztywno wpisaliśmy kulturę do naszego kodu. Jeśli jednak mamy przetłumaczoną aplikacje na język niemiecki, hiszpański i angielski to musimy użytkownikowi dać do wyboru tylko jeden z trzech języków. Można także pobrać tablicę wszystkich dostępnych kultur, wywołując metodę System.Globalization.CultureInfo.GetCultures. Oraz z typu wyliczeniowego CultureTypes wybrać interesujące nas kultury. Najbardziej przydatne wartości z CultureTypes:

AllCultures- wszystkie kultury które przewidziano w .NET Framework

NeutralCultures- neutralna kultura która zapewnia tylko język a nie formatowanie danych.

SpecificCultures- wyszczególniona kultura, język + formatowanie danych

Jak formatować dane wyjściowe z różnych kultur

Poniższy kod demonstruje wyświetlanie instancji DateTime w różnych kulturach.

   1: // C#
   2: // Display the current time using the default culture
   3: Console.WriteLine(DateTime.Now.ToString());
   4: // Display the current time using the English-Great Britain culture
   5: Console.WriteLine(DateTime.Now.ToString(new CultureInfo("en-GB")));
   6: // Display the current time using the Russian culture
   7: Console.WriteLine(DateTime.Now.ToString(new CultureInfo("ru-RU")));

 

Zakładając, że komputer  domyślnie jest ustawiony na en-US, poprzedni kod daje następujący wynik:

8/17/2008 10:06:31 PM
17/08/2008 20:06:31
17.08.2008 20:06:31

Zwróćmy uwagę na następujące rzeczy:

W kulturze en-US data wyświetlana jest w formacie mm/dd/yyyy i czas AM/PM

W kulturze en-GB data wyświetlana jest w formacie dd/mm/yyyy

W kulturze ru-RU data wyświetlana jest w formacie dd.mm.yyyy

Rozważmy teraz wyświetlanie walut dla tych samych kultur:

   1: // C#
   2: double d = 1234567.89;
   3: // Show the figure as currency using the default culture
   4: Console.WriteLine(d.ToString("C"));
   5: // Show the figure as currency using the English-Great Britain culture
   6: Console.WriteLine(d.ToString("C", new CultureInfo("en-GB")));
   7: // Show the figure as currency using the Russian culture
   8: Console.WriteLine(d.ToString("C", new CultureInfo("ru-RU")));

 

Analogicznie do poprzedniego przykładu i poprzednich kultur uzyskamy:

$1,234,567.89
£1,234,567.89
1 234 567,89p.

Ja sformatować dane ręcznie?

Jeśli chcemy edytować dane ręcznie musimy uzyskać informacje o danej kulturze, sposobie formatowania przy użyciu np. CultureInfo.NumberFormat lub CultureInfo.DateTimeFormat. Poniższy kod pokazuje jak używać NumberFormat.Number-DecimalSeparator, NumberFormat.NumberGroupSizes, i NumberFormat.NumberGroupSeparator do wyświetlania długich liczb.

   1: // C#
   2: public static void Main()
   3: {
   4: FormatData(new CultureInfo("es-ES"));
   5: FormatData(new CultureInfo("ru-RU"));
   6: FormatData(new CultureInfo("en-US"));
   7: Console.ReadKey();
   8: }
   9: private static void FormatData(CultureInfo ci)
  10: {
  11: // Display the selected culture
  12: Console.WriteLine(ci.ToString() + ":");
  13: // Identify the number and copy it to a string
  14: double d = 1234567.89;
  15: string formattedNumber = d.ToString();
  16: // Identify the location of the culture-sensitive decimal point in the number
  17: int decimalIndex = formattedNumber.IndexOf(
  18: Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator) + 1;
  19: // Extract only the decimal portion of the number
  20: formattedNumber = formattedNumber.Substring(
  21: decimalIndex, formattedNumber.Length - decimalIndex);
  22: // Add the culture-specific decimal point before the number
  23: formattedNumber = ci.NumberFormat.NumberDecimalSeparator + formattedNumber;
  24: // Extract only the whole portion of the number
  25: string wholeDigits = Math.Floor(d).ToString();
  26: // Add each whole digit to formattedNumber, with grouping separators
  27: for (int a = 0; a < wholeDigits.Length; a++)
  28: {
  29: // Examine CultureInfo.NumberFormat.NumberGroupSizes to determine
  30: // whether the current location requires a separator
  31: bool requiresSeparator = false;
  32: foreach (int sep in ci.NumberFormat.NumberGroupSizes)
  33: {
  34: if ( (a > 0) && ((a % sep) == 0) )
  35: {
  36: requiresSeparator = true;
  37: }
  38: }
  39: // Add a separator if required
  40: if (requiresSeparator)
  41: {
  42: formattedNumber = ci.NumberFormat.NumberGroupSeparator + formattedNumber;
  43: }
  44: // Add the number to the final string
  45: formattedNumber =
  46: wholeDigits.ToCharArray()[wholeDigits.Length - a - 1] + formattedNumber;
  47: }
  48: // Display the manual results and the automatically formatted version
  49: Console.WriteLine(" Manual: {0}", formattedNumber);
  50: Console.WriteLine(" Automatic: {0}", d.ToString("N", ci));
  51: }

Dane wyjściowe:

es-ES:
Manual: 1.234.567,89
Automatic: 1.234.567,89
ru-RU:
Manual: 1 234 567,89
Automatic: 1 234 567,89
en-US:
Manual: 1,234,567.89
Automatic: 1,234,567.89

Sortowanie i porównywanie obiektów

Mamy świadomość, że w zależności od kultury inaczej sortuje się alfabet (bo np. mogą występować różne specyficzne znaki) czy też będzie brana pod uwagę wielkość liter. Poniższy przykład pokazuje to na przykładzie znaku Æ dla dwóch kultur:

   1: // C#
   2: public static void Main()
   3: {
   4: string[] words = new string[] { "Apple", "Æble" };
   5: Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
   6: SortWords(words);
   7: Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
   8: SortWords(words);
   9: }
  10: private static void SortWords(string[] words)
  11: {
  12: Console.WriteLine(Thread.CurrentThread.CurrentCulture);
  13: Array.Sort(words);
  14: foreach (string s in words)
  15: {
  16: Console.WriteLine(s);
  17: }
  18: }

 

Na ekranie konsoli w wyniku dostaniemy:

en-US
Æble
Apple
da-DK
Apple
Æble

W traning kit jest ładna tabelka która pokazuje kultury obsługujące różne sortowania etc.

To tyle w tym temacie. Więcej informacji można znaleźć w traning kit a dokładnie mówiąc m.in opis wielu właściwości dla kultur…jest tego chyba z 3 strony oraz krótki opis jak stworzyć własną kulturę. Tym artykułem również kończymy na naszej stornie cykl artykułów przygotowujących do egzaminu 70-536. Artykuły te należy traktować jako ostatnie przypomnienie najważniejszych informacji przed samym egzaminem. Dzięki wszystkim którzy czytali artykuły, dzielili się swoimi uwagami i spostrzeżeniami. 

Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.

Tagi: , , ,

70-536: Sending E-mail

Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.

Jak wysłać wiadomość

Po utworzeniu wiadomości, musimy wysłać ją przez serwer SMTP, który prześle  wiadomość do odbiorcy. W .NET klasą, która reprezentuje serwer SMTP jest klasa SmtpClient. Aby wysłać wiadomość wywołujemy SmtpClient.Send. Najczęściej wysyłanie wiadomości jest tak proste jak w poniższym przykładzie(gdzie smtp.contoso.com jest nazwą lokalnego serwera SMTP):

   1: MailMessage m = new MailMessage
   2:    ("jane@northrup.org",
   3:     "ben@northrup.org",
   4:     "Quarterly data report.",
   5:     "Hello, world.");
   6: SmtpClient client = new SmtpClient("smtp.contoso.com");
   7: client.Send(m);

 

Obsługiwanie wyjątków E-mail

Podczas wysyłania wiadomości, wiele rzeczy może pójść nie tak. Na przykład: serwer może nie być dostępny, serwer może odrzucić poświadczenia użytkownika itp. W każdym takim przypadku runtime rzuca wyjątkiem a my musimy tak przygotować naszą aplikację, żeby ten wyjątek złapała i obsłużyła.

Kiedy wysyłamy wiadomości, powinniśmy być zawsze przygotowani na złapanie wyjątku SmtpException.
Wiadomości często są odrzucane ponieważ np. nie można odnaleźć serwera smtp, serwer identyfikuje wiadomość jako spam itd.
Powinniśmy również obsługiwać wyjątek SmtpFailedRecipientException, który runtime rzuca jeśli serwer smtp odrzuca email odbiorcy.
Poniższa tabelka podsumowuje wyjątki, które mogą być rzucone podczas pracy z mailami:

 

Sytuacja Wyjątek
Nie określono serwera hosta InvalidOperationException
Nazwa serwera hosta nie została znaleziona SmtpException z wewnętrznym WebException
Wysyłasz maila do odbiorcy na lokalnym serwerze ale odbiorca nie ma poczty SmtpFailedRecipientException
Niepoprawny user bądź są inne problemy z transmisją nieobjętych w innych wyjątkach SmtpException

Jak konfigurować poświadczenia

Aby zredukować spam, wszystkie serwery smtp powinny odrzucać wiadomości od nieautoryzowanych użytkowników. Serwery smtp dostarczane przez dostawców usług internetowych zwykle określają, który użytkownik jest uprawniony na podstawie jego adresu IP.
Inne serwery smtp wymagają od użytkownika dostarczenia nazwy użytkownika oraz hasła. Aby użyć domyślnych poświadczeń sieciowych, ustaw SmtpClient.UseDefaultCredentials na true. Alternatywnie można ustawić SmtpClient.Credentials na CredentialCache.DefaultNetworkCredentials, jak na poniższym przykładzie:

   1: SmtpClient client = new SmtpClient("smtp.contoso.com");
   2: client.Credentials = CredentialCache.DefaultNetworkCredentials;

Aby określić nazwę użytkownika i hasło, musimy stworzyć instancję klasy System.Net.NetworkCredential i użyć jej do zdefiniowania SmtpClient.Credentials. Poniższy przykład pokazuje zaprogramowane na stałe poświadczenia:

   1: SmtpClient client = new SmtpClient("smtp.contoso.com");
   2: client.Credentials = new NetworkCredential("user", "password");

Jak skonfigurować SSL

Inną bardzo ważną właściwością związaną z bezpieczeństwem jest SmtpClient.EnableSsl. Kiedy ustawimy tę właściwość na true, runtime  szyfruje połączenia SMTP używając SSL. Należy jednak pamiętać, że nie wszystkie serwery smtp wspierają SSL.

Jak wysyłać wiadomości asynchronicznie

Wysyłanie wiadomości e-mail często trwa mniej niż sekundę. Jednak często serwer smtp jest powolny lub w ogóle nie odpowiada. Wtedy nasza aplikacja czeka tyle ile określiliśmy w SmtpClient.Timeout. Choć aplikacja czeka wtedy na serwer smtp, użytkownik nie może robić nic w międzyczasie.
Na szczęście można wysyłać wiadomości asynchronicznie, czyli tak,  żeby użytkownik nie musiał czekać aż e-mail zostanie wysłany. Możemy również dać użytkownikowi opcję anulowania wysyłania.
Aby wysyłać wiadomość asynchronicznie należy:

1.  Stworzyć metodę SmtpClient.SendCompleted, która reaguje na zdarzenie. Metoda wymaga ustalenia czy transmisja się udała, nie udała bądź czy została anulowana.

2. Dodajemy obsługę zdarzenia SmtpClient.SendCompleted.

3. Wywołujemy SmptClient.SendAsync.

4. Jeśli chcesz udostępnić użytkownikowi anulowanie emaili, możesz to zrobić za pomocą metody SmtpClient.SendAsyncCancel.

Przykład:

   1: static void sc_SendCompleted(object sender, AsyncCompletedEventArgs e)
   2: {
   3:   if (e.Cancelled)
   4:     Console.WriteLine("Message cancelled");
   5:   else if (e.Error != null)
   6:     Console.WriteLine("Error: " + e.Error.ToString());
   7:   else
   8:     Console.WriteLine("Message sent");
   9: }

Poniższy kod tworzy obiekt SmtpClient, dodaje obsługę zdarzenia, wywołuje asynchroniczne wysyłanie a następnie natychmiast to wysyłanie anuluje. Oczywiście, w prawdziwym programie powinniśmy poczekać aż użytkownik sam anuluje wysyłanie :). Zakładamy że obiekt MailMessage o nazwie mm  już istnieje.

   1: SmtpClient sc = new SmtpClient("server_name");
   2: // Add the event handler
   3: sc.SendCompleted += new SendCompletedEventHandler(sc_SendCompleted);
   4: // Send the message asynchronously
   5: sc.SendAsync(mm, null);
   6: // Cancel the send
   7: sc.SendAsyncCancel();

SmtpClient.SendAsync akceptuje dwa parametry: obiekt MailMessage do wysłania oraz jakiś drugi, który jest obiektem wyłącznie do własnego użytku.

 

To wszystko na dzisiaj i to wszystko z mojej strony w tej serii artykułów. W środę ukaże się ostatni artykuł z serii 70-536. Dzięki za uwagę i być może do “usłyszenia” w następnej serii.

Kolejny artykuł z serii to Formatowanie danych za pomocą mechanizmu globalizacji

Tagi: , ,

Eastgroup.pl na facebooku