Hampir setiap aplikasi ASP.NET perlu melacak data untuk sesi pengguna. ASP.NET menyediakan kelas HttpSessionState untuk menyimpan nilai status sesi. Sebuah instance dari kelas HttpSessionState untuk setiap permintaan HTTP dapat diakses di seluruh aplikasi Anda menggunakan properti statis HttpContext.Current.Session. Akses ke instance yang sama menjadi lebih sederhana di setiap Halaman dan UserControl menggunakan properti Session pada Halaman atau UserControl.
Kelas HttpSessionState menyediakan kumpulan pasangan kunci/nilai, di mana kuncinya bertipe String dan nilainya bertipe Object. Ini berarti Sesi sangat fleksibel dan Anda dapat menyimpan hampir semua jenis data di Sesi.
Namun (selalu ada tetapi) fleksibilitas ini bukannya tanpa konsekuensi. Biayanya adalah kemudahan munculnya bug ke dalam aplikasi Anda. Banyak bug yang muncul tidak akan ditemukan melalui pengujian unit, dan mungkin tidak melalui pengujian terstruktur apa pun. Bug ini sering kali hanya muncul ketika aplikasi telah diterapkan ke lingkungan produksi. Ketika bug tersebut muncul ke permukaan, seringkali sangat sulit, bahkan tidak mungkin, untuk menentukan bagaimana bug tersebut terjadi dan dapat mereproduksi bug tersebut. Ini berarti biaya perbaikannya sangat mahal.
Artikel ini menyajikan strategi untuk membantu mencegah bug jenis ini. Ia menggunakan Pola Desain yang disebut Fasad, yang membungkus antarmuka gratis yang disediakan oleh kelas HttpSessionState (yang dapat memenuhi persyaratan aplikasi apa pun) dengan antarmuka yang dirancang dan dikontrol dengan baik yang dibuat khusus untuk aplikasi tertentu. Jika Anda tidak terbiasa dengan Pola Desain atau pola Fasad, pencarian cepat di internet tentang "pola desain fasad" akan memberi Anda banyak latar belakang. Namun, Anda tidak harus memahami pola desain untuk memahami artikel ini.
Contoh kode yang ditampilkan dalam artikel ini ditulis dalam C#, namun konsepnya dapat diterapkan pada bahasa .NET apa pun.
Apa masalahnya?
Pada bagian artikel ini saya akan menjelaskan masalah dengan akses langsung ke kelas HttpSessionState, tanpa fasad. Saya akan menjelaskan jenis bug yang dapat muncul.
Berikut ini menunjukkan kode tipikal yang ditulis untuk mengakses variabel status sesi.
// Menyimpan variabel sesi
Sesi["beberapa string"] = anyOldObject;
// Membaca variabel sesi
DateTime startDate = (DateTime)Sesi["Tanggal Mulai"];
Masalah muncul dari antarmuka fleksibel yang disediakan oleh HttpSessionState: kuncinya hanyalah string dan nilainya tidak diketik dengan kuat.
Menggunakan String Literal sebagai Kunci
Jika literal string digunakan sebagai kunci, nilai string dari kunci tersebut tidak diperiksa oleh kompiler. Sangat mudah untuk membuat nilai sesi baru hanya dengan kesalahan pengetikan sederhana.
Sesi["diterima"] = 27;...
Sesi["diterima"] = 32;
Dalam kode di atas, dua nilai sesi terpisah telah disimpan.
Kebanyakan bug seperti ini akan diidentifikasi dengan pengujian unit – tetapi tidak selalu. Mungkin tidak selalu terlihat bahwa nilainya tidak berubah seperti yang diharapkan.
Kita dapat menghindari bug semacam ini dengan menggunakan konstanta:
private const string yang diterima = "diterima";...
Sesi[diterima] = 27;...
Sesi[diterima] = 32;
Tidak Ada Pemeriksaan Jenis
Tidak ada pemeriksaan tipe terhadap nilai yang disimpan dalam variabel sesi. Kompiler tidak dapat memeriksa kebenaran dari apa yang disimpan.
Perhatikan kode berikut:
Session["maxValue"] = 27;...
int maxValue = (int)Sesi["maxValue"];
Di tempat lain kode berikut digunakan untuk memperbarui nilai.
Sesi["maxValue"] = 56,7;
Jika kode untuk membaca variabel sesi "maxValue" ke dalam variabel int maxValue dijalankan lagi maka akan ada InvalidCastException yang dilempar.
Kebanyakan bug seperti ini akan diidentifikasi dengan pengujian unit – tetapi tidak selalu.
Menggunakan Kembali Kunci Secara Tidak Sengaja
Bahkan ketika kita mendefinisikan konstanta pada setiap halaman untuk kunci sesi, ada kemungkinan untuk secara tidak sengaja menggunakan kunci yang sama di seluruh halaman. Perhatikan contoh berikut:
Kode pada satu halaman:
private const string edit = "edit";...
Sesi[edit] = benar;
Kode pada halaman kedua, ditampilkan setelah halaman pertama:
private const string edit = "edit";...
if ((bool)Sesi[edit])
{
...
}
Kode pada halaman ketiga, tidak terkait:
private const string edit = "edit";...
Sesi[edit] = salah;
Jika halaman ketiga ditampilkan karena alasan tertentu sebelum halaman kedua ditampilkan, nilainya mungkin tidak sesuai dengan yang diharapkan. Kode mungkin akan tetap berjalan, tetapi hasilnya salah.
Biasanya bug ini TIDAK akan ditemukan dalam pengujian. Hanya ketika pengguna melakukan kombinasi navigasi halaman tertentu (atau membuka jendela browser baru) barulah bug tersebut muncul.
Yang terburuk, tidak ada yang menyadari bahwa bug telah muncul, kita mungkin hanya mengubah data ke nilai yang tidak diinginkan.
Menggunakan kembali Kunci secara tidak sengaja - lagi
Pada contoh di atas, tipe data yang sama disimpan dalam variabel sesi. Karena tidak ada pengecekan tipe terhadap apa yang disimpan, masalah tipe data yang tidak kompatibel juga dapat terjadi.
Kode pada satu halaman:
Session["FollowUp"] = "true";
Kode pada halaman kedua:
Session["FollowUp"] = 1;
Kode pada halaman ketiga:
Session["FollowUp"] = true;
Saat bug muncul, InvalidCastException akan muncul.
Biasanya bug ini TIDAK akan ditemukan dalam pengujian. Hanya ketika pengguna melakukan kombinasi navigasi halaman tertentu (atau membuka jendela browser baru) barulah bug tersebut muncul.
Apa yang Dapat Kita Lakukan?
Perbaikan Cepat Pertama
Hal pertama dan paling sederhana yang bisa kita lakukan adalah memastikan kita tidak pernah menggunakan literal string untuk kunci sesi. Selalu gunakan konstanta dan hindari kesalahan pengetikan sederhana.
batas string const pribadi = "batas";...
Sesi[batas] = 27;...
Sesi[batas] = 32;
Namun, ketika konstanta didefinisikan secara lokal (misalnya pada tingkat halaman) kita mungkin masih menggunakan kembali kunci yang sama secara tidak sengaja.
Perbaikan Cepat yang Lebih Baik
Daripada menentukan konstanta di setiap halaman, kelompokkan semua konstanta kunci sesi ke dalam satu lokasi dan berikan dokumentasi yang akan muncul di Intellisense. Dokumentasi harus dengan jelas menunjukkan kegunaan variabel sesi. Misalnya, tentukan kelas hanya untuk kunci sesi:
SessionKeys kelas statis publik
{
///
/// Maksimum...
///
public const string Limit = "batas";
}
...
Sesi[SessionKeys.Limit] = 27;
Saat Anda membutuhkan variabel sesi baru, jika Anda memilih nama yang sudah digunakan, Anda akan mengetahuinya saat Anda menambahkan konstanta ke kelas SessionKeys. Anda dapat melihat cara penggunaannya saat ini dan dapat menentukan apakah Anda harus menggunakan kunci lain.
Namun, kami masih belum memastikan konsistensi tipe data.
Cara yang Jauh Lebih Baik - Menggunakan Fasad
Hanya akses HttpSessionState dari dalam satu kelas statis di aplikasi Anda - fasad. Tidak boleh ada akses langsung ke properti Session dari dalam kode pada halaman atau kontrol, dan tidak boleh ada akses langsung ke HttpContext.Current.Session selain dari dalam fasad
. Semua variabel sesi akan diekspos sebagai properti kelas fasad.
Ini memiliki keuntungan yang sama dengan menggunakan satu kelas untuk semua kunci sesi, ditambah keuntungan berikut:
Pengetikan yang kuat tentang apa yang dimasukkan ke dalam variabel sesi.
Tidak perlu memasukkan kode di mana variabel sesi digunakan.
Semua manfaat penyetel properti untuk memvalidasi apa yang dimasukkan ke dalam variabel sesi (lebih dari sekadar mengetik).
Semua manfaat pengambil properti saat mengakses variabel sesi. Misalnya, menginisialisasi suatu variabel saat pertama kali diakses.
Contoh Kelas Fasad Sesi
Berikut adalah contoh kelas untuk mengimplementasikan fasad Sesi untuk aplikasi bernama MyApplication.
Runtuh
///
/// MyApplicationSession menyediakan fasad ke objek Sesi ASP.NET.
/// Semua akses ke variabel Session harus melalui kelas ini.
///
kelas statis publik MyApplicationSession
{
# Konstanta Pribadi wilayah
//------------------------------------------------ ---------------------
private const string userAuthorisation = "UserAuthorisation";
string const pribadi teamManagementState = "TeamManagementState";
string const pribadi startDate = "Tanggal Mulai";
string const pribadi endDate = "Tanggal Akhir";
//------------------------------------------------ ---------------------
# endregion
# wilayah Properti Publik
//------------------------------------------------ ---------------------
///
/// Nama Pengguna adalah nama domain dan nama pengguna pengguna saat ini.
///
Nama Pengguna string statis publik
{
dapatkan { return HttpContext.Current.User.Identity.Name; }
}
///
/// UserAuthorisation berisi informasi otorisasi untuk
/// pengguna saat ini.
///
Otorisasi Pengguna statis publik. Otorisasi Pengguna
{
mendapatkan
{
Otorisasi Pengguna userAuth
= (Otorisasi Pengguna)HttpContext.Saat ini.Sesi[Otorisasi Pengguna];
// Periksa apakah UserAuthorisation telah kedaluwarsa
jika (
penggunaAuth == nol ||
(penggunaAuth.Created.AddMinutes(
Aplikasi Saya.Pengaturan.Caching.AuthorisationCache.CacheExpiryMinutes))
< TanggalWaktu.Sekarang
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Nama Pengguna);
Otorisasi Pengguna = userAuth;
}
kembalikan userAuth;
}
kumpulan pribadi
{
HttpContext.Current.Session[userAuthorisation] = nilai;
}
}
///
/// TeamManagementState digunakan untuk menyimpan keadaan saat ini
/// halaman TeamManagement.aspx.
///
TeamManagementState statis publik TeamManagementState
{
mendapatkan
{
return (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
mengatur
{
HttpContext.Current.Session[teamManagementState] = nilai;
}
}
///
/// Tanggal Mulai adalah tanggal paling awal yang digunakan untuk memfilter rekaman.
///
Tanggal Mulai Statis publik
{
mendapatkan
{
if (HttpContext.Saat ini.Sesi[tanggal mulai] == null)
kembali DateTime.MinValue;
kalau tidak
return (DateTime)HttpContext.Current.Session[startDate];
}
mengatur
{
HttpContext.Current.Session[tanggal mulai] = nilai;
}
}
///
/// Tanggal Akhir adalah tanggal terakhir yang digunakan untuk memfilter catatan.
///
Tanggal Akhir Waktu statis publik
{
mendapatkan
{
if (HttpContext.Saat ini.Sesi[tanggal akhir] == null)
return DateTime.MaxValue;
kalau tidak
return (DateTime)HttpContext.Current.Session[endDate];
}
mengatur
{
HttpContext.Current.Session[endDate] = nilai;
}
}
//------------------------------------------------ ---------------------
# wilayah akhir
}
Kelas tersebut mendemonstrasikan penggunaan pengambil properti yang dapat memberikan nilai default jika suatu nilai belum disimpan secara eksplisit. Misalnya, properti StartDate menyediakan DateTime.MinValue sebagai default.
Pengambil properti untuk properti UserAuthorisation menyediakan cache sederhana dari instance kelas UserAuthorisation, memastikan bahwa instance dalam variabel sesi selalu diperbarui. Properti ini juga menunjukkan penggunaan penyetel privat, sehingga nilai dalam variabel sesi hanya dapat disetel di bawah kendali kelas fasad.
Properti Nama Pengguna menunjukkan nilai yang mungkin pernah disimpan sebagai variabel sesi tetapi tidak lagi disimpan dengan cara ini.
Kode berikut menunjukkan bagaimana variabel sesi dapat diakses melalui fasad. Perhatikan bahwa tidak perlu melakukan casting apa pun pada kode ini.
// Menyimpan variabel sesi
MyApplicationSession.StartDate = DateTime.Hari ini.AddDays(-1);
// Membaca variabel sesi
DateTime startDate = MyApplicationSession.StartDate;
Manfaat Tambahan
Manfaat tambahan dari pola desain fasad adalah menyembunyikan implementasi internal dari aplikasi lainnya. Mungkin di masa depan Anda dapat memutuskan untuk menggunakan mekanisme lain dalam mengimplementasikan status sesi, selain kelas ASP.NET HttpSessionState bawaan. Anda hanya perlu mengubah bagian dalam fasad - Anda tidak perlu mengubah apa pun di aplikasi lainnya.
Ringkasan
Penggunaan fasad untuk HttpSessionState memberikan cara yang jauh lebih kuat untuk mengakses variabel sesi. Ini adalah teknik yang sangat sederhana untuk diterapkan, tetapi dengan manfaat yang besar.