Rozdział 19.
Oddzielanie kodu od treści
W osiemnastu poprzednich rozdziałach książki przedstawionych zostało kilka sposobów na oddzielanie interfejsu użytkownika stron ASP.NET od kodu który go obsługuje (napisanego w języku VB.NET). Przykładowo, zostały przedstawione sposoby przenoszenia kodu, który nie jest bezpośrednio związany z interfejsem użytkownika do obiektów biznesowych, dzięki czemu strony ASP.NET mogą zawierać wyłącznie kod związany z obsługą prezentacji danych. Co więcej, wszelkie polecenia SQL można zapisać w bazie danych w formie procedur zachowanych i usunąć je ze stron ASP.NET (zagadnienia te zostały omówione w rozdziale 12., pt.: „Zastosowanie zaawansowanych technik obsługi danych”). Wszelkie ustawienia i zmienne można natomiast zapisać w plikach konfiguracyjnych, takich jak web.config.
W tym rozdziale zostanie przedstawionych kilka bardziej zaawansowanych metod separacji kodu źródłowego od zawartości strony, czyli kodu kontrolującego działanie aplikacji od kodu odpowiedzialnego za prezentację danych (na przykład: elementów sterujących HTML oraz internetowych elementów sterujących). Programiści ASP.NET bardzo oczekiwali możliwości takiej separacji, gdyż dzięki nim można uprościć strony i logicznie zgrupować wykorzystywany w nich kod. Przecież strony ASP.NET służą wyłącznie do prezentacji interfejsu użytkownika, a zatem po co miałby być w nich umieszczany kod o innym przeznaczeniu?
W tym rozdziale poznamy także sposoby dostosowywania stron ASP.NET do pochodzenia użytkownika. Metody te pozwalają na modyfikację zawartości stron ASP.NET zupełnie niezależnie od wydzielonego kodu stron.
W tym rozdziale przedstawionych zostanie bardzo wiele przykładów, a zatem… zaczynajmy!
W tym rozdziale zostaną omówione następujące zagadnienia:
Czym jest kod obsługi formularzy?
Jak używać kodu obsługi przy tworzeniu stron ASP.NET.
W jaki sposób elementy sterujące użytkownika mogą korzystać z kodu obsługi.
Jak określić pochodzenie użytkownika (na podstawie używanego języka).
Jak określić informacje o kulturze i regionie użytkownika.
W jaki sposób można wydzielić ze stron ASP.NET najczęściej używane łańcuchy znaków i zapisać je w niezależnych plikach zasobów.
Potrzeba rozdzielania różnych rodzajów kodu
Być może przypominasz sobie z dyskusji przedstawionej w rozdziale 2., że ASP.NET stara się uprościć życie programistom umożliwiając niezależne grupowanie kodu ASP.NET oraz kodu HTML. Na przykład, większość (o ile nie cały) kod ASP.NET powinien być umieszczany w blokach SCRIPT na samym początku stron ASP.NET i oddzielony od kodu HTML. Oddzielenie obu rodzajów kodu zostało przedstawione na rysunku 19.1.
Kod ASP.NET
<%@Page Language="VB" %>
<%@Import Namespace="System.Xml" %>
<%@Import Namespace="System.Xml.XPath" %>
<script runat="server">
sub SelectData(obj as object,e as eventargs)
Dim objDocument as New XPathDocument _
(Server.MapPath("../r11/books.xml"))
Dim objNav as XPathNavigator = objDocument.CreateNavigator
lblMessage.Text = ""
try
dim objIterator as XPathNodeIterator = _
objNav.Select(tbQuery.Text)
While objIterator.MoveNext()
lblMessage.Text += "<" & _
objIterator.Current.Name & "> " & _
objIterator.Current.Value & "<br>"
end while
catch ex As Exception
lblMessage.Text = ex.Message
end try
end sub
</script>
Kod HTML
<html><body>
<form runat="server">
<h2>Zapytania XPath</h2>
<p>
Na przyład:<br>
<b><code>//book[last()]/@ISBN/text()</b></code> lub
<b><code>descendant::book/author/last-name</b></code><p>
Podaj zapytanie XPath:
<asp:Textbox id="tbQuery" runat=server/>
<asp:Button id="btnSubmit" text="Wykonaj zapytanie"
runat=server OnClick="SelectData"/><p>
<asp:Label id="lblMessage" runat=server/>
</form>
</body></html>
Rysunek 19.1. Idealny sposób zapisu stron ASP.NET
Opis rysunku
ASP.NET Code — Kod ASP.NET
(kod ASP.NET został oznaczony jako górna ramka)
HTML Code — Kod HTML
(kod HTML został oznaczony jako dolna ramka)
Tworzenie kodu w sposób przedstawiony na rysunku 19.1 jest bardzo korzystne i to z wielu powodów — łatwiej można modyfikować zarówno kod ASP.NET jak i kod HTML, kod źródłowy strony staje się w ten sposób bardziej logiczny i nie trzeba szukać w nim bloków kodu wykonywalnego. Niemniej jednak możliwości rozdzielania różnych rodzajów kodu jakie daje ASP.NET są jeszcze większe. Dzięki wykorzystaniu kodu obsługi oraz plików zasobów można całkowicie oddzielić kod od zawartości strony. Pierwsze z powyższych rozwiązań umożliwia zapisywanie kodu w niezależnych plikach, natomiast drugie — zgrupowanie często wykorzystywanych wartości (takich jak na przykład komunikaty o błędach) i zapisanie ich w jednym miejscu, niezależnym od strony ASP.NET.
Kod obsługi formularzy
Patrząc na rysunek 19.1 nie trudno zauważyć korzyści płynące z oddzielenia kodu od treści strony. Ale co by się stało, gdyby można było usunąć całą początkową zawartość pliku .aspx? Dzięki temu istniałaby możliwość faktycznego oddzielenia kod ASP.NET do zawartości strony. Technologia ASP.NET pozwala na osiągnięcie takiej separacji dzięki wykorzystaniu kodu obsługi formularzy. Kod obsługi formularzy jest sposobem na całkowite oddzielenie każdego i całego kodu ASP.NET od kodu interfejsu użytkownika. Teraz zamiast jednego pliku z rysunku 19.1 otrzymujemy dwa pliki przedstawione na rysunku 19.2.
Wyłącznie kod HTML
<%@ Page Inherits="CodeBehind2" src="CodeBehind2.vb" %>
<html><body>
<form runat="server">
<asp:Calendar id="Calendar1" runat="server"
OnSelectionChanged="DateChanged"
Cellpadding="5" Cellspacing="5"
DayHeaderStyle-Font-Bold="True"
DayNameFormat="Short"
Font-Name="Arial" Font-Size="12px"
height="250px"
NextPrevFormat="ShortMonth"
NextPrevStyle-ForeColor="white"
SelectedDayStyle-BackColor="#ffcc66"
SelectedDayStyle-Font-Bold="True"
SelectionMode="DayWeekMonth"
SelectorStyle-BackColor="#99ccff"
SelectorStyle-ForeColor="navy"
SelectorStyle-Font-Size="9px"
ShowTitle="true"
TitleStyle-BackColor="#ddaa66"
TitleStyle-ForeColor="white"
TitleStyle-Font-Bold="True"
TodayDayStyle-Font-Bold="True" />
</form>
Wybrałeś:
<asp:Label id="lblMessage" runat="server"/>
</body></html>
Wyłącznie kod ASP.NET (kod obsługi formularza)
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Public Class CodeBehind2 : Inherits Page
public lblMessage as Label
public Calendar1 as Calendar
Public sub Page_Load(obj as object, e as eventargs)
if not Page.IsPostBack then
Calendar1.SelectedDate = DateTime.Now
lblMessage.Text = Calendar1.SelectedDate. _
ToString("dddd, MMMM dd yyyy")
end if
End Sub
Public sub DateChanged(obj as object, e as eventargs)
if Calendar1.SelectedDates.Count > 1 then
lblMessage.Text = Calendar1.SelectedDates(0). _
ToString("dddd, MMMM dd yyyy") & " do " & _
Calendar1.SelectedDates(Calendar1.SelectedDates. _
Count - 1).ToString("dddd, MMMM dd yyyy")
else
lblMessage.Text = Calendar1.SelectedDate. _
ToString("dddd, MMMM dd yyyy")
end if
End Sub
End Class
Rysunek 19.2. Dzięki kodowi obsługi formularzy ASP.NET pozwala na całkowite oddzielenie interfejsu użytkownika od logiki programu
Opis rysunku
HTML … — Wyłącznie kod HTML
ASP.NET … — Wyłącznie kod ASP.NET (kod obsługi formularza)
Przeanalizujmy jeszcze raz sposób działania ASP.NET, abyśmy lepiej zrozumieli model programowania wykorzystujący kod obsługi.
Gdy klient po raz pierwszy przesyła żądanie dotyczące jakiejś strony ASP.NET (pliku z rozszerzeniem .aspx), ASP.NET przetwarza tę stronę i analizuje wszystkie wykorzystywane na niej komponenty (na przykład, elementy sterujące serwera). Następnie ASP.NET tworzy klasę dynamiczną dla danego pliku .aspx. To właśnie tak klasa jest następnie kompilowana, wykonywana i to ona generuje kod HTML przesyłany następnie do klienta. Musi to być kasa potomna klasy System.Web.UI.Page, która zawiera definicje wykorzystywane przez wszystkie stron ASP.NET. Cały ten proces jest wykonywany w sposób całkowicie niezauważalny, w momencie zgłoszenia żądania dotyczącego strony ASP.NET.
Jednak plik .aspx nie musi być bezpośrednią klasą potomną klasy Page — o ile tylko w jakikolwiek sposób będzie dziedziczyć po tej klasie, to wszystko będzie w porządku. Oznacza to, że można stworzyć pewną klasę pośrednią, dziedziczącą po klasie Page i zażądać, aby plik .aspx dziedziczył po tej klasie pośredniej. Ta nowa klasa pośrednia może udostępniać dowolne możliwości funkcjonalne, które będą dostępne dla plików .aspx. Wzajemne relacje pomiędzy tymi wszystkimi klasami zostały przedstawione na rysunku 19.3.
Rysunek 19.3. Strona ASP.NET musi być bezpośrednią, lub pośrednią klasą potomną klasy System.Web.UI.Page
Opis rysunku
System.Web.UI.Page — System.Web.UI.Page
Intermediate class — Klasa pośrednia
derive directly — dziedziczy bezpośrednio
… or indirectly — … lub pośrednio
Dynamic … — Klasa dynamiczna utworzona na podstawie pliku .aspx
Send… — Przesyła wyniki do przeglądarki
Ta nowa klasa pośrednia jest naszym kodem obsługi formularzy. Definiuje ona możliwości funkcjonalne z których może korzystać strona ASP.NET. Jednak przy tak dużej ilości wykorzystywanych klas łatwo będzie się można pogubić. W zasadzie wszystko sprowadza się do tego, iż strona ASP.NET musi być klasą potomną klasy Page, lecz od programisty zależy określenie hierarchii tego pokrewieństwa.
Wprowadzenie klasy kodu obsługi nie daje żadnych oczywistych korzyści. Strona kodu obsługi nie zawiera żadnych własnych możliwości funkcjonalnych, a zatem dziedzicząc po niej strona ASP.NET niczego nie zyskuje. Strona kodu obsługi także w żaden sposób nie wspomaga wykonywania stron ASP.NET. Niemniej jednak wykorzystanie tej strony pozwala na przeniesienie kodu do klasy pośredniej, dzięki czemu w stronie ASP.NET może pozostać znacznie uproszczony kod obsługi interfejsu użytkownika.
Wykorzystanie kodu obsługi w stronach ASP.NET
Stworzenie kodu obsługi formularza jest niezwykle proste; cały proces przypomina tworzenie obiektów biznesowych, z tą różnicą iż niczego nie trzeba kompilować. Niemniej jednak trzeba przedsięwziąć pewne środki bezpieczeństwa, aby zapewnić że wszystko będzie działać poprawnie. Listing 19.1 przedstawia typową postać strony ASP.NET prezentującej informacje pobierane z bazy danych. Za chwilę, na podstawie tej strony wygenerujemy kod obsługi formularza.
Listing 19.1. --> Typowa strona ASP.NET zawiera zarówno kod programu jak i kod HTML.[Author:p8R]
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<script runat="server">
'deklarujemy połączenie
dim strConnString as string = "Provider=" & _
"Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\ASPNET\data\banking.mdb"
dim objConn as new OleDbConnection(strConnString)
sub Page_Load(obj as Object, e as EventArgs)
if Not Page.IsPostBack then
FillDataGrid()
end if
end sub
private sub FillDataGrid(Optional EditIndex as integer=-1)
'otwieramy połączenie
dim objCmd as OleDbCommand = new OleDbCommand _
("select * from tblUsers", objConn)
dim objReader as OleDbDataReader
try
objCmd.Connection.Open()
objReader = objCmd.ExecuteReader
catch ex as OleDbException
lblMessage.Text = "Błąd pobierania informacji z bazy danych."
end try
DataGrid1.DataSource = objReader
DataGrid1.DataBind()
objReader.Close
objCmd.Connection.Close()
end sub
</script>
<html><body>
<form runat="server">
<asp:Label id="lblMessage" runat="server" />
<asp:DataGrid id="DataGrid1" runat="server"
BorderColor="black"
GridLines="Vertical"
cellpadding="4"
cellspacing="0"
width="100%"
Font-Name="Arial"
Font-Size="8pt"
HeaderStyle-BackColor="#cccc99"
ItemStyle-BackColor="#ffffff"
AlternatingItemStyle-Backcolor="#cccccc"
AutoGenerateColumns="True" />
</asp:DataGrid><p>
</form>
</body></html>
Analiza
Kod przedstawiony na powyższym listingu nawiązuje połączenie z bazą danych stworzoną w --> rozdziale 8[Author:p8R] , pt.: „Podstawowe wiadomości na temat tworzenia baz danych”. W wierszach od 7. do 9. jest tworzony łańcuch zapytania, a w wierszu 10. obiekt OleDbConnection. W procedurze Page_Load wywoływana jest procedura FillDataGrid, która pobiera informacje z bazy danych i wiąże je z elementem sterujący DataGrid zdefiniowanym w wierszach od 43. do 55. Wykonanie powyższej strony powoduje wygenerowanie wyników przedstawionych na rysunku 19.4.
Rysunek 19.4. Prosta strona ASP.NET pobierająca informacje z bazy danych.
A teraz, bazując na kodzie przedstawionym na listingu 19.1, stworzymy kod obsługi formularza. W pierwszej kolejności należy stworzyć klasę potomną klasy System.Web.UI.Page. Oto przykład szkieletu takiej klasy:
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Data
Imports System.Data.OleDb
Public Class CodeBehind1 : Inherits Page
`tu zostanie wstawiony kod klasy
End Class
Powyższy szkielet klasy będzie podstawą do usunięcia kodu skryptu ze strony przedstawionej na listingu 19.1. Należy pamiętać, że tworząc klasę języka VB.NET należy własnoręcznie zaimportować wszystkie przestrzenie nazw, które w ASP.NET były importowane automatycznie. Dodatkowo trzeba także zaimportować przestrzenie nazw System.Data oraz System.Data.OleDb, gdyż tworzona strona ASP.NET będzie z nich korzystać przy wyświetlaniu danych.
Kolejnym krokiem jest stworzenie wszystkich zmiennych publicznych, których będziemy potrzebować. Ze względu na naturę kodu obsługi formularzy, proces ten będzie się nieco różnić do tego, do czego jesteśmy przyzwyczajeni. Przyjrzyjmy się nieco dokładniej temu zagadnieniu. Klasa kodu obsługi będzie używana do kontroli interfejsu użytkownika (czyli, formularza internetowego) — zawiera ona całą logikę konieczną do obsługi zdarzeń generowanych przez interfejs użytkownika. Jednak klasa ta nie zawiera żadnych elementów interfejsu użytkownika — bez wyjątków są one umieszczane w kodzie strony .aspx. A zatem mamy problem. Klasa kodu obsługi musi kontrolować interfejs użytkownika, który w całości jest definiowany w odrębnym pliku. W jaki sposób można to zrobić?
Zaraz poznasz powód dla którego strona ASP.NET ma być klasą potomną klasy kodu obsługi. Otóż jeśli wszystkie konieczne elementy interfejsu użytkownika zastaną zadeklarowane w klasie kodu obsługi, to plik .aspx odziedziczy je. Oznacza to, że kod obsługi stworzy i obsłuży wszystkie elementy interfejsu użytkownika, lecz nie będzie ich wyświetlać. Za wyświetlenie elementów interfejsu użytkownika wciąż będzie odpowiedzialna strona ASP.NET. Powyższa idea została zilustrowana na rysunku 19.5.
Rysunek 19.5. Kod obsługi deklaruje elementy interfejsu użytkownika oraz logikę ich obsługi, natomiast strona ASP.NET wyłącznie je wyświetla
Opis rysunku
Code-behind form — Kod obsługi formularza
ASP.NET Page — Strona ASP.NET
reszta bez zmian
Przenieśmy zatem kod z listingu 19.1 do kodu obsługi formularza przedstawionego na listingu 19.2.
Listing 19.2. Kod obsługi zawiera wszelkie możliwości funkcjonalne związane z obsługą interfejsu użytkownika (CodeBehind1.vb)
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Data
Imports System.Data.OleDb
Public Class CodeBehind1 : Inherits Page
'deklarujemy zmienne publiczne, które oddziedziczy
'plik .aspx.
public lblMessage as Label
public DataGrid1 as DataGrid
'deklarujemy łańcuch połączenia
private strConnString as string = "Provider=" & _
"Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\ASPNET\data\banking.mdb"
private objConn as new OleDbConnection(strConnString)
sub Page_Load(obj as Object, e as EventArgs)
if Not Page.IsPostBack then
FillDataGrid()
end if
end sub
private sub FillDataGrid(Optional EditIndex as integer=-1)
'otwieramy połączenie
dim objCmd as OleDbCommand = new OleDbCommand _
("select * from tblUsers", objConn)
dim objReader as OleDbDataReader
try
objCmd.Connection.Open()
objReader = objCmd.ExecuteReader
catch ex as OleDbException
lblMessage.Text = "Błąd przy pobieraniu danych z bazy."
end try
DataGrid1.DataSource = objReader
DataGrid1.DataBind()
objReader.Close
objCmd.Connection.Close()
end sub
End Class
Analiza
Zapisz powyższy kod w pliku o nazwie CodeBehind1.vb. W wierszach od 1. do 6. są importowane niezbędne przestrzenie nazw, a w wierszu 8. rozpoczyna się deklaracja klasy kodu obsługi. Zwróć uwagę, iż nosi ona nazwę CodeBehind1 i jest klasą potomną klasy Page.
Na stronie ASP.NET z listingu 19.1 zostały użyte dwa elementy sterujące serwera — etykieta o nazwie lblMessage oraz element DataGrid nazwie DataGrid1. W naszej klasie kodu obsługi elementy te zostały zadeklarowane jako zmienne (odpowiednio w wierszach 10. i 11.). Teraz strona ASP.NET odziedziczy te elementy oraz wszelkie możliwości funkcjonalne zdefiniowane w klasie. Pozostała część kodu nie różni się niczym do zawartości strony z listingu 19.1; oczywiście nie dotyczy to kodu HTML który w pliku CodeBehind1.vb nie został umieszczony. Teraz przyjrzyjmy się kodowi strony .aspx, która będzie dziedziczyć po klasie kodu obsługi.
Listing 19.3. Dziedziczenie po klasie kodu obsługi.
<%@ Page Inherits="CodeBehind1" src="CodeBehind1.vb" %>
<html><body>
<form runat="server">
<asp:Label id="lblMessage" runat="server" />
<asp:DataGrid id="DataGrid1" runat="server"
BorderColor="black" GridLines="Vertical"
cellpadding="4" cellspacing="0"
width="100%" Font-Name="Arial"
Font-Size="8pt"
HeaderStyle-BackColor="#cccc99"
ItemStyle-BackColor="#ffffff"
AlternatingItemStyle-Backcolor="#cccccc"
AutoGenerateColumns="True" />
</asp:DataGrid><p>
</form>
</body></html>
Analiza
Zapisz powyższy fragment kodu w pliku o nazwie listing1903.aspx i umieść go w tym samym folderze, w którym został zapisany plik zawierający klasę kodu obsługi. Za wyjątkiem pierwszego wiersza powyższy kod niczym nie różni się od fragmentu kodu HTML z listingu 19.1. Jednak w pierwszym wierszu pojawił się nowy element — dyrektywa @ Page z atrybutami Inherits oraz src. Pierwszy z tych atrybutów — Inherits — określa klasę nadrzędną, po której będzie dziedziczyć strona ASP.NET. W naszym przypadku chcemy, aby klasą nadrzędną strony była klasa kodu obsługi — CodeBehind1. Atrybut src zawiera ścieżkę dostępu do pliku kodu obsługi (oznacza to, iż plik zawierający kod obsługi oraz plik .aspx nie muszą być umieszczone w tym samym folderze). I to wszystko! Wystarczy wyświetlić tę stronę w przeglądarce, a uzyskane wyniki nie powinny się niczym różnić od wyników przedstawionych na rysunku 19.4.
Elementy sterujące serwera DataGrid oraz Label z listingu 19.3 są kopiami zmiennych klasowych (klasy CodeBehind1), zadeklarowanych na listingu 19.2. A zatem, dowolny kod umieszczony wewnątrz klasy kodu obsługi i odwołujący się do tych zmiennych, będzie miał pełen dostęp do wszystkich właściwości odpowiednich elementów sterujących formularza internetowego (takich jak tekst wyświetlany przez etykietę).
Przyjrzyjmy się teraz kodowi obsługującemu zdarzenia generowane przez formularz. Tym razem oba pliki zostaną utworzone od samego początku. Listing 19.4 przedstawia kod strony ASP.NET definiującej interfejs użytkownika.
Notatka
Nic nie stoi na przeszkodzi, aby elementy interfejsu użytkownika oraz logika działania strony ASP.NET były umieszczone w tym samym pliku. Kod programu nie musi być w całości umieszczany w klasie kodu obsługi, a w pliku kodu obsługi można umieszczać elementy interfejsu użytkownika. Co więcej, elementy interfejsu użytkownika deklarowane w kodzie obsługi, mogą być obsługiwane przez kod zdefiniowany w pliku .aspx. Niemniej jednak nie ma żadnego powodu, aby umieszczać kod w stronie ASP.NET jeśli można go zaimplementować jako kod obsługi.
Listing 19.4. Plik .aspx bez kodu ASP.NET.
<%@ Page Inherits="CodeBehind2" src="CodeBehind2.vb" %>
<html><body>
<form runat="server">
<asp:Calendar id="Calendar1" runat="server"
OnSelectionChanged="DateChanged"
Cellpadding="5" Cellspacing="5"
DayHeaderStyle-Font-Bold="True"
DayNameFormat="Short"
Font-Name="Arial" Font-Size="12px"
height="250px"
NextPrevFormat="ShortMonth"
NextPrevStyle-ForeColor="white"
SelectedDayStyle-BackColor="#ffcc66"
SelectedDayStyle-Font-Bold="True"
SelectionMode="DayWeekMonth"
SelectorStyle-BackColor="#99ccff"
SelectorStyle-ForeColor="navy"
SelectorStyle-Font-Size="9px"
ShowTitle="true"
TitleStyle-BackColor="#ddaa66"
TitleStyle-ForeColor="white"
TitleStyle-Font-Bold="True"
TodayDayStyle-Font-Bold="True" />
</form>
Wybrałeś:
<asp:Label id="lblMessage" runat="server"/>
</body></html>
Powyższy listing zawiera element sterujący serwera Calendar oraz etykietę. Choć plik kodu obsługi nie został jeszcze utworzony, to jednak nazwa klasy oraz ścieżka dostępu do pliku zostały już podane w dyrektywnie @ Page umieszczonej w pierwszym wierszu. W ten sposób już zawczasu wiadomo jaki plik należy utworzyć. W wierszu 6. określana jest procedura obsługi zdarzenia SelectionChanged kalendarza (elementu sterującego serwera Calendar). Należy także zwrócić uwagę na nazwy obu elementów sterujących — Calendar1 oraz lblMessage.
Próba wyświetlenia tej strony bez wcześniejszego utworzenia kodu obsługi, spowoduje wystąpienie błędów. Błędy te zostaną zgłoszone z dwóch powodów. Po pierwsze ASP.NET będzie szukać klasy kodu obsługi podanej w atrybucie Inherit dyrektywy @ Page lecz nie będzie w stanie jej znaleźć. Po drugie, do obsługi zdarzenia SelectionChanged ma być wykorzystana procedura DateChanged, która nigdzie nie została zdefiniowana. Aby rozwiązać te problemy, wystarczy stworzyć kod obsługi przedstawiony na listingu 19.5.
Listing 19.5. Kontrola zdarzeń generowanych przez element sterujący kalendarza (CodeBehind2.vb).
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Public Class CodeBehind2 : Inherits Page
public lblMessage as Label
public Calendar1 as Calendar
Public sub Page_Load(obj as object, e as eventargs)
if not Page.IsPostBack then
Calendar1.SelectedDate = DateTime.Now
lblMessage.Text = Calendar1.SelectedDate. _
ToString("dddd, MMMM dd yyyy")
end if
End Sub
Public sub DateChanged(obj as object, e as eventargs)
if Calendar1.SelectedDates.Count > 1 then
lblMessage.Text = Calendar1.SelectedDates(0). _
ToString("dddd, MMMM dd yyyy") & " do " & _
Calendar1.SelectedDates(Calendar1.SelectedDates. _
Count - 1).ToString("dddd, MMMM dd yyyy")
else
lblMessage.Text = Calendar1.SelectedDate. _
ToString("dddd, MMMM dd yyyy")
end if
End Sub
End Class
Analiza
Zapisz powyższy fragment kodu w pliku o nazwie CodeBehind2.vb. W wierszu 6. deklarowana jest klasa kodu obsługi o nazwie CodeBehind2; co odpowiada nazwie podanej w atrybucie Inherit dyrektywy @ Page z listingu 19.4. W wierszach 7. i 8. tworzone są dwie zmienne publiczne — lblMessage oraz Calendar1. Należy zwrócić uwagę, iż są to te same nazwy które nadaliśmy elementom sterującym w pliku .aspx. Procedura Page_Load zapisana w wierszach od 10. do 16. wybiera w kalendarzu dzisiejszą datę i wyświetla stosowny komunikat wykorzystując w tym celu element sterujący etykiety.
I w końcu, metoda DataChanged której kod rozpoczyna się w wierszu 18. służy do obsługi zdarzeń SelectionChanged kalendarza (patrz wiersz 6. listingu 19.4). Metoda ta wyświetla na stronie datę w rozbudowanym formacie. Jeśli jednocześnie zostanie wybranych kilka dni (co można sprawdzić za pomocą właściwości SelectedDates.Count), to chcemy poinformować użytkownika o tym fakcie wyświetlając na stronie odpowiedni przedział dat (patrz wiersze od 19. do 23.). W przeciwnym przypadku wyświetlana jest jedna — wybrana data (patrz wiersze 25. i 26.). Teraz wyświetlenie w przeglądarce strony z listingu 19.4 da rezultaty przedstawione na rysunku 19.6.
Rysunek 19.6. Kod obsługi formularza obsługuje zdarzenia generowane na stronie ASP.NET
Teraz możesz już całkowicie oddzielić kod obsługujący formularze internetowe od warstwy prezentacji danych.
Wykorzystanie kodu obsługi w elementach sterujących użytkownika
Wykorzystanie kodu obsługi w elementach sterujących użytkownika różni się nieco od wykorzystania go do obsługi interfejsu użytkownika stron ASP.NET. Konkretnie rzecz biorąc, w tym przypadku, kod obsługi musi być klasą potomną klasy System.Web.UI.UserControl (a nie klasy Page). Więcej informacji na temat tworzenia i wykorzystania elementów sterujących użytkownika można znaleźć w rozdziale 6., pt.: „Ciąg dalszy wiadomości na temat tworzenia formularzy internetowych”. Na listingach od 19.6 do 19.8 przedstawiony został zmodyfikowany kod aplikacji kalkulatora stworzonej w rozdziale 2., w którym został wykorzystany element sterujący użytkownika oraz kod obsługi formularza.
Listing 19.6. Element sterujący użytkownika (Calculator.ascx).
<%@ Control Inherits="CalculatorControl" src="Calculator.vb" %>
Liczba 1: <asp:textbox id="tbNumber1" runat="server"/><br>
Liczba 2: <asp:textbox id="tbNumber2" runat="server"/><p>
<asp:button id="btAdd" runat="server" Text="+"
OnClick="btOperator_Click" />
<asp:button id="btSubtract" runat="server" Text="-"
OnClick="btOperator_Click"/>
<asp:button id="btMultiply" runat="server" Text="*"
OnClick="btOperator_Click"/>
<asp:button id="btDivide" runat="server" Text="/"
OnClick="btOperator_Click"/><p>
Wynik:
<asp:label id="lblMessage" runat="server"/>
Analiza
Zapisz powyższy fragment kodu w pliku o nazwie Calculator.ascx. Element sterujący użytkownika przedstawiony na powyższym listingu jest bardzo prosty. Zawiera on kod HTML wyświetlający dwa pola tekstowe w których użytkownicy mogą wpisywać liczby, cztery przyciski umożliwiające wykonywanie podstawowych operacji arytmetycznych oraz etykietę służącą do wyświetlania wyników. W pierwszym wierszu powyższego kodu, zamiast dyrektywy @ Page została umieszczona dyrektywa @ Control, jednak składnia jej zapisu jest identyczna — także w tym przypadku klasa kodu obsługi jest określana przy użyciu atrybutu Inherits a nazwa pliku w jaki został on zapisany — przy użyciu atrybutu src. Koniecznie należy zapamiętać nazwy wszystkich elementów sterujących, gdyż będą one konieczne przy tworzeniu kodu obsługi formularza. Plik kodu obsługi dla powyższego elementu sterującego użytkownika został przedstawiony na listingu 19.7.
Listing 19.7. Klasa kod obsługi (Calculator.vb).
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Public Class CalculatorControl : Inherits UserControl
public lblMessage as Label
public btAdd as Button
public btSubtract as Button
public btMultiply as Button
public btDivide as Button
public tbNumber1 as TextBox
public tbNumber2 as TextBox
Sub btOperator_Click(obj as object, e as eventargs)
lblMessage.Text = Operate(CType(obj, Button).Text, _
tbNumber1.Text, tbNumber2.Text).ToString
End Sub
private function Operate(operator as string, number1 as string, optional number2 as string = "1") as double
select case operator
case "+"
Operate = CDbl(number1) + CDbl(number2)
case "-"
Operate = CDbl(number1) - CDbl(number2)
case "*"
Operate = CDbl(number1) * CDbl(number2)
case "/"
Operate = CDbl(number1) / CDbl(number2)
end select
end function
End Class
Analiza
Zapisz powyższy kod w pliku o nazwie Calculator.vb. Możliwości funkcjonalne zaimplementowane w tym przykładzie są znacznie bardziej skomplikowane do tych z listingu 19.5, niemniej jednak sposób ich zapisu jest identyczny. W pierwszej kolejności, w wierszach do 1. do 4., są importowane niezbędne przestrzenie nazw. W wierszu 6. rozpoczyna się deklaracja klasy CalculatorControl, będącej klasą potomną klasy UserControl (a nie klasy Page). Następnie, w wierszach od 7. do 13. zostały zadeklarowane publiczne zmienne klasowe odpowiadające elementom sterującym wykorzystywanym w tworzonym elemencie sterującym użytkownika.
Gdy użytkownik kliknie jeden z przycisków elementu sterującego, zostanie wykonana procedura obsługi zdarzenia o nazwie btOperator_Clicked. Procedura ta wywoła metodę Operate pobierającą trzy argumenty — operację jaką należy wykonać oraz dwa operandy. W momencie przesyłania żądania, przycisk jaki spowodował zgłoszenie zdarzenia będzie reprezentowany przez zmienną obj (patrz wiersz 15.) Przyjrzyjmy się pierwszemu argumentowi wywołania metody Operate zapisanemu w wierszu 16.:
Ctype(obj, Button).Text
Powyższe wyrażenie pobiera wartość właściwości Text przycisku, który spowodował zgłoszenie zdarzenia (na przykład, dla przycisku btAdd będzie to „+”). Jeśli tą samą czynności mielibyśmy wykonać w kodzie strony ASP.NET, to wystarczyłoby użyć wyrażenia obj.Text, niezależnie od tego, że klasa Object nie dysponuje właściwością o nazwie Text. Jednak klasy języka VB.NET działają inaczej niż strony ASP.NET. Konkretnie rzecz biorąc nie dysponują one możliwością późnego łączenia.
Nowe wyrażenie
Późne łączenie (ang.: late binding) oznacza, że zmienne typu Object (takie jak obj) nie są przetwarzane aż do czasu wykonania programu. Aż do tego momentu można ich używać do reprezentacji obiektów dowolnych typów. To właśnie z tego względu można użyć wyrażenia obj.Text pomimo faktu, iż klasa Object nie dysponuje właściwością o tej nazwie. ASP.NET zezwoli na wykorzystania wyrażenia obj.Text gdyż wie, że w czasie obsługi żądania zmienna obj może się stać obiektem klasy Button (gdyby się jednak nie stała, to moglibyśmy mieć poważne problemy).
Gdyby mechanizm późnego łączenia nie był wykorzystywany, to klasa Object byłaby od razu przetwarzana a kompilator zgłosiłby błąd zaraz po określeniu że została podjęta próba uzyskania dostępu do właściwości której nie ma (klasa Object nie posiada bowiem żadnej właściwości o nazwie Text). Właśnie z tego powodu należy sprawić, aby kompilator VB.NET sądził że zmienna obj jest przyciskiem jeszcze zanim zostanie przesłane żądanie, gdyż dzięki temu będzie można wykorzystać w kodzie właściwość Text. Metoda CType rzutuje jeden typ danych na inny, a wyrażenie użyte w wierszu 16. rzutuje zmienną typu Object na zmienną typu Button. Może się wydawać, że jest to dosyć złożone rozwiązanie, jednak gdy stworzysz więcej stron ASP.NET i zdobędziesz więcej informacji na temat łączenia, wszystko stanie się znacznie prostsze.
Metoda Operate zdefiniowana w wierszach od 20. do 31. analizuje operator przekazany przez metodę btOperator_Clicked przy wykorzystaniu instrukcji wyboru select case. W zależności od przekazanego operatora wykonywane jest odpowiednie działanie. Jego wynik jest następnie zwracany do metody btOperator_Clikced i wyświetlany na stronie WWW.
Na listingu 19.8 przedstawiony został kod wyjątkowo krótkiej strony ASP.NET która wykorzystuje stworzony przed chwilą element sterujący użytkownika.
Listing 19.8. Strona ASP.NET wykorzystująca element sterujący użytkownika oraz kod obsługi formularza.
<%@ Page language="VB" %>
<%@ Register TagPrefix="ASPNETDK" TagName="Calculator" src="Calculator.ascx" %>
<html><body>
<form runat="server">
<ASPNETDK:Calculator id="Calc1" runat="server"/>
</form>
</body></html>
Analiza
Powyższa strona jest wyjątkowo prosta. Zapewne pamiętasz, że element sterujący użytkownika musi zostać zarejestrowany na stronie ASP.NET. Służy do tego dyrektywa @ Register, opisana w szczegółowo w rozdziale 6., pt.: „Ciąg dalszy wiadomości na temat tworzenia formularzy internetowych”. Po zarejestrowaniu, elementu sterującego użytkownika będzie można używać tak samo, jak wszelkich innych elementów sterujących (patrz wiersz 5.). Wyświetl tę stronę w przeglądarce, a następnie spróbuj wpisać jakieś liczby w polach formularza i wykonać jakieś działania. Wyniki powinny przypominać te, przedstawione na rysunku 19.7.
Rysunek 19.7. Kod obsługi formularza obsługuje zdarzenia generowane przez element sterujący użytkownika umieszczony na stronie ASP.NET
Pliki zasobów i lokalizacja
Udając się na wakacje zabieramy walizkę z ubraniami, przyborami toaletowymi oraz innymi rzeczami osobistymi. Można by także zabrać te wszystkie rzeczy bezpośrednio ze sobą (albo i na sobie) ale było by to bardzo dziwne i niewygodne (nie wspominając w ogóle o ograniczonej ilości kieszeni!). Ta walizka jest na wakacjach przedmiotem o pierwszorzędny znaczeniu — bez niej nasza wycieczka stała by się koszmarem.
ASP.NET pozwala nam na „pakowanie walizki” dla tworzonej aplikacji. Wszelkie informacje (takie jak zmienne, komunikaty, notatki, rysunki, itp.) można zapisać w pliku zasobu. Strony ASP.NET bez trudu mogą następnie pobierać zasoby zgromadzone w takim pliku, gdy tylko będą potrzebne.
Nowe wyrażenie
Pliki zasobów są zazwyczaj wykorzystywane do lokalizowania aplikacji. Lokalizowanie jest procesem polegającym na modyfikacji wyników generowanych przez aplikację tak aby były one zgodne z różnymi kulturami i językami. Można tworzyć pliki zasobów dla wszystkich kultur jakie będą korzystać z danej aplikacji. Na przykład, w pliku zasobów można zapisać wiadomość powitalną. Jeśli aplikację odwiedzi osoba anglojęzyczna komunikat powitalny będzie miał postać „Hello”, dla użytkowników z Francji lub innych krajów francuskojęzycznych komunikat ten będzie miał postać „Bonjour”. Zapisując wiadomość powitalną dla każdego z języków w odrębnym pliku zasobów nie trzeba będzie tworzyć różnych stron ASP.NET dla różnych języków. Znacznie łatwiej jest stworzyć nowy plik zasobów.
W ASP.NET lokalizacja aplikacji oraz grupowanie zasobów jest bardzo łatwym procesem. Pozwala on na modyfikację wyników działania aplikacji bez konieczności wprowadzania jakichkolwiek zmian w kodzie, czyli stanowi jeszcze jedną formę oddzielania kodu od treści. W kilku kolejnych częściach niniejszego rozdziału przedstawione zostaną sposoby określania jacy użytkownicy korzystają z aplikacji, metody modyfikacji aplikacji pod ich kątem oraz zapisywania zasobów w plikach.
Lokalizowanie aplikacji
W idealnym przypadku lokalizowania aplikacji będziemy dążyć do automatycznego określenia pochodzenia korzystających z niej użytkowników, tak aby można było wykorzystać odpowiednie języki oraz inne informacje kulturowe. Niestety ze względu na charakter WWW nie ma żadnego standardowego sposobu na określenie geograficznego położenia osoby odwiedzającej witrynę.
Na szczęście jednak istniej kilka sposobów rozwiązania tego problemu. Można określić główny język ustawiony w przeglądarce użytkownika. Zazwyczaj, choć oczywiście nie zawsze, ustawienie to określa język oraz informacje kulturowe, które najbardziej odpowiadają użytkownikowi przeglądarki; na tej podstawie będzie można określić działanie aplikacji. Odstęp do tych informacji zapewnia obiekt Request.UserLanguages, który gromadzi informacje o językach przekazane przez przeglądarkę i przechowuje je w formie listy posortowanej zgodnie z priorytetami określonymi przez użytkownika. Przykład kodu umożliwiającego określenie głównego języka przedstawiony została na listingu 19.9.
Listing 19.9. Wykorzystanie obiektu Request do określenia językowych preferencji użytkownika.
<%@Page Language="VB" %>
<%@Import Namespace="System.Globalization" %>
<script runat="server">
sub Page_Load(obj as object,e as eventargs)
lblMessage.Text =Request.UserLanguages(0).ToString
end sub
</script>
<html><body>
Twoim głównym językiem jest:
<asp:Label id="lblMessage" runat="server"/>
</body></html>
Analiza
W wierszu 2. importowana jest zupełnie nowa przestrzeń nazw — System.Globalization. Zawiera ona wiele klas, które będą konieczne przy modyfikacji wyników działania aplikacji w zależności od ustawień językowych i kulturowych. Warto zwrócić uwagę, iż w powyższym przykładzie nie jest używany żaden obiekt którejkolwiek z klas dostępnych w tej przestrzeni nazw. Już nie długo będziemy jednak z nich korzystać! W wierszu 6. jest pobierany i wyświetlany główny język użytkownika. Właściwość UserLanguages zwraca tablicę języków wybranych przez użytkownika (określonych w przeglądarce), posortowaną według priorytetów tych języków (najczęściej używane języki oraz ich kody zostały przedstawione w tabeli 19.1). Język główny (lub język o najwyższym priorytecie) można określić przy użyciu wyrażenia UserLanguages(0). Wyniki wykonania strony z listingu 19.9 zostały przedstawione na rysunku 19.8.
Tabela 19.1. Najczęściej wykorzystywane języki i ich kody.
Język |
Kod |
Angielski (Anglia) |
en-uk |
Angielski (Australia) |
en-au |
Angielski (Kanada) |
en-ca |
Angielski (Stany Zjednoczone) |
en-us |
Arabski (Egipt) |
ar-eg |
Chiński (Hong-Kong) |
zh-hk |
Flamandzki (Belgia) |
nl-be |
Francuski (Francja) |
fr |
Hiszpańki (tradycyjny) |
es |
Hiszpański (Meksyk) |
es-mx |
Japoński |
ja |
Koreański |
ko |
Niemiecki (Niemcy) |
de |
Polski |
pl |
Portugalski (Portugalia) |
pt |
Rosyjski (Rosja) |
ru |
Włoski (Włochy) |
it |
Rysunek 19.8. Obiekt Request może posłużyć od określenia głównego języka wybranego w przeglądarce
Teraz, po określeniu głównego języka można na jego podstawie określić informacje kulturowe, które będą wykorzystywane w aplikacji ASP.NET. W tym celu posłużymy się obiektem klasy System.Globalization.CultureInfo. Obiekty tej klasy zawierają kompletne informacje reprezentujące kulturę określaną przez używany język. Do informacji tych, oprócz języka, należy także używany kalendarz, nazwy krajów, format zapisu liczb i dat, oraz wiele innych elementów. Przykład wykorzystania obiektu tej klasy został przedstawiony na listingu 19.10.
Listing 19.10. Wykorzystanie obiektu klasy CultureInfo.
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Globalization" %>
<script runat="server">
sub Page_Load(obj as object, e as eventargs)
dim strLanguage as string = Request.UserLanguages(0).ToString
lblMessage.Text = "Język główny: " & _
strLanguage & "<br>"
dim objCulture as new CultureInfo(strLanguage)
lblMessage.Text += "Pełna nazwa: " & _
objCulture.EnglishName & "<br>"
lblMessage.Text += "Nazwa rodzima: " & _
objCulture.NativeName & "<br>"
lblMessage.Text += "Skrót: " & _
objCulture.ThreeLetterISOLanguageName & "<br>"
lblMessage.Text += "Godzina: " & _
DateTime.Now.ToString("D", objCulture) & "<br>"
end sub
</script>
<html><body>
<b>Informacje kulturowe o użytkowniku:</b> <p>
<asp:Label id="lblMessage" runat="server"/>
</body></html>
Analiza
Powyższy kod jest modyfikacją przykładu z listingu 19.9. W wierszu 2. importowana jest przestrzeń nazw System.Globalization. W wierszach 6. i 7. pobierany jest główny język przeglądarki użytkownika (w identyczny sposób jak na listingu 19.9). Język ten jest następnie wyświetlany w etykiecie zdefiniowanej w wierszu 26. W wierszu 12., tworzony jest nowy obiekt klasy CultureInfo bazujący na pobranym wcześniej języku głównym. W wierszach od 13. do 20. wyświetlane są różne informacje kulturowe. W celu poprawnego sformatowania daty została wykorzystana metoda ToString akceptująca dwa argumenty — łańcuch znaków określający sposób wyświetlenia daty oraz obiekt CultureInfo zawierający dodatkowe informacje określające postać daty. Na rysunku 19.9 zostały przedstawione wyniki wykonania powyższej strony ASP.NET w przypadku gdy głównym językiem wybranym w przeglądarce jest francuski.
Rysunek 19.9. Wyniki wygenerowane przez stronę zostały dostosowane do ustawień kulturowych użytkownika i to bez wprowadzania jakichkolwiek modyfikacji w kodzie
Klasa CultureInfo udostępnia więcej właściwości, które pozwalają na uzyskanie bardziej szczegółowych informacji na temat ustawień kulturowych. Najczęściej stosowane właściwości tej klasy zostały przedstawione w tabeli 19.2.
Tabela 19.2. Właściwości klasy CultureInfo.
Właściwość |
Opis |
Calendar |
Kalendarz używany w danej kulturze (na przykład: gregoriański, koreański, itp.). |
CurrentCulture |
Właściwość określa informacje kulturowe ustawione na serwerze (a nie w przeglądarce użytkownika). Właściwość ta zwraca obiekt klasy CultureInfo. Właściwość jest przeznaczona wyłącznie do odczytu. |
CurrentUICulture |
Właściwość pobiera informacje kulturowe używane na serwerze. Jest ona używana do określania ustawień kulturowych plików zasobów. (Patrz podrozdział „Zapisywanie zasobów w plikach”, w dalszej części tego rozdziału.) Właściwość taj jest przeznaczona wyłącznie do odczytu. |
DateTimeFormat |
Zwraca obiekt klasy DateTimeFormatInfo określający sposób formatowania daty i czasu zgodnie z bieżącymi ustawieniami kulturowymi. Więcej informacji na ten temat można znaleźć w dokumentacji .NET SDK. |
DisplayName |
Pełna nazwa kultury reprezentowanej przez dany obiekt CultureInfo, w języku określonym przez interfejs użytkownika. |
EnglishName |
Pełna nazwa obiektu klasy CultureInfo w języku angielskim. |
Name |
To samo co właściwość DisplayName, z tym iż ta jest przeznaczona wyłącznie do odczytu. |
NativeName |
Pełna nazwa obiektu klasy CultureInfo w danym języku. |
NumberFormat |
Właściwość określa format wyświetlania liczb (czyli gdzie umieszczać przecinki, punkty dziesiętne, itp.) Zwraca obiekt klasy NumberFormatInfo. |
ThreeLetterISOLanguageName |
Trzyliterowy kod oznaczający daną kulturę w standardzie ISO 3166. |
ThreeLetterWindowLanguageName |
Wersja trzyliterowego oznaczenia kultury stosowana w systemie Windows. |
TwoLetterISOLanguageName |
Dwuliterowy kod oznaczający daną kulturę w standardzie ISO 3166. |
Informacje kulturowe dla danej strony ASP.NET można także określić przy użyciu atrybutu Culture dyrektywy @ Page. Poniższa dyrektywa sprawi, że na stronie zostaną wykorzystane ustawienia kulturowe dla języka niemieckiego:
<%@ Page Language="VB" Culture="de" %>
W tym przypadku, jeśli na stronie będą wykorzystywane obiekty związane z ustawieniami kulturowymi, to wykorzystają one ustawienia kulturowe podane w dyrektywie @ Page a nie określone przez serwer. Na przykład, w przypadku wykorzystania ustawień kulturowych dla języka niemieckiego, wywołanie DateTime.Now.ToString("D") spowoduje wyświetlenie następującej daty:
Montag, 3. Dezember 2001
W przypadku użycia ustawień kulturowych dla języka oznaczonego symbolem en-us, ten sam fragment kodu wygeneruje wyniki:
Monday, December 03, 2001
To bardzo prosty sposób określania ustawień kulturowych jakie mają być wykorzystane na stronie, o ile są one z góry znane.
Notatka
Właściwość CultureInfo.CurrentCulture jest w rzeczywistości „skrótem” do
-->
właściwości[Author:p8R]
System.Threading.CurrentThread.CurrentCulture. Obie te właściwości zwracają obiekt klasy CultureInfo, który może zostać wykorzystany w sposób przedstawiony na powyższych przykładach. Różnica pomiędzy nimi polega na tym, iż właściwość CultureInfo.CurrentCulture jest przeznaczona wyłącznie do odczytu, natomiast właściwość System.Threading.CurrentThread.CurrentCulture pozwala także na zapis informacji. Z tego powodu, jeśli będziesz musiał zmienić bieżące ustawienia kulturowe powinieneś posłużyć się tą drugą właściwością.
Kultura jest właściwością aktualnie wykonywanego wątku (innymi słowy — aktualnie wykonywanego fragmentu bieżącej aplikacji). W przypadku określania ustawień kulturowych w ASP.NET są one określane dla całej aplikacji. Oznacza to, że te same informacje kulturowe można pobrać za pośrednictwem bieżącego wątku jak i z obiektu CultureInfo. (W dalszej części rozdziału zostanie przedstawiony przykład wykorzystania obiektu System.Threading.CurrentThread.
Oprócz obiektu CultureInfo można także wykorzystać obiekt klasy RegionInfo zawierający, między innymi, informacje o symbolu waluty oraz o tym, czy w danym regionie jest wykorzystywany system metryczny. Sposób użycia obiektów obu tych klas jest bardzo podobny, z tym, że w przypadku obiektów klasy RegionInfo nie można podawać języka w celu określenia postaci pobieranych informacji — zamiast nich wykorzystywane są skróty nazw krajów. Na przykład: US dla Stanów Zjednoczonych bądź FR dla Francji. Przykład wykorzystania obiektów tej klasy został przedstawiony na listingu 19.11.
Listing 19.11. Wyświetlanie informacji o regionie z jakiego pochodzi użytkownik.
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Globalization" %>
<script runat="server">
sub Page_Load(obj as object, e as eventargs)
dim objRegion as RegionInfo
if Page.IsPostBack then
objRegion = new RegionInfo(btRegion.Text)
else
objRegion = RegionInfo.CurrentRegion
end if
lblMessage.Text = "Region: " & objRegion.Name & "<br>"
lblMessage.Text += "Pełna nazwa: " & _
objRegion.EnglishName & "<br>"
lblMessage.Text += "Waluta: " & _
objRegion.CurrencySymbol & "<br>"
lblMessage.Text += "Waluta ISO: " & _
objRegion.ISOCurrencySymbol & "<br>"
lblMessage.Text += "Skrót: " & _
objRegion.ThreeLetterISORegionName & "<br>"
lblMessage.Text += "Czy jest używany system metryczny: " _
& objRegion.IsMetric
end sub
</script>
<html><body>
<form runat="server">
<b>Twoje ustawienia:</b> <p>
<asp:Label id="lblMessage" runat="server"/><p>
Zmień na (i.e. 'US', 'FR', 'JP', etc):
<asp:TextBox id="btRegion" runat="server"
AutoPostBack=true />
</form>
</body></html>
Analiza
W momencie pierwszego wyświetlenia powyższej strony, do obiektu RegionInfo zostaną wczytane domyślne informacje o regionie (patrz wiersz 11.). Informacje o aktualnie wybranym regionie są wyświetlane w wierszach od 14. do 25., między innymi należą do nich symbol i skrót waluty. Gdy użytkownik wpisze jakieś informacje w polu tekstowym zdefiniowanym w wierszach 34. i 35. strona zostanie automatycznie wyświetlona ponownie (dzięki użyciu atrybutu AutoPostBack o wartości true). Kod regionu podany przez użytkownika w polu tekstowym jest następnie wykorzystywany w celu stworzenia nowego obiektu RegionInfo (patrz wiersz 9.). Wygląd strony po wybraniu regionu o kodzie JP (czyli Japonii) został przedstawiony na rysunku 19.10.
Rysunek 19.10. Na stronie zostają wyświetlone informacje o wybranym regionie
Najczęściej wykorzystywane właściwości klasy RegionInfo zostały przedstawione w tabeli 19.3.
Tabela 19.3. Właściwości klasy RegionInfo.
Właściwość |
Opis |
CurrencySymbol |
Symbol używany do oznaczania wartości monetarnych w danym regionie. |
CurrentRegion |
Właściwość pobiera domyślny serwer używany na serwerze (a nie na komputerze użytkownika). Właściwość zwraca obiekt klasy RegionInfo. |
DisplayName |
Pełna nazwa regionu identyfikowanego przez obiekt RegionInfo w języku określonym przez interfejs użytkownika. |
EnglishName |
Nazwa regionu identyfikowanego przez obiekt RegionInfo w języku angielskim. |
IsMetric |
Wartość logiczna określająca czy w danym regionie wykorzystywany jest system metryczny. |
ISOCurrencySymbol |
Kod ISO znaku zapisanego we właściwości CurrencySymbol. Na przykład, znakowi $ odpowiada kod ISO USD. |
Name |
Zawiera te same informacje co właściwość CurrencySymbol, lecz jest przeznaczona wyłącznie do odczytu. |
ThreeLetterISORegionName |
Trzyliterowy kod oznaczający daną kulturę w standardzie ISO 3166. |
ThreeLetterWindowRegionName |
Wersja trzyliterowego oznaczenia kultury stosowana w systemie Windows. |
TwoLetterISORegionName |
Dwuliterowy kod oznaczający daną kulturę w standardzie ISO 3166. |
Nowe wyrażenie
Istnieje także możliwość określenia sposobu kodowania wyników generowanych przez strony ASP.NET. Kodowanie, to sposób w jaki znaki są reprezentowane przez komputer; przykładowe sposoby kodowania to Unicode lub ASCII. Strony ASP.NET domyślnie używają kodowania Unicode, jednak ustawienie to może się zmieniać w zależności od regionu.
Dostępne są dwa, różne sposoby określania sposobu kodowania wykorzystywanego w aplikacji — bezpośrednio na stronach ASP.NET bądź też w pliku web.config. Na stronach ASP.NET sposób kodowania określa się przy użyciu atrybutu ResponseEncoding dyrektywy @ Page; jak na poniższym przykładzie:
<%@ Page Language="VB" ResponseEncoding="UTF-8" %>
W przypadku określania sposobu kodowania w pliku web.config należy to zrobić w następujący sposób:
<configuration>
<system.web>
<globalization fileEncoding="utf-8" />
</system.web>
</configuration>
Jak na razie przedstawione zostały wyłącznie sposoby zmieniania informacji generowanych przez obiekty związane z ustawieniami kulturowymi i regionalnymi, a co ze zwykłym tekstem? Jak można skonwertować zwyczajny tekst, tak aby korzystał z wybranych ustawień językowych? Niestety nie można tego zrobić w żaden prosty sposób — trzeba po prostu, samodzielnie przetłumaczyć tekst. Jeśli witryna jest odwiedzana przez osoby posługujące się różnymi językami, to zazwyczaj będzie to oznaczać konieczność stworzenia odrębnych wersji językowych każdej ze stron. Na szczęście w ASP.NET nie trzeba się uciekać do aż tak drastycznych rozwiązań — można bowiem wykorzystać pliki zasobów.
Zapisywanie zasobów w plikach
Pliki zasobów są używane do przechowywania informacji wykorzystywanych w aplikacjach niezależnie od ich kodu. Istnieje możliwość zastosowania wielu wersji pliku zasobów, dzięki czemu strony ASP.NET mogą prezentować różne informacje bez konieczności wprowadzania jakichkolwiek zmian w kodzie. Posługując się przykładem lokalizacji, można by stworzyć wiele różnych plików zasobów, po jednym dla każdego z ustawień kulturowych wykorzystywanych przez użytkowników odwiedzających witrynę. Każdy z tych plików zawierałby dokładnie te same informacje zapisane w różnych językach.
Przeanalizujmy przykład prostej strony ASP.NET, której treść można umieścić w pliku zasobów. Kod tej strony został przedstawiony na listingu 19.12.
Listing 19.12. Strona ASP.NET, która posłuży jako przykład wykorzystania plików zasobów.
<%@ Page Language="VB" %>
<script runat="server">
sub Page_Load(obj as Object, e as EventArgs)
lblMessage.Text = DateTime.Now.ToString("t")
end sub
</script>
<html><body>
<b>Witamy!</b> Teraz jest:
<asp:Label id="lblMessage" runat="server"/><p>
Ta strona demonstruje sposób wykorzystywania plików zasobów
w ASP.NET.<p>
<font size=1>Nie zapomnij sprawdzić tego w domu!</font>
</body></html>
Powyższy przykład jest bardzo prosty — wyświetla kilka komunikatów tekstowych o stałej treści oraz bieżącą godzinę. Wyniki wykonania tej strony zostały przedstawione na rysunku 19.11.
Rysunek 19.11. Prosta strona ASP.NET w której można wykorzystać pliki zasobów
Załóżmy, że chcielibyśmy przetłumaczyć tę stronę na język francuski. Oczywiście można by stworzyć nową wersję strony o tej samej postaci lecz z innymi komunikatami tekstowymi, ale po co utrudniać sobie życie? Znacznie lepszym rozwiązaniem będzie stworzenie dwóch plików zasobów. Są to zwyczajne pliki tekstowe, które można utworzyć przy użyciu dowolnego edytora tekstów. W pierwszej kolejności stwórzmy polską wersję takiego pliku. W tym celu, w edytorze tekstowym należy wpisać poniższy tekst:
[strings]
Powitanie=Witamy!
Czas=Teraz jest:
Tresc=Ta strona demonstruje sposób wykorzystywania plików zasobów w ASP.NET
Stopka=<font size=1>Nie zapomnij sprawdzić tego w domu!</font>
Zapisz ten fragment tekstu w pliku o nazwie data.pl.txt (za chwilę wyjaśnię dlaczego nazwa pliku ma akurat taką postać). Utworzony plik zawiera jedną sekcję, w której zostały zapisane wszystkie wykorzystywane łańcuchy znaków. Informacje zostały zapisane w formie par nazwa-wartość:
Powitamie=Witamy!
Powitanie jest nazwą zasobu do którego będziemy się odwoływać w kodzie strony ASP.NET, natomiast Witamy! jest wartością jaka zostanie wyświetlona na stronie wynikowej. Poniżej przedstawiona została francuskojęzyczna wersja pliku zasobów:
[strings]
Powitanie=Bonjour!
Czas=L'heure maintenent est:
Tresc=Cette page demonstrate utiliser files de resource avec ASP.NET
Stopka=<font size=1>N'oublie pas essayez de faire ceci chez soi!</font>
Powyższy tekst zapisz w pliku o nazwie data.fr.txt. (Tworząc oba pliki tekstowe należy pamiętać, aby każdy z łańcuchów znaków został zapisany w osobnym wierszu. Jeśli w łańcuchu znaków znajdą się znaki nowego wiersza, to podczas generacji właściwych plików zasobów pojawią się błędy.) Warto zwrócić uwagę, iż w obu plikach zostały użyte te same nazwy kluczy. Nazwy te muszą być identyczne, gdyż w przeciwnym przypadku ASP.NET nie będzie w stanie odszukać zasobów.
ASP.NET nie jest jednak w stanie korzystać z plików zasobów w ich obecnej postaci. Trzeba je zatem skonwertować do postaci, którą ASP.NET będzie rozumieć. Do tego celu służy program narzędziowy resgen.exe (nazywany także generatorem zasobów). Program ten konwertuje tekstowe pliki zasobów i na ich podstawie generuje pliki z rozszerzeniem .resources. A zatem, wyświetl okno wiersza poleceń i przejdź do folderu, w którym zapisałeś stworzone wcześniej pliki .txt. Następnie wydaj poniższe polecenie (i nie zapomnij nacisnąć klawisza Enter):
resgen data.pl.txt
resgen data.fr.txt
Program powinien wyświetlić następujące wyniki:
Read in 4 resources from 'data.pl.txt'
Writing resource file... Done.
Po wykonaniu powyższych poleceń w folderze pojawią się dwa nowe pliki — data.pl.resources oraz data.fr.resources. Z tych plików może już korzystać zarządca zasobów ASP.NET. A zatem, użyjmy tych zasobów na stronie ASP.NET!
Za obsługę wszystkich zasobów w ASP.NET odpowiada obiekt klasy System.Resources.ResourceManager. Obiekt taki jest w stanie odnaleźć plik zasobów odpowiadający ustawieniom kulturowym wykorzystywanym przez użytkownika i pobrać zapisane w nim zasoby. Sposób użycia tego obiektu został przedstawiony na listingu 19.13.
Listing 19.13. Pobieranie informacji z plików zasobów przy wykorzystaniu obiektu ResourceManager.
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import namespace="System.Resources" %>
<%@ Import namespace="System.Threading" %>
<script runat="server">
sub Page_Load(obj as object, e as eventargs)
dim objRM as ResourceManager
dim strLanguage as string = _
Request.UserLanguages(0).ToString
dim objCulture as new CultureInfo(strLanguage)
Thread.CurrentThread.CurrentCulture = _
new CultureInfo(strLanguage)
Thread.CurrentThread.CurrentUICulture = _
new CultureInfo(strLanguage)
objRM = ResourceManager. _
CreateFileBasedResourceManager("data", _
Server.MapPath("."), Nothing)
lblGreeting.Text = objRM.GetString("Powitanie")
lblTime.Text = objRM.GetString("Czas") & " " & _
DateTime.Now.ToString("t")
lblBlurb.Text = objRM.GetString("Tresc")
lblDisclaimer.Text = objRM.GetString("Stopka")
objRM.ReleaseAllResources
end sub
</script>
<html><body>
<b><asp:Label id="lblGreeting" runat="server"/></b>
<asp:Label id="lblTime" runat="server"/><p>
<asp:Label id="lblBlurb" runat="server"/><p>
<asp:Label id="lblDisclaimer" runat="server"/>
</body></html>
Analiza
W pierwszej kolejności należy zwrócić uwagę na wykorzystanie dwóch dodatkowych przestrzeni nazw, importowanych w wierszach 3. i 4. Przestrzeń nazw System.Resources jest konieczna aby można było korzystać z obiektu ResourceManager. Także przestrzeń nazw System.Threading jest niezbędna (w dalszej części rozdziału wyjaśnię dlaczego). W wierszu 8., w standardowy sposób, deklarowany jest nowy obiekt ResourceManager. W wierszach 9. i 10. określany jest główny język wybrany w przeglądarce (główny język jest określany w taki sam sposób jak w przykładzie przedstawionym na listingu 19.9.).
W wierszach do 12. do 15., na podstawie wybranego języka głównego, określane są ustawienia kulturowe. Pamiętasz zapewne, że informacje kulturowe można podawać przy wykorzystaniu właściwości System.Threading.CurrentThread.CurrentCulture, która pozwala na pobranie lub zapis obiektu klasy CultureInfo (była o tym mowa we wcześniejszej części rozdziału, w podrozdziale pt.: „Lokalizowanie aplikacji”). W wierszu 12. tworzony jest nowy obiekt klasy CultureInfo zawierający informacje kulturowe wybrane na podstawie określonego wcześniej głównego języka używanego w przeglądarce. Wszystkie obiekty, które dysponują możliwością lokalizacji, będą teraz wykorzystywały wybrane informacje kulturowe (na przykład, po wybraniu informacji kulturowych dla języka angielskiego, godzina będzie wyświetlana w formie 10:16 PM, a nie 22:16).
Niemniej jednak sam fakt zmiany ustawień kulturowych nie sprawi, że ASP.NET pobierze dane z odpowiednich plików zasobów. W tym celu, oprócz wybrania odpowiednich informacji kulturowych, należy także przypisać odpowiednią wartość właściwości System.Threading.CurrentThread.CurrentUICulture (patrz wiersze 14. i 15.). To właśnie na podstawie tej właściwości ASP.NET określa jaki plik zasobów należy użyć.
Odczytanie zasobów z pliku realizowane jest przez metodę CreateFileBasedResourceManager, której wywołanie zostało zapisane w wierszach od 17. do 19. Metoda ta wymaga podania trzech argumentów — początkowej części (prefiksu) nazwy plików zasobów, ścieżki dostępu do tych plików oraz obiektu którego należy użyć do przetworzenia informacji pobranych z pliku zasobów (przy czym ten trzeci argument jest opcjonalny). Wartość drugiego argumentu określana jest przy użyciu metody Server.MapPath, która zwraca fizyczną ścieżkę dostępu do folderu w jakim są przechowywane pliki zasobów. (Więcej informacji na temat metody MapPath można znaleźć w rozdziale 4., pt.: „Stosowanie obiektów ASP.NET w językach C# i VB.NET”, w podrozdziale „Obiekt HttpServerUtility”.) W naszym przypadku nie jest potrzebny żaden obiekt służący do przetwarzania danych pobieranych z pliku zasobów, a zatem jako trzeci argument wywołania metody CreateFileBasedResourceManager należy przekazać wartość Nothing.
Przypomnij sobie, że nazwy plików zasobów mają następującą postać — data.kultura.resources. Metoda CreateFileBasedResourceManager poszukuje plików o nazwach pasujących do schematu prefiks.kultura.resources. A zatem, prefiks podany w wywołaniu tej metody musi odpowiadać początkowej części nazw plików zasobów. W naszym przypadku, w razie wybrania ustawień kulturowych dla języka pl (polskiego) metoda będzie poszukiwać pliku o nazwie data.pl.resources, natomiast w razie wybrania ustawień kulturowych dla języka fr (francuskiego) będzie ona poszukiwać pliku data.fr.resources. Prefiks służy do logicznego grupowania plików zasobów.
Następnie, w wierszach do 21. do 25. wywoływana jest metoda GetString obiektu ResourceManager, która pobiera pary klucz-wartość zapisane w pliku zasobów. Pobrane łańcuchy znaków są wyświetlane na stronie wynikowej przy użyciu czterech etykiet (elementów sterujących Label), zdefiniowanych w wierszach do 32. do 35. Otwierając plik zasobów zarządzający nimi mechanizm blokuje dostęp do nich, dzięki czemu żadne inne aplikacje nie będą w stanie ich zmienić. Na rysunkach 19.12 oraz 19.13 zostały przedstawione wyniki wykonania strony z listingu 19.13, w przypadku wyboru ustawień kulturowych dla języków pl-PL oraz fr-FR.
Rysunek 19.12. W przypadku wybrania ustawień kulturowych dla języka pl-PL ASP.NET pobiera dane z pliku zasobów data.pl.resources
Rysunek 19.13. W przypadku wybrania ustawień kulturowych dla języka fr-FR ASP.NET pobiera dane z pliku zasobów data.fr.resources
Notatka
Warto pamiętać, że informacje kulturowe można także określić przy użyciu atrybut Culture dyrektywy @ Page:
<%@ Page Language="VB" Culture="fr-FR" %>
Jeśli jednak zastosujesz takie rozwiązanie w ostatnim przykładzie, to zasoby nie zostaną pobrane z odpowiedniego pliku. Problem polega na tym, iż informacje kulturowe są określana na podstawie głównego języka używanego w przeglądarce (określanego w wierszu 9.), a nie na podstawie atrybutu dyrektywy @ Page. Aby rozwiązać ten problem, wiersze do 9. do 11. listingu 19.13 należy zastąpić następującym fragmentem kodu:
dim strLanguage as string = CultureInfo.CurrentCulture.ToString
Podpowiedź
Zazwyczaj w całej aplikacji powinny być wykorzystywane te same informacje kulturowe, dlatego możesz zastanowić się nad wykorzystaniem rozwiązania polegającego na podaniu tych informacji w metodzie Application_BeginRequest definiowanej w pliku global.asax. W ten sposób informacje kulturowe będą poprawnie ustawiane przed rozpoczęciem obsługi każdego żądania odbieranego na serwerze.
Innym rozwiązaniem mogłoby być stworzenie obiektu ResourceManager i zapisanie go jako zmiennej aplikacyjnej w metodzie Application_OnStart. W ten sposób można uniknąć konieczności tworzenia obiektów ResourceManager podczas obsługi każdego otrzymywanego żądania. Na przykład, do metody Application_OnStart można by dodać poniższy fragment kodu:
Application("RM") = New ResourceManager("data", _
Server.MapPath("."), Nothing)
Powyższy fragment kodu tworzy obiekt ResourceManager dostępny dla całej aplikacji. Dostęp do niego można uzyskać za pomocą wyrażenia Application("RM").
To nie jest ASP!
Dążenie od oddzielenia kodu od treści stron WWW i czerpania wynikających z tego korzyści nie jest niczym nowym w technologii ASP.NET. W rzeczywistości programiści ASP dążyli do tego celu już od dłuższego czasu, zwłaszcza w klasycznej technologii ASP bazującej na wykorzystaniu bloków kodu wykonywalnego a nie bloków deklarowania kodu. Brak możliwości separacji kodu od treści sprawiał, iż kod ASP musiał się przeplatać ze zwyczajnym kodem HTML. To z kolei niezwykle utrudniało testowanie i modyfikację stron ASP, gdyż programiści musieli szukać błędów na całej stronie. Dzięki wykorzystaniu bloków deklarowania kodu problem ten niemal całkowicie znikną, gdyż cały kod jest umieszczany w jednym miejscu — na samym początku strony ASP.NET.
Programiści korzystający z tradycyjnej technologii ASP musieli oddzielać kod od treści stron przy użyciu mechanizmów dołączania zawartości plików zewnętrznych (SSI). W takich dołączanych plikach można było umieszczać całkowicie dowolny kod, w tym także kod HTML, dlatego też często w nich umieszczano fragmenty interfejsu użytkownika wraz z ich obsługą. Mechanizmy dołączania plików są wciąż dostępne lecz w stronach ASP.NET znacznie częściej korzysta się aktualnie z elementów sterujących użytkownika lub kodu obsługi formularzy. Oba te rozwiązania — zarówno elementy sterujące użytkownika, jak i kod obsługi formularzy — dają znacznie większe możliwości programistycznej kontroli nad tworzonym kodem niż technologia SSI, w której możliwości takie praktycznie nie istniały.
Zupełnie nowym elementem ASP.NET są pliki zasobów. Programiści którzy mieli kontakt z językami takimi jak Visual Basic (VB) lub C++ mogli się już z nimi spotkać. Pliki zasobów były w tych językach bardzo często wykorzystywane, zresztą z tych samych powodów, dla których aktualnie są one dostępne także w ASP.NET — głównie chodzi tu o możliwość oddzielania kodu od treści.
W klasycznej technologii ASP nie było żadnych możliwości oddzielenia od stron ASP informacji wykorzystywanych przy lokalizowaniu aplikacji. Z tego względu, bardzo często trzeba było tworzyć wiele wersji tej samej strony. Co więcej, wcześniejsze wersje technologii ASP nie pozwalały na modyfikowanie informacji kulturowych i regionalnych.
Wszystkie te modyfikacje sprawiają, że technologia ASP.NET jest znacznie bardziej wszechstronna niż jej poprzednie wersje. Aktualnie programiści mają znacznie więcej możliwości związanych z rozmieszczaniem kodu na stronach.
Jest całkiem prawdopodobne, że przy domyślnych ustawieniach przeglądarki, na przykład polskiej wersji Internet Explorera, powyższy przykład nie będzie działać poprawnie. W przeglądarce może zostać wyświetlone komunikat o błędzie rozpoczynający się od słów: „Culture "pl" is a neutral culture....” Należy go rozumieć w ten sposób, iż informacje kulturowe określone przez przeglądarkę nie są wystarczające by na ich podstawie można było określić sposób formatowania informacji. Rozwiązanie tego problemu sprowadza się do określenia (w przeglądarce) bardziej szczegółowych informacji na temat wybranego języka głównego, na przykład zamiast języka Polski [pl], należy podać język pl-pl. Po wybraniu języka głównego o takiej postaci powyższy przykład będzie działać poprawnie.
2 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
2 Dokument2
Kod listingu podany w książce (w oryginale) różni się od kodu podanego w przykładach dołączonych do książki. W tłumaczeniu umieściłem kod z przykładu (listing1901.aspx).
Informacja o modyfikacji bazy danych w rozdziale Bonus Project 2 została pominięta, gdyż rozdziały te nie są tłumaczone.
System.Threading.CurrentThread.CurrentCulture to właściwość, która przechowuje obiekt