Współdzielenie stanu sesji pomiędzy klasycznymi aplikacjami ASP a aplikacjami ASP.NET
Billy Yuen
Microsoft Corporation
Dotyczy:
Microsoft® ASP.NET
Streszczenie
W artykule wyjaśniono, w jaki sposób współdzielić stan sesji pomiędzy klasycznym ASP a ASP.NET, wykorzystując klasy Microsoft .NET Framework oraz funkcjonalność serializacji platformy .NET Framework. Współdzielenie stanu sesji pozwala na przeprowadzenie konwersji istniejących aplikacji ASP do ASP.NET etapami, gdy stara i nowa część aplikacji działają równolegle.
Długość dokumentu — 12 stron po wydrukowaniu
Pobierz przykładowy kod źródłowy do tego artykułu
Spis treści
Wstęp
Wstęp teoretyczny
Implementacja w ASP.NET
Implementacja w ASP
Program demonstracyjny
Włączanie obiektów COM w istniejącą aplikację ASP
Ograniczenia i możliwości rozwoju kodu
Podsumowanie
Wstęp
ASP.NET firmy Microsoft® jest najnowszą technologią do tworzenia aplikacji internetowych. Zalety ASP.NET w porównaniu z klasycznym ASP to: 1) lepsza struktura wdrożeniowa dzięki oddzieleniu warstwy prezentacyjnej (interfejs użytkownika) od logiki biznesowej; 2) kod ASP.NET jest w pełni kompilowany w przeciwieństwie do kodu klasycznego ASP, który jest interpretowany; 3) możliwość kompilowania kodu w połączeniu z buforowaniem oznacza znacznie większą wydajność witryn napisanych w ASP.NET w porównaniu z ich odpowiednikami utworzonymi w klasycznym ASP.
Konwersja aplikacji ASP do ASP.NET może przynieść wiele korzyści, jednak istnieje sporo aplikacji ASP o znaczeniu krytycznym. Aplikacje takie często są bardzo skomplikowane. Proces konwersji może wymagać zaangażowania dużych zasobów i może powodować niepoprawne działanie istniejącej aplikacji. Jednym ze sposobów na rozwiązanie tych problemów jest równoległe uruchamianie aplikacji ASP i ASP.NET oraz konwersja aplikacji moduł po module. Aby równolegle uruchomić starą i nową aplikację, wymagane jest współdzielenie stanu sesji pomiędzy klasycznym ASP i ASP.NET. W niniejszym artykule wyjaśniono, jak z użyciem kilku klas oraz funkcji serializacji Microsoft® .NET Framework współdzielić stan sesji pomiędzy aplikacjami.
Wstęp teoretyczny
Pliki cookie to najpopularniejszy sposób identyfikacji sesji użytkownika w aplikacjach internetowych, który może służyć do identyfikacji stanu sesji zarówno w ASP jak i w ASP.NET. Informacja o stanie sesji jest przechowywana w pamięci skryptu ASP i nie może być współdzielona z innymi aplikacjami, takimi jak ASP.NET. Jeśli natomiast stan sesji byłby przechowywany w standardowym formacie w bazie Microsoft® SQL Server, wówczas dostęp do niego mógłby być realizowany zarówno przez klasyczny kod ASP i jak i kod ASP.NET.
W niniejszym przykładzie do identyfikacji sesji użytkownika używany jest plik cookie o nazwie mySession. Gdy użytkownik wysyła żądanie do aplikacji internetowej, serwer tworzy unikatowy plik cookie identyfikujący sesję i wysyła go do użytkownika. Przy kolejnym żądaniu unikatowy plik cookie wysyłany jest z powrotem do serwera. Plik cookie umożliwia identyfikację sesji użytkownika i załadowanie z serwera SQL (za pomocą specjalnie przygotowanej klasy) danych o sesji użytkownika. Ta specjalnie przygotowana klasa zapewnia dostęp do całego stanu sesji w aplikacji internetowej. Po zakończeniu obsługi żądania, dane o sesji zostaną ponownie zapisane w bazie serwera SQL (patrz ilustracja 1).
Ilustracja 1. Przepływ danych
Implementacja w ASP.NET
Każda strona internetowa ASP.NET jest klasą pochodną klasy System.Web.UI.Page. Klasa Page zawiera instancję obiektu HttpSession, w którym przechowywane są dane sesji. W tym przykładzie opracowano niestandardową klasę pochodną System.Web.UI.Page — klasę o nazwie SessionPage, zapewniającą taką samą funkcjonalność co klasa Page. Jedyną różnicą pomiędzy klasami SessionPage oraz Page jest to, że domyślny obiekt HttpSession został zastąpiony niestandardowym obiektem sesji (w języku C# zastosowanie modyfikatora new do składowej klasy pozwala ukryć składową odziedziczoną po klasie podstawowej).
public class SessionPage : System.Web.UI.Page
{
...
public new mySession Session = null;
...
}
Niestandardowa klasa sesji do przechowywania stanu sesji w pamięci operacyjnej wykorzystuje obiekt HybridDictionary (obiekt HybridDictionary może sprawnie przechowywać nieograniczoną liczbę elementów sesji). Aby umożliwić współpracę z klasycznym ASP, typ danych przechowywanych wewnątrz stanu sesji został ograniczony do typu string (domyślny obiekt HttpSession dopuszcza przechowywanie danych dowolnego typu, nie jest to jednak zgodne z klasycznym ASP).
[Serializable]
public class mySession
{
private HybridDictionary dic = new HybridDictionary();
public mySession()
{
}
public string this [string name]
{
get
{
return (string)dic[name.ToLower()];
}
set
{
dic[name.ToLower()] = value;
}
}
}
Klasa Page umożliwia dostosowywanie działania różnych zdarzeń i metod. Metoda OnInit jest używana do inicjowania stanu obiektu Page. Jeżeli w żądaniu nie przesłano pliku cookie mySession, do przeglądarki, która przesłała żądanie, zostanie wysłany nowy plik cookie mySession. Jeśli plik cookie został przesłany w żądaniu, to jego zawartość wykorzystana jest do pobrania stanu sesji z serwera SQL. Służy do tego niestandardowy obiekt dostępu do danych o nazwie SessionPersistence. Nazwa źródła danych DSN oraz wartość SessionExpiration pobierane są z pliku web.config.
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
cookie = this.Request.Cookies[sessionPersistence.SessionID];
if (cookie == null)
{
Session = new mySession();
CreateNewSessionCookie();
IsNewSession = true;
}
else
Session = sessionPersistence.LoadSession(
Server.UrlDecode(cookie.Value).ToLower().Trim(),
dsn,
SessionExpiration
);
this.Unload += new EventHandler(this.PersistSession);
}
private void CreateNewSessionCookie()
{
cookie = new HttpCookie(sessionPersistence.SessionID,
sessionPersistence.GenerateKey());
this.Response.Cookies.Add(cookie);
}
Klasa SessionPersistence do serializacji i deserializacji stanu sesji w formacie binarnym używa obiektu BinaryFormatter z Microsoft .NET Framework, co umożliwia uzyskanie optymalnej wydajności. Otrzymane dane binarne mogą być składowane w bazie danych serwera SQL jako pole typu image.
public mySession LoadSession(string key, string dsn,
int SessionExpiration)
{
SqlConnection conn = new SqlConnection(dsn);
SqlCommand LoadCmd = new SqlCommand();
LoadCmd.CommandText = command;
LoadCmd.Connection = conn;
SqlDataReader reader = null;
mySession Session = null;
try
{
LoadCmd.Parameters.Add("@ID", new Guid(key));
conn.Open();
reader = LoadCmd.ExecuteReader();
if (reader.Read())
{
DateTime LastAccessed =
reader.GetDateTime(1).AddMinutes(SessionExpiration);
if (LastAccessed >= DateTime.Now)
Session = Deserialize((Byte[])reader["Data"]);
}
}
finally
{
if (reader != null)
reader.Close();
if (conn != null)
conn.Close();
}
return Session;
}
private mySession Deserialize(Byte[] state)
{
if (state == null) return null;
mySession Session = null;
Stream stream = null;
try
{
stream = new MemoryStream();
stream.Write(state, 0, state.Length);
stream.Position = 0;
IFormatter formatter = new BinaryFormatter();
Session = (mySession)formatter.Deserialize(stream);
}
finally
{
if (stream != null)
stream.Close();
}
return Session;
}
Na koniec obsługi żądania zgłaszane jest zdarzenie Unload klasy Page. Procedura obsługi, zarejestrowana dla zdarzenia Unload, serializuje dane sesji do formatu binarnego i zachowuje binarne dane wynikowe w bazie danych serwera SQL.
private void PersistSession(Object obj, System.EventArgs arg)
{ sessionPersistence.SaveSession(
Server.UrlDecode(cookie.Value).ToLower().Trim(),
dsn, Session, IsNewSession);
}
public void SaveSession(string key, string dsn,
mySession Session, bool IsNewSession)
{
SqlConnection conn = new SqlConnection(dsn);
SqlCommand SaveCmd = new SqlCommand();
SaveCmd.Connection = conn;
try
{
if (IsNewSession)
SaveCmd.CommandText = InsertStatement;
else
SaveCmd.CommandText = UpdateStatement;
SaveCmd.Parameters.Add("@ID", new Guid(key));
SaveCmd.Parameters.Add("@Data", Serialize(Session));
SaveCmd.Parameters.Add("@LastAccessed", DateTime.Now.ToString());
conn.Open();
SaveCmd.ExecuteNonQuery();
}
finally
{
if (conn != null)
conn.Close();
}
}
private Byte[] Serialize(mySession Session)
{
if (Session == null) return null;
Stream stream = null;
Byte[] state = null;
try
{
IFormatter formatter = new BinaryFormatter();
stream = new MemoryStream();
formatter.Serialize(stream, Session);
state = new Byte[stream.Length];
stream.Position = 0;
stream.Read(state, 0, (int)stream.Length);
stream.Close();
}
finally
{
if (stream != null)
stream.Close();
}
return state;
}
Klasa SessionPage i inne związane z nią klasy znajdują się w podzespole SessionUtility. Aby móc współdzielić stan sesji z aplikacjami napisanymi w klasycznym ASP, w nowym projekcie ASP.NET należy utworzyć referencję do podzespołu SessionUtility, a każda strona internetowa musi być klasą pochodną klasy SessionPage, a nie klasy Page. Po zakończeniu konwersji kodu, nowa aplikacja może zostać zmodyfikowana tak, by używała rodzimego obiektu HttpSession. Wystarczy usunąć deklarację składowej Session z klasy SessionPage i odsłonić tym samym obiekt HttpSession.
Implementacja w ASP
Standardowy obiekt sesji, dostępny w ASP, może przechowywać dane jedynie w pamięci operacyjnej. Aby składować dane w bazie danych na serwerze SQL, konieczne jest napisanie — na przykład w Microsoft® Visual Basic® 6.0 — własnego obiektu COM, który zastąpi obiekt sesji wbudowany w ASP. Na początku obsługi każdego żądania tworzona będzie instancja tego obiektu. Kod inicjujący obiektu załaduje następnie dane sesji z serwera SQL. Po wykonaniu skryptu ASP instancja obiektu zostanie zwolniona, a dane sesji trafią z powrotem do serwera SQL.
Podstawowym zadaniem obiektu sesji jest zapewnienie dostępu do wewnętrznych obiektów usług IIS. Obiekt sesji do przechowywania stanu sesji używa klasy mySession z podzespołu SessionUtility, natomiast do ładowania i zapisywania danych sesji w bazie danych serwera SQL — klasy SessionPersistence tego podzespołu. Klasy mySession oraz SessionPersistence muszą zostać udostępnione jako obiekty COM za pomocą narzędzia regasm.exe. Regasm.exe pozwala na utworzenie i zarejestrowanie biblioteki typów, co umożliwia wykorzystywanie klas .NET Framework przez klientów COM.
Informacja o stanie sesji ładowana jest podczas tworzenia obiektu. Konstruktor obiektu (class_initialize) najpierw pobiera z obiektu Application plik cookie sesji, czas trwania sesji (SessionTimeOut) oraz łańcuch połączenia z bazą danych (SessionDSN). Następnie tworzy instancję obiektu mySession, który będzie przechowywał dane sesji. Po wykonaniu powyższych czynności konstruktor próbuje załadować stan sesji z serwera SQL, odpowiadający otrzymanemu plikowi cookie. Jeśli serwer SQL nie posiada informacji o sesji lub jeśli sesja wygasła, zostanie utworzony nowy plik cookie. Jeśli serwer SQL zwróci dane sesji, zostaną one umieszczone w obiekcie mySession.
Private Sub Class_Initialize()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "Class_Initialize"
Set mySessionPersistence = New SessionPersistence
Set myObjectContext = GetObjectContext()
mySessionID = ReadSessionID()
myDSNString = GetConnectionDSN()
myTimeOut = GetSessionTimeOut()
myIsNewSession = False
Call InitContents
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
Private Sub InitContents()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "InitContents"
If mySessionID = "" Then
Set myContentsEntity = New mySession
mySessionID = mySessionPersistence.GenerateKey
myIsNewSession = True
Else
Set myContentsEntity =
mySessionPersistence.LoadSession(mySessionID, myDSNString, myTimeOut)
End If
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
Po zakończeniu wykonywania skryptu ASP uruchamiany jest destruktor (class_terminate) obiektu przechowującego bieżące dane sesji. Destruktor zapisze dane sesji przy użyciu metody SessionPersistence.SaveSession(). Jeśli jest to nowa sesja, destruktor wyśle do przeglądarki nowy plik cookie.
Private Sub Class_Terminate()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "Class_Terminate"
Call SetDataForSessionID
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
Private Sub SetDataForSessionID()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "SetDataForSessionID"
Call mySessionPersistence.SaveSession(mySessionID,
myDSNString, myContentsEntity, myIsNewSession)
If myIsNewSession Then Call WriteSessionID(mySessionID)
Set myContentsEntity = Nothing
Set myObjectContext = Nothing
Set mySessionPersistence = Nothing
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
Kod źródłowy projektu SessionUtility dla ASP.NET, menedżer sesji COM oraz kod programu demonstracyjnego można pobrać klikając odnośnik znajdujący się na początku niniejszego artykułu.
Program demonstracyjny
Program demonstracyjny inkrementuje zmienną liczbową i wyświetla wynik wykonanej operacji. Liczba będzie wzrastać niezależnie od tego, czy ładowana będzie strona ASP czy ASP.NET, ponieważ wartość liczbowa przechowywana jest w bazie danych serwera SQL i jest współdzielona pomiędzy klasycznym ASP a ASP.NET.
Instalacja programu demonstracyjnego:
Utwórz nową bazę danych o nazwie SessionDemoDb.
Utwórz tabelę SessState (osql.exe -E -d SessionDemoDb -i Session.sql).
Utwórz na serwerze IIS nowy katalog wirtualny o nazwie Demo.
Wyłącz obsługę sesji ASP (na karcie konfiguracji aplikacji).
Do utworzonego wcześniej katalogu wirtualnego skopiuj pliki web.config, testPage.aspx, Global.asa, testPage.asp oraz GlobalInclude.asp.
Zmodyfikuj nazwę DSN w plikach Global.asa oraz web.config. Ustawienie automatycznego czasu zakończenia sesji jest opcjonalne. Wartość domyślna to 20 minut.
Plik SessionUtility.dll umieść w Global Assembly Cache (gacutil /i SessionUtility.dll).
Używając polecenia regasm.exe udostępnij plik SessionUtility.dll jako obiekt COM (regasm.exe SessionUtility.dll /tlb:SessionUtility.tlb).
Skopiuj plik SessionManager.dll do folderu lokalnego i zarejestruj go używając polecenia regsvr32.exe (regsvr32 SessionManager.dll).
Przypisz prawa odczytu i wykonania pliku SessionMgr.dll dla konta IUSR_<nazwa_komputera>.
Uruchomienie programu demonstracyjnego:
Uruchom przeglądarkę Microsoft® Internet Explorer.
Załaduj stronę testPage.asp wykorzystującą klasyczne ASP. W przeglądarce powinna pojawić się liczba „1”.
Kliknij przycisk Odśwież, aby ponownie załadować stronę. Liczba widoczna na ekranie powinna się zwiększyć.
Załaduj stronę testPage.aspx wykorzystującą ASP.NET. Liczba powinna ponownie wzrosnąć.
Proces ten powinien poprawnie przebiegać również w przypadku rozpoczęcia testu od załadowania strony testPage.aspx.
Włączanie obiektów COM w istniejącą aplikację ASP
Powszechną praktyką, stosowaną przy tworzeniu aplikacji ASP, jest dołączanie (include) na początku każdego skryptu pliku zawierającego współdzielone fragmenty kodu i stałe. Najlepszym sposobem na włączenie własnego obiektu sesji jest umieszczenie kodu tworzącego instancję tego obiektu we wspólnym pliku dołączanym (include file). Ostatni krok to po prostu zastąpienie wszystkich referencji do obiektu Session referencjami do naszego własnego obiektu przechowującego stan sesji.
Ograniczenia i możliwości rozwoju kodu
Podanego tu rozwiązania nie można zastosować w istniejących aplikacjach ASP, które obiekt Session wykorzystują do przechowywania obiektów COM. W takim wypadku do poprawnego działania niestandardowego obiektu sesji potrzebny jest własny mechanizm serializowania i deserializowania stanu sesji. Przedstawione tu rozwiązanie nie obsługuje także przechowywania tablic łańcuchów znaków. Realizacja tej funkcjonalności nie wymaga dużego nakładu pracy — do łączenia wszystkich elementów tablicy w jeden ciąg można wykorzystać funkcję Join języka Microsoft® Visual Basic® 6.0, a do podzielenia ciągu z powrotem na poszczególne elementy tablicy — funkcję Split. W .NET Framework metody Join oraz Split są elementami klasy String.
Podsumowanie
ASP.NET to nowe podejście do programowania i zupełnie nowa architektura aplikacji. Technologia ta ma wiele zalet w stosunku do klasycznego ASP. Chociaż przejście z ASP do ASP.NET nie jest procesem prostym, lepszy model programowania i poprawiona wydajność wynagradza czas poświęcony na konwersję kodu. Mimo że opisane tutaj rozwiązanie nie umożliwia składowania obiektów COM w obiekcie Session, powinno znacznie uprościć proces migracji.
O autorze
Billy Yuen jest pracownikiem Microsoft Technology Center w Dolinie Krzemowej w północnej Kalifornii. Ośrodek ten koncentruje się na tworzeniu rozwiązań opartych na Microsoft .NET Framework. Z autorem można skontaktować się pod adresem billyy@microsoft.com.
10