Pisanie kodu ogólnego dostępu do danych w ASP.NET 2.0 i ADO.NET 2.0
Dr Shahram Khosravi
Rozwiązania informacyjne
Kwiecień 2005 r.
Dotyczy:
Microsoft ADO.NET 2.0
Microsoft ASP.NET 2.0
Microsoft .NET Framework 2.0
Microsoft Visual Web Developer 2005 Express Edition Beta
Język programowania C#
Podsumowanie: Użyj podejścia krok po kroku, aby dowiedzieć się, jak używać różnych ASP.NET 2.0 i ADO.NET 2.0 narzędzi i technik pisania ogólnego kodu dostępu do danych. (18 stron drukowanych)
Pobierz skojarzony przykładowy kod: GenericDataAccessSample.exe.
Wprowadzenie
Większość aplikacji internetowych zawiera kod dostępu do danych, aby uzyskać dostęp do bazowego magazynu danych w celu wykonywania podstawowych operacji danych, takich jak Select, Update, Deletei Insert. W tym artykule użyto podejścia krok po kroku, aby pokazać, jak deweloperzy stron mogą korzystać z różnych ASP.NET 2.0 i ADO.NET 2.0 narzędzi i technik pisania ogólnego kodu dostępu do danych, który może służyć do uzyskiwania dostępu do różnych typów magazynów danych. Pisanie ogólnego kodu dostępu do danych jest szczególnie ważne w aplikacjach internetowych opartych na danych, ponieważ dane pochodzą z wielu różnych źródeł, w tym programu Microsoft SQL Server, Oracle, dokumentów XML, plików prostych i usług sieci Web, tylko po to, aby wymienić kilka.
W tym artykule użyto prostej aplikacji internetowej jako łóżka testowego dla całego kodu przedstawionego tutaj. Aplikacja składa się z dwóch części: pierwsza część umożliwia administratorowi systemu wysyłanie biuletynów do wszystkich subskrybentów listy wysyłkowej. Druga część umożliwia użytkownikom subskrybowanie lub anulowanie subskrypcji listy wysyłkowej. Pierwsza część artykułu rozpoczyna się od zaimplementowania prostego kodu dostępu do danych (patrz Rysunek 1) w celu uzyskania dostępu do programu Microsoft SQL Server i wyodrębnienia listy subskrybentów. Kod jest modyfikowany i bardziej ogólny w trakcie tego artykułu.
Rysunek 1. Metoda GetSubscribers wyodrębnia listę wszystkich subskrybentów.
public IEnumerable GetSubscribers()
{
SqlConnection con = new SqlConnection();
con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";
SqlCommand com = new SqlCommand();
com.Connection = con;
com.CommandText = "Select * From Subscribers";
com.CommandType = CommandType.Text;
DataSet ds = new DataSet();
SqlDataAdapter ad = new SqlDataAdapter();
ad.SelectCommand = com;
con.Open();
ad.Fill(ds);
con.Close();
return ds.Tables[0].DefaultView;
}
Ponieważ kod dostępu do danych na rysunku 1 zawiera kod do tworzenia obiektów ADO.NET, takich jak SqlConnection, SqlCommandi wystąpienia SqlDataAdapter. Kod dostępu do danych nie może służyć do pobierania listy subskrybentów z innych magazynów danych, takich jak baza danych Oracle. Deweloperzy stron muszą modyfikować kod dostępu do danych (przy użyciu metody GetSubscribers) za każdym razem, gdy muszą uzyskać dostęp do nowego magazynu danych. Następnie zobacz, jak ADO.NET 2.0 używa wzorca dostawcy, aby ułatwić deweloperom stron pisanie ogólnego kodu dostępu do danych w celu uzyskania dostępu do różnych typów magazynów danych.
Wzorzec dostawcy w ADO.NET 2.0
Głównym problemem z metodą GetSubscribers jest to, że zawiera kod tworzenia obiektów ADO.NET. Zgodnie ze wzorcem dostawcy kod dostępu do danych musi delegować odpowiedzialność za podanie kodu do tworzenia obiektów ADO.NET do innej klasy. Nazywam tę klasę "klasą dostawcy kodu", ponieważ udostępnia ona kod do tworzenia obiektów ADO.NET. Klasa dostawcy kodu udostępnia metody, takie jak CreateConnection, CreateCommandi CreateDataAdapter, gdzie każda metoda udostępnia kod tworzenia odpowiedniego obiektu ADO.NET.
Ponieważ klasa dostawcy kodu zawiera rzeczywisty kod, tej samej klasy nie można używać do uzyskiwania dostępu do różnych magazynów danych. W związku z tym kod dostępu do danych (metoda GetSubscribers) musi zostać zmodyfikowany i skonfigurowany ponownie w celu delegowania odpowiedzialności za dostarczanie kodu do nowej klasy dostawcy kodu za każdym razem, gdy jest używany do uzyskiwania dostępu do nowego magazynu danych. Metoda GetSubscribers jest nadal powiązana z kodem, mimo że nie zawiera kodu.
Wzorzec dostawcy oferuje rozwiązanie tego problemu i składa się z następujących kroków:
Projektowanie i implementowanie abstrakcyjnej klasy dostawcy podstawowego.
Utwórz wszystkie klasy dostawców kodu z abstrakcyjnej klasy dostawcy podstawowego.
Kod dostępu do danych (metoda GetSubscribers) umożliwia użycie abstrakcyjnej klasy bazowej zamiast poszczególnych klas dostawcy kodu.
Abstrakcyjna klasa bazowa deleguje odpowiedzialność za podanie kodu do tworzenia obiektów ADO.NET do odpowiedniej podklasy. Abstrakcyjna klasa bazowa nosi nazwę DbProviderFactory. Poniżej przedstawiono niektóre metody tej klasy:
public abstract class DbProviderFactory { public virtual DbConnection CreateConnection(); public virtual DbCommand CreateCommand(); public virtual DbDataAdapter CreateDataAdapter(); }
Każda podklasa zawiera kod umożliwiający utworzenie odpowiednich obiektów ADO.NET dla określonego magazynu danych. Na przykład podklasa SqlClientFactory zawiera kod tworzenia obiektów ADO.NET w celu uzyskania dostępu do programu Microsoft SQL Server, jak pokazano na rysunku 2.
Rysunek 2. Klasa SqlClientFactory i niektóre z jej metod
public class SqlClientFactory : DbProviderFactory { public override DbConnection CreateConnection() { return new SqlConnection(); } public override DbCommand CreateCommand() { return new SqlCommand(); } public override DbDataAdapter CreateDataAdapter() { return new SqlDataAdapter(); } }
Wzorzec dostawcy umożliwia kodowi dostępu do danych traktowanie wszystkich podklas tak samo, ponieważ są to wszystkie podklasy tej samej klasy bazowej. Jeśli chodzi o kod dostępu do danych, wszystkie podklasy są typu DbProviderFactory. Kod dostępu do danych nie ma możliwości znajomości określonego typu używanej podklasy. Spowoduje to wprowadzenie nowego problemu. Jeśli kod dostępu do danych (metoda GetSubscribers) nie zna typu podklasy, jak można utworzyć wystąpienie wystąpienia podklasy?
Rozwiązanie wzorca dostawcy do tego problemu składa się z następujących trzech części:
- Unikatowy ciąg służy do identyfikowania każdej podklasy. ADO.NET 2.0 używa przestrzeni nazw podklasy jako unikatowego identyfikatora ciągu. Na przykład unikatowy identyfikator ciągu
System.Data.SqlClient iSystem.Data.OracleClient identyfikują odpowiednioSqlClientFactory i y.OracleClientFactor - Plik tekstowy (zwykle plik XML) służy do przechowywania informacji o wszystkich podklasach. ADO.NET 2.0 używa plików machine.config i web.config do przechowywania wymaganych informacji. Informacje o podklasie zawierają między innymi unikatowy identyfikator ciągu i nazwę typu podklasy. Na przykład informacje o podklasie SqlClientFactory zawierają unikatowy identyfikator ciągu System.Data.SqlClient oraz nazwę typu podklasy, tj. System.Data.SqlClient.SqlClientFactory.
- Metoda statyczna została zaprojektowana i zaimplementowana. Metoda może być częścią abstrakcyjnej klasy bazowej lub części oddzielnej klasy. ADO.NET 2.0 używa oddzielnej klasy o nazwie DbProviderFactories uwidaczniającą metodę statyczną GetFactory. Metoda przyjmuje unikatowy identyfikator ciągu żądanej podklasy jako jedyny argument i przeszukuje plik machine.config dla podklasy o podanym unikatowym identyfikatorze ciągu. Metoda wyodrębnia nazwę żądanej podklasy i używa odbicia w celu dynamicznego utworzenia wystąpienia podklasy.
Kod dostępu do danych (metoda GetSubscribers) wywołuje metodę statyczną GetFactory i przekazuje odpowiedni unikatowy identyfikator ciągu w celu uzyskania dostępu do wystąpienia odpowiedniej podklasy. Gdy metoda GetSubscribers uzyskuje dostęp do wystąpienia, wywołuje odpowiednie metody tworzenia, takie jak CreateConnection(), CreateCommand(), itp., aby utworzyć wystąpienie odpowiednich obiektów ADO.NET, jak pokazano na rysunku 3.
Rysunek 3. Wersja metody GetSubscribers, która używa nowego wzorca dostawcy ADO.NET
public IEnumerable GetSubscribers()
{
DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection con = provider.CreateConnection();
con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";
DbCommand com = provider.CreateCommand();
com.Connection = con;
com.CommandText = "Select * From Subscribers";
com.CommandType = CommandType.Text;
DataSet ds = new DataSet();
DbDataAdapter ad = provider.CreateDataAdapter();
ad.SelectCommand = com;
con.Open();
ad.Fill(ds);
con.Close();
return ds.Tables[0].DefaultView;
}
Kod dostępu do danych (metoda GetSubscribers) deleguje odpowiedzialność za podanie kodu do tworzenia obiektów ADO.NET do wystąpienia klasy dostawcy kodu, które metoda GetFactory tworzy i zwraca. W związku z tym ten sam kod dostępu do danych może służyć do uzyskiwania dostępu do różnych magazynów danych, takich jak Microsoft SQL Server i Oracle.
Kod tworzenia odpowiednich obiektów ADO.NET jest specyficzny dla magazynu danych. Wzorzec dostawcy w ADO.NET 2.0 usuwa te części specyficzne dla magazynu danych z kodu dostępu do danych (metoda GetSubscribers), aby uczynić go bardziej ogólnym. Jednak wzorzec dostawcy nie usuwa wszystkich części specyficznych dla magazynu danych. Bliższa kontrola metody GetSubscribers ujawnia następujące pozostałe części specyficzne dla magazynu danych:
- Parametry połączenia
- Unikatowy identyfikator ciągu identyfikujący podstawową klasę dostawcy kodu
- Tekst polecenia
- Typ polecenia
Jeśli coś nie zostanie zrobione w powyższych częściach, kod dostępu do danych jest nadal powiązany z określonym typem magazynu danych. Wzorzec dostawcy w wersji ADO.NET 2.0 nie pomaga w tym problemie. Jednak ADO.NET 2.0 udostępnia nam inne narzędzia i techniki usuwania pierwszych dwóch części specyficznych dla magazynu danych, takich jak parametry połączenia i unikatowy identyfikator parametrów z metody GetSubscribers.
Parametry połączenia
Parametry połączenia to niektóre z najcenniejszych zasobów w aplikacji internetowej. Są one tak ważne, że program .NET Framework 2.0 traktuje je jako "obywateli pierwszej klasy". Plik web.config obsługuje teraz nową sekcję o nazwie <connectionStrings>, która zawiera wszystkie parametry połączenia używane w aplikacji. W związku z tym przeniesiemy parametry połączenia z metody GetSubscribers do tej sekcji:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add
name="MySqlConnectionString"
connectionString="Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
Element <dodawania> podelementu <connectionStrings> uwidacznia następujące trzy ważne atrybuty:
- Nazwa— przyjazna nazwa parametrów połączenia
- connectionString— rzeczywiste parametry połączenia
- providerName— unikatowy identyfikator ciągu lub niezmienny klasy dostawcy kodu
Program NET Framework 2.0 udostępnia kod dostępu do danych (metodę GetSubscribers) z odpowiednimi narzędziami w celu ogólnego wyodrębnienia wartości parametrów połączenia z pliku web.config zgodnie z opisem w poniższym artykule. Przestrzeń nazw System.Configuration w programie .NET Framework 2.0 zawiera nową klasę o nazwie Configuration. Ta klasa reprezentuje całą zawartość pliku web.config lub machine.config. Kod dostępu do danych nie może użyć nowego operatora do bezpośredniego utworzenia wystąpienia tej klasy.
Sama klasa uwidacznia statyczną metodę o nazwie GetWebConfiguration, która pobiera ścieżkę do pliku web.config i zwraca wystąpienie klasy Configuration, która reprezentuje całą zawartość pliku web.config:
Configuration configuration = Configuration.GetWebConfiguration("~/");
Klasa dziedziczona z klasy ConfigurationSection reprezentuje każdą sekcję pliku web.config. Nazwa klasy składa się z nazwy sekcji oraz słowa kluczowego Sekcja. Na przykład klasa ConnectionStringsSection reprezentuje zawartość <connectionStrings> pliku web.config. Kod dostępu do danych (metoda GetSubscribers) nie może użyć nowego operatora do bezpośredniego utworzenia wystąpienia klasy ConnectionStringsSection. Klasa Configuration uwidacznia właściwość kolekcji o nazwie Sections zawierającą wszystkie obiekty reprezentujące różne sekcje pliku web.config:
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
Ponieważ sekcje są kolekcją obiektów ConfigurationSection, kod dostępu do danych musi rzutować zwrócone wystąpienie. Po dokonaniu dostępu do obiektu ConnectionStringsSection za pomocą metody GetSubscribers uzyskuje dostęp do wartości parametrów połączenia:
string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;
Rysunek 4 przedstawia nową wersję metody GetSubscribers zawierającą wymagany kod do wyodrębnienia parametrów połączenia w sposób ogólny.
Rysunek 4. Wersja metody GetSubscribers, która wyodrębnia parametry połączenia z pliku web.config
public IEnumerable GetSubscribers()
{
DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection con = provider.CreateConnection();
Configuration configuration = Configuration.GetWebConfiguration("~/");
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
con.ConnectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;
DbCommand com = provider.CreateCommand();
com.Connection = con;
com.CommandText = "Select * From Subscribers";
com.CommandType = CommandType.Text;
DataSet ds = new DataSet();
DbDataAdapter ad = provider.CreateDataAdapter();
ad.SelectCommand = com;
con.Open();
ad.Fill(ds);
con.Close();
return ds.Tables[0].DefaultView;
}
Metoda GetSubscribers zawiera teraz ciąg MySqlConnectionString, dlatego nadal musimy zmodyfikować metodę GetSubscribers, aby dodać obsługę innego magazynu danych, takiego jak baza danych Oracle. Wydawałoby się, że wracamy do kwadratu. Tak naprawdę nie. Uzyskaliśmy kilka ważnych korzyści dzięki przeniesieniu parametrów połączenia z kodu dostępu do danych do pliku web.config:
Parametry połączenia są teraz wartością atrybutu connectionString <dodać> podrzędnego elementu connectionStrings pliku web.config, który jest dokumentem XML. Wielką rzeczą dotyczącą dokumentu XML jest to, że możemy zaszyfrować jeden element w dokumencie. Nie musimy szyfrować całego dokumentu, jeśli musimy chronić tylko niewielką część dokumentu. Program .NET Framework 2.0 zawiera narzędzie, które umożliwia szyfrowanie sekcji <connectionStrings> w celu ochrony naszego najważniejszego zasobu, parametrów połączenia. Wyobraź sobie, ile szkody może zrobić haker do naszej cennej bazy danych, jeśli dostanie rękę na naszych parametrach połączenia. Pamiętaj, że parametry połączenia to wszyscy hakerzy muszą uzyskać dostęp do naszej bazy danych.
Może się wydawać, że wszystko, co zrobiliśmy, to zastąpić następujący ciąg
"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
z nowym ciągiem MySqlConnectionString. Istnieje jednak jedna duża różnica. Poprzedni ciąg zawiera informacje specyficzne dla bazy danych programu SQL Server, które nie dotyczą innej bazy danych, takiej jak Oracle, ale ten ostatni ciąg jest po prostu przyjazną nazwą.
Jednak przyjazna nazwa może nadal powodować problemy, ponieważ odwołuje się do określonych parametrów połączenia w <connectionStrings> sekcji pliku web.config. W naszym przykładzie odnosi się on do parametrów połączenia używanych do uzyskiwania dostępu do programu Microsoft SQL Server. Oznacza to, że metoda GetSubscribers (kod dostępu do danych) musi zostać zmodyfikowana w celu użycia innej przyjaznej nazwy w celu uzyskania dostępu do innego magazynu danych, takiego jak Oracle.
Aby uniknąć modyfikowania kodu dostępu do danych, możemy przenieść przyjazną nazwę z kodu dostępu do danych do sekcji <appSettings> pliku web.config i dynamicznie wyodrębnić go w środowisku uruchomieniowym w następujący sposób:
string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
Przenosimy również nazwę dostawcy do sekcji <appSettings>:
string providerName = ConfigurationSettings.AppSettings["ProviderName"];
Deweloperzy stron po prostu zmieniają wartość atrybutu <dodać> podelement <appSettings> element do tego samego kodu dostępu do danych, aby uzyskać dostęp do innego magazynu danych bez wprowadzania żadnych zmian w kodzie dostępu do danych.
Rysunek 5 przedstawia wersję kodu dostępu do danych (metodę GetSubscribers), która zawiera ostatnie zmiany.
Rysunek 5. Wersja metody GetSubscribers w celu wyodrębnienia nazwy dostawcy i przyjaznej nazwy parametrów połączenia z pliku web.config
public IEnumerable GetSubscribers()
{
string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
string providerName = ConfigurationSettings.AppSettings["ProviderName"];
Configuration configuration = Configuration.GetWebConfiguration("~/");
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
DbConnection con = provider.CreateConnection();
con.ConnectionString = section.ConnectionStrings[connectionStringName].ConnectionString;
DbCommand com = provider.CreateCommand();
com.Connection = con;
com.CommandText = "Select * From Subscribers";
com.CommandType = CommandType.Text;
DataSet ds = new DataSet();
DbDataAdapter ad = provider.CreateDataAdapter();
ad.SelectCommand = com;
con.Open();
ad.Fill(ds);
con.Close();
return ds.Tables[0].DefaultView;
}
Kontrolki źródła danych
Najnowsza wersja metody GetSubscribers, jak pokazano na rysunku 5, nadal nie jest ogólna z powodu następujących problemów:
- Metoda zawiera magazyn danych — określone informacje, tj. tekst polecenia i typ polecenia. W związku z tym deweloperzy stron nadal muszą zmodyfikować metodę, zanim będą mogli jej użyć do uzyskania dostępu do innej bazy danych.
- Metoda zwraca wystąpienie klasy DataView do elementów wywołujących, tak aby metoda karmiła dane tabelaryczne obiektów wywołujących. Możemy traktować to jako kontrakt między metodą GetSubscribers i jej obiektami wywołującymi. Osoby wywołujące oczekują, że metoda GetSubscribers będzie przestrzegać kontraktu we wszystkich okolicznościach, nawet jeśli sam magazyn danych bazowych nie jest tabelaryczny. Ponieważ metoda GetSubscribers używa obiektów ADO.NET w celu uzyskania dostępu do bazowego magazynu danych, nie może uzyskać dostępu do hierarchicznego magazynu danych, takiego jak plik XML, gdzie wystąpienia klas w pliku System.Xml i jego podrzędnych przestrzeni nazw muszą być używane zamiast ADO.NET obiektów.
Głównym problemem z kodem dostępu do danych używanym w metodzie GetSubscribers jest to, że zawiera on bezpośrednio rzeczywisty kod, który wyodrębnia dane z bazowego magazynu danych. Jest to dokładnie rodzaj problemu, który został zaprojektowany w celu rozwiązania wzorca dostawcy programu .NET Framework 2.0. Zgodnie ze wzorcem dostawcy metoda GetSubscribers musi delegować odpowiedzialność za podanie kodu na potrzeby uzyskiwania dostępu do magazynu danych do innej klasy. Jest ona nazywana klasą dostawcy kodu. Typ klasy dostawcy kodu zależy od typu magazynu danych, do której uzyskuje dostęp. Te klasy dostawcy kodu są zbiorczo znane jako kontrolki źródła danych. ASP.NET 2.0 zawiera kilka różnych typów kontrolek źródła danych, w tym SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSourcei SiteMapDataSource.
Kontrolka SqlDataSource została specjalnie zaprojektowana do aktualizowania, usuwania, wstawiania i wyodrębniania danych z relacyjnych magazynów danych, takich jak Microsoft SQL Server i Oracle. Kontrolka AccessDataSource to podklasa kontrolki SqlDataSource, która wie, jak pracować z bazami danych programu Microsoft Access. Z drugiej strony kontrolka ObjectDataSource używa obiektów biznesowych w pamięci jako magazynu danych. Kontrolka XmlDataSource została zaprojektowana specjalnie do wyodrębniania danych z dokumentów XML. Jednak kontrolka XmlDataSource nie zapewnia dostępu do zapisu do bazowego dokumentu XML.
Każda kontrola źródła danych uwidacznia jeden lub więcej widoków jego bazowego magazynu danych. Każdy widok jest wystąpieniem odpowiedniej klasy. Na przykład kontrolki SqlDataSource, AccessDataSource i ObjectDataSource udostępniają widoki, które są wystąpieniami klas SqlDataSourceView, AccessDataSourceViewi ObjectDataSourceView klas. Widoki ukrywają rzeczywisty typ bazowego magazynu danych i sprawiają, że działa on jak oczekiwany typ kodu dostępu do danych. Na przykład metoda GetSubscribers oczekuje tabelarycznego typu magazynu danych, ponieważ przesyła dane tabelaryczne swoich klientów. Widoki tabelaryczne umożliwiają metodę GetSubscribers wyodrębnianie danych tabelarycznych z bazowego magazynu danych nawet wtedy, gdy sam magazyn danych jest hierarchicznym źródłem danych, takim jak dokument XML. Dzięki temu metoda GetSubscribers traktuje magazyny danych tabelarycznych i hierarchicznych jako magazyny danych tabelarycznych.
Kontrolki źródła danych mogą udostępniać klientom dwa typy widoków: tabelaryczne i hierarchiczne. ASP.NET 2.0 zawiera dwie kontrolki źródła danych, które zapewniają oba typy widoków, XmlDataSource i SiteMapDataSource. Pozostałe kontrolki źródła danych — SqlDataSource, AccessDataSource i ObjectDataSource — przedstawiają tylko widoki tabelaryczne. Można je jednak rozszerzyć w celu zapewnienia widoków tabelarycznych i hierarchicznych.
Tabelaryczne kontrolki źródła danych
Tabelaryczna kontrola źródła danych sprawia, że jego bazowy magazyn danych działa jak tabelaryczny magazyn danych, niezależnie od tego, czy magazyn danych jest tabelaryczny. Tabelaryczny magazyn danych składa się z tabel wierszy i kolumn, w których każdy wiersz reprezentuje element danych. Nazwa tabeli jednoznacznie identyfikuje i lokalizuje tabelę między innymi tabelami w tabelarycznym magazynie danych. Widok tabelaryczny działa jak tabela, co oznacza, że widoki są nazwane.
Jak wspomniano wcześniej, każda klasa kontroli źródła danych i skojarzona z nią klasa widoku (np. klasa SqlDataSource i skojarzona z nią klasa SqlDataSourceView) udostępniają rzeczywisty kod do aktualizowania, usuwania, wstawiania i wyodrębniania danych z bazowego magazynu danych. Oczywiście kod dla każdej kontroli źródła danych i skojarzonej z nią klasy widoku jest specjalnie zaprojektowany do pracy z określonym typem magazynu danych. W związku z tym każda klasa kontroli źródła danych i skojarzona z nią klasa widoku są specyficzne dla magazynu danych. Stanowi to poważny problem dla metody GetSubscribers, która używa kontrolek źródła danych i ich widoków tabelarycznych w celu uzyskania dostępu do bazowego magazynu danych, ponieważ wiąże metodę z określonym typem magazynu danych, co oznacza, że ta sama metoda nie może być używana do uzyskiwania dostępu do różnych typów magazynów danych.
ASP.NET 2.0 oferuje rozwiązanie, które używa wzorca dostawcy do:
- Wprowadzenie do interfejsu IDataSource
i klasy abstrakcyjnej DataSourceView - Uzyskiwanie wszystkich tabelarycznych kontrolek źródła danych z interfejsu IDataSource
- Uzyskiwanie wszystkich widoków tabelarycznych z klasy abstrakcyjnej DataSourceView
Interfejs IDataSource i klasa abstrakcyjna DataSourceView delegują odpowiedzialność za dostarczanie rzeczywistego kodu do aktualizowania, usuwania, wstawiania i wyodrębniania danych z magazynu danych do odpowiednich podklas. Kod dostępu do danych, taki jak metoda GetSubscribers, musi używać metod i właściwości interfejsu IDataSourceSource i klasy abstrakcyjnej DataSourceView. Nie mogą używać żadnej metody lub właściwości specyficznej dla określonej klasy kontroli źródła danych, takiej jak SqlDataSource lub określona klasa widoku źródła danych, taka jak SqlDataSourceView. Wzorzec dostawcy umożliwia kodowi dostępu do danych traktowanie wszystkich kontrolek źródła danych i ich odpowiednich widoków źródła danych w ogólny sposób. Jeśli chodzi o kod dostępu do danych, wszystkie kontrolki źródła danych są typu IDataSource, a wszystkie widoki źródeł danych są typu DataSourceView. Kod dostępu do danych nie ma możliwości poznania rzeczywistego typu kontroli źródła danych i używanego obiektu widoku źródła danych. Powoduje to nowy problem. Jeśli kod dostępu do danych (metoda GetSubscribers) nie zna typu kontroli źródła danych, jak można utworzyć wystąpienie wystąpienia tego wystąpienia?
Jak wspomniano wcześniej, wzorzec dostawcy udostępnia rozwiązanie, które składa się z następujących kroków:
- Unikatowy identyfikator ciągu służy do identyfikowania każdej klasy kontroli źródła danych.
- Plik tekstowy (zwykle plik XML) służy do przechowywania informacji o wszystkich klasach kontroli źródła danych.
- Mechanizm został zaprojektowany i zaimplementowany, który wyszukuje plik XML dla podklasy o danym identyfikatorze ciągu.
Teraz zobaczmy, jak ASP.NET 2.0 implementuje powyższe trzy zadania wzorca dostawcy. ASP.NET 2.0 uzyskuje wszystkie kontrolki źródła danych z klasy control
- Można je utworzyć deklaratywnie.
- Zapisują i przywracają swoje wartości właściwości po powrocie.
- Są one dodawane do drzewa kontrolek zawierającej stronę.
Pierwsza funkcja umożliwia deweloperom stron deklaratywne tworzenie wystąpień kontrolek źródła danych w odpowiednich plikach .aspx. W związku z tym plik .aspx działa jako plik tekstowy lub XML, którego wymaga drugi krok wzorca dostawcy. Architektura sterowania ASP.NET 2.0 dynamicznie tworzy wystąpienie zadeklarowanej kontroli źródła danych i przypisuje wystąpienie do zmiennej, której nazwa jest wartością właściwości ID zadeklarowanego źródła danych. Obejmuje to pierwsze i trzecie kroki wymagane przez wzorzec dostawcy.
Rysunek 6 przedstawia wersję metody GetSubscribers, która używa metod i właściwości interfejsu IDataSourceSource i klasy abstrakcyjnej DataSourceView w celu uzyskania dostępu do bazowego magazynu danych:
Rysunek 6. Wersja metody GetSubscribers używająca metod i właściwości interfejsu IDataSource oraz klasy abstrakcyjnej DataSourceView
void GetSubscribers()
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
DataSourceSelectArguments args = new DataSourceSelectArguments();
if (dv.CanSort)
args.SortExpression = "Email";
DataSourceViewSelectCallback callback = new DataSourceViewSelectCallback(SendMail);
dv.Select(args, callback);
}
Pierwszy wiersz metody GetSubscribers wyraźnie pokazuje, że metoda traktuje kontrolkę źródła danych jako obiekt typu IDataSource. Metoda nie powinna dbać o rzeczywisty typ kontrolki źródła danych, na przykład czy jest to kontrolka SqlDataSource, AccessDataSource, ObjectDataSource lub XmlDataSource. Umożliwi to deweloperom stron przełączenie się z jednej kontroli źródła danych na inną bez konieczności modyfikowania kodu dostępu do danych (metoda GetSubscribers). W następnej sekcji omówiono tę ważną kwestię, bardziej szczegółowo.
Metoda GetSubscribers wywołuje metodę GetView obiektu IDataSource, aby uzyskać dostęp do domyślnego obiektu widoku tabelarycznego. Zwróć uwagę, że metoda GetView zwraca obiekt typu DataSourceView. Metoda GetSubscribers nie powinna dbać o rzeczywisty typ obiektu widoku, na przykład czy jest to obiekt SqlDataSourceView, AccessDataSourceView, ObjectDataSourceView lub XmlDataSourceView.
Następnie metoda GetSubscribers tworzy wystąpienie klasy DataSourceSelectArguments, aby zażądać dodatkowych operacji, takich jak wstawianie, stronicowanie lub pobieranie łącznej liczby wierszy zwracanych przez operację Select. Metoda najpierw musi sprawdzić wartość właściwości CanInsert, CanPagelub CanRetrieveTotalRowCount właściwości klasy DataSourceView, aby upewnić się, że obiekt widoku obsługuje odpowiednią operację przed wysłaniem żądania.
Ponieważ operacja Select jest asynchroniczna, metoda GetSubscribers rejestruje metodę SendMail jako wywołanie zwrotne. Metoda Select automatycznie wywołuje metodę SendMail po wysłaniu zapytania do danych i przekazywaniu danych jako argumentu, jak pokazano na rysunku 7.
Rysunek 7. Metoda SendMail wylicza dane i wyodrębnia niezbędne informacje.
void SendMail(IEnumerable data)
{
string firstName = String.Empty;
string lastName = String.Empty;
IEnumerator iter = data.GetEnumerator();
while (iter.MoveNext())
{
MailMessage message = new MailMessage();
message.From = "admin@greatnews.com";
message.To = DataBinder.Eval(iter.Current, "Email").ToString();
message.Subject = "NewsLetter";
firstName = DataBinder.Eval(iter.Current, "FirstName").ToString();
lastName = DataBinder.Eval(iter.Current, "LastName").ToString();
string mes = "Dear " + firstName + " " + lastName + ",<br/>";
mes += MessageBody.Text;
message.Body = mes;
message.BodyFormat = MailFormat.Html;
SmtpMail.SmtpServer = "<myserver>";
SmtpMail.Send(message);
}
}
Metoda SendMail wywołuje metodę GetEnumerator obiektu przekazanego jako pierwszy argument w celu uzyskania dostępu do obiektu modułu wyliczającego i używa modułu wyliczającego do wyliczania danych. Metoda SendMail używa metody Eval klasy DataBinder, aby wyodrębnić adres e-mail, imię i nazwisko każdego subskrybenta oraz wysyła list wiadomości do każdego z nich.
Przełączanie z jednej kontroli źródła danych na inną
Jak wspomniano wcześniej, architektura sterowania ASP.NET dynamicznie tworzy wystąpienie kontrolki źródła danych zadeklarowanej na odpowiedniej stronie .aspx i przypisuje ją do zmiennej, której nazwa jest wartością id właściwości zadeklarowanego źródła danych. Takie dynamiczne utworzenie wystąpienia zadeklarowanej kontrolki źródła danych izoluje metodę GetSubscribers z rzeczywistego typu kontrolki źródła danych i umożliwia metodzie traktowanie wszystkich kontrolek źródła danych jako obiektów typu IDataSource. Dzięki temu deweloperzy stron mogą przełączać się z jednego typu kontroli źródła danych na inny bez modyfikowania kodu dostępu do danych (metoda GetSubscribers). W tej sekcji przedstawiono przykład takiego przypadku.
Załóżmy, że nasza aplikacja internetowa używa metody GetSubscribers w połączeniu z kontrolką ObjectDataSource, aby pobrać listę subskrybentów z relacyjnego magazynu danych, takiego jak microsoft SQL Server:
<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />
Załóżmy, że nasza aplikacja internetowa działa w środowisku, w którym lista subskrybentów może również pochodzić z dokumentu XML. Dokument XML może być lokalnym plikiem XML lub zasobem zdalnym dostępnym za pośrednictwem adresu URL. W związku z tym nasza aplikacja musi mieć możliwość pobrania listy subskrybentów z dokumentów XML. Oczywiście kontrolka ObjectDataSource nie jest przeznaczona do pobierania danych tabelarycznych z dokumentów XML, co oznacza, że musimy użyć kontrolki źródła danych, która może pobierać dane tabelaryczne z dokumentów XML, takich jak kontrolka XmlDataSource.
Metoda GetSubscribers nie używa żadnej właściwości ani metody specyficznej dla klas ObjectDataSource i ObjectDataSourceView i używa tylko metod i właściwości interfejsu IDataSourceSource i klasy abstrakcyjnej DataSourceView do obsługi kontrolek źródła danych. Możemy łatwo przełączyć się z obiektu ObjectDataSource do kontrolki XmlDataSource i użyć tego samego kodu dostępu do danych co metoda GetSubscribers, aby pobrać listę subskrybentów:
<asp:XmlDataSource ID="MySource" Runat="Server"
DataFile="data.xml" XPath="/Subscribers/Subscriber" />
Wartość atrybutu XPath kontrolki XmlDataSource jest ustawiona na /Subskrybenci/Subskrybent, aby wybrać wszystkich subskrybentów.
Operacja wstawiania i usuwania
Przypomnij sobie, że nasza aplikacja internetowa składa się z dwóch części. Druga część aplikacji umożliwia użytkownikom subskrybowanie/anulowanie subskrypcji z listy adresowej. Metoda
Rysunek 8. Metoda Subskrybuj jest wywoływana po kliknięciu przycisku Subskrybuj.
void Subscribe(Object sender, EventArgs e)
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
KeyedList values = new KeyedList();
values.Add("Email", Email.Text);
values.Add("FirstName", FirstName.Text);
values.Add("LastName", LastName.Text);
DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
if (dv.CanInsert)
dv.Insert(values, callback);
}
Pierwszy wiersz metody Subscribe pokazuje, że metoda nie dba o rzeczywisty typ kontroli źródła danych. W związku z tym możemy przełączyć się do innej kontroli źródła danych, aby obsługiwać nowy magazyn danych bez konieczności zmiany kodu w metodzie Subskrybuj.
Metoda używa wystąpienia klasy KeyedList do zbierania wiadomości e-mail, imienia i nazwiska subskrybenta. Nie musimy używać klasy KeyedList. Możemy użyć dowolnej klasy, która implementuje interfejs
Metoda Subskrybuj sprawdza wartość właściwości CanInsert obiektu widoku źródła danych, aby upewnić się, że obiekt widoku obsługuje operację Insert, zanim wywoła metodę Insert. Metoda Subskrybuj przekazuje wystąpienie KeyedList jako pierwszy argument do metody Insert.
Metoda Anuluj subskrypcję działa podobnie do metody Subskrybuj. Główną różnicą jest to, że metoda Anuluj subskrypcję wywołuje metodę Delete odpowiedniego obiektu widoku w celu usunięcia subskrypcji z bazowego magazynu danych, jak pokazano na rysunku 9.
Rysunek 9. Metoda Anuluj subskrypcję jest wywoływana po kliknięciu przycisku Anuluj subskrypcję.
void Unsubscribe(Object sender, EventArgs e)
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
KeyedList keys = new KeyedList();
keys.Add("Email", Email.Text);
KeyedList oldValues = new KeyedList();
oldValues.Add("Email", Email.Text);
DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
if (dv.CanDelete)
dv.Delete(keys, oldValues, callback);
}
Hierarchiczne kontrolki źródła danych
Metoda GetSubscribers (kod dostępu do danych) przesyła dane tabelaryczne do obiektów wywołujących. Jednak czasami kod dostępu do danych musi zwracać dane hierarchiczne do swoich obiektów wywołujących. W tej sekcji przedstawiono implementację wersji metody GetSubscribers zwracającej dane hierarchiczne. Możemy o tym myśleć jako o kontrakcie między metodą a jej obiektami wywołującym. Obiekt wywołujący oczekuje, że metoda zwróci dane hierarchiczne zarówno z hierarchicznych, jak i tabelarycznych magazynów danych.
ASP.NET 2.0 używa wzorca dostawcy do izolowania metody GetSubscribers z rzeczywistego typu bazowego magazynu danych i przedstawia metodę z hierarchicznymi widokami magazynu danych. Dzięki temu metoda może traktować zarówno hierarchiczne, jak i tabelaryczne magazyny danych jako hierarchiczne magazyny danych.
Każda hierarchiczna kontrola źródła danych jest specjalnie zaprojektowana do pracy z określonym magazynem danych. Jednak ponieważ wszystkie hierarchiczne kontrolki źródła danych implementują interfejs IHierarchicalDataSource i wszystkie hierarchiczne widoki źródła danych pochodzące z klasy HierarchicalDataSourceView, metoda GetSubscribers nie musi zajmować się specyfikami każdej kontrolki źródła danych i może traktować wszystkie z nich w sposób ogólny.
IHierarchicalEnumerable GetSubscribers()
{
IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
return dv.Select();
}
Pierwszy wiersz metody GetSubscribers pokazuje, że metoda traktuje kontrolkę źródła danych jako obiekt typu IHierarchicalDataSource i nie dba o rzeczywisty typ kontrolki źródła danych. Umożliwi to deweloperom stron przejście do nowej hierarchicznej kontroli źródła danych w celu dodania obsługi nowego magazynu danych bez konieczności modyfikowania kodu w metodzie GetSubscribers.
Następnie metoda GetSubscribers wywołuje metodę GetHierarchicalView metody HierarchicalDataSourceView, aby uzyskać dostęp do widoku hierarchicznego z daną ścieżką, taką jak "/Subskrybenci". Zwróć uwagę, że metoda Select nie jest asynchroniczna. Aplikacja przekazuje dane zwrócone z metody GetSubscribers do metody SendMail (zobacz Rysunek 15). Zwróć uwagę, że dane są typu IHierarchicalEnumerable.
Element IHierarchicalEnumerable implementuje IEnumerable, co oznacza, że uwidacznia metodę GetEnumerator. Metoda SendMail wywołuje metodę GetEnumerator w celu uzyskania dostępu do odpowiedniego obiektu IEnumerator, który jest następnie używany do wyliczania danych. Element IHierarchicalEnumerable uwidacznia również metodę o nazwie GetHierarchyData, która pobiera wyliczony obiekt i zwraca skojarzony z nim obiekt IHierarchyData.
Interfejs IHierarchyData uwidacznia ważną właściwość o nazwie Item, która nie jest elementem danych. Metoda SendMail używa metody Eval klasy XPathBinder do oceny wyrażeń XPath względem obiektu Item.
Rysunek 10. Metoda SendMail wylicza dane, wyodrębnia niezbędne informacje i wysyła biuletyn do każdego subskrybenta.
void SendMail(IHierarchicalEnumerable data)
{
string firstName = String.Empty;
string lastName = String.Empty;
IEnumerator iter = data.GetEnumerator();
while (iter.MoveNext())
{
IHierarchyData ihdata = data.GetHierarchyData(iter.Current);
MailMessage message = new MailMessage();
message.From = "admin@subscribers.com";
message.To = XPathBinder.Eval(ihdata.Item, "@Email").ToString();
message.Subject = "NewsLetter";
firstName = XPathBinder.Eval(ihdata.Item, "@FirstName").ToString();
lastName = XPathBinder.Eval(ihdata.Item, "@LastName").ToString();
string mes = "Hi " + firstName + " " + lastName + ",<br/>";
mes += MessageBody.Text;
message.Body = mes;
message.BodyFormat = MailFormat.Html;
SmtpMail.SmtpServer = "MyServer";
SmtpMail.Send(message);
}
}
Konkluzja
Korzystając z podejścia krok po kroku przedstawiającego różne narzędzia i techniki ASP.NET 2.0 i ADO.NET 2.0, w tym artykule pokazano, jak deweloperzy stron mogą pisać ogólny kod dostępu do danych, który może służyć do uzyskiwania dostępu do różnych typów magazynów danych.
dr Shahram Khosravijest starszym inżynierem oprogramowania z Schlumberger Information Solutions (SIS). Shahram specjalizuje się w ASP.NET, usługach sieci Web XML, technologiach .NET, technologiach XML, grafika komputerowa 3D, HI/Użyteczność, wzorce projektowe i opracowywanie kontrolek i składników serwera ASP.NET. Ma ponad 10 lat doświadczenia w programowaniu obiektowym. Używa różnych narzędzi i technologii firmy Microsoft, takich jak SQL Server i ADO.NET. Shahram napisał artykuły na temat platformy .NET i technologii ASP.NET dla magazynu asp.netPRO.