แอปพลิเคชัน ASP.NET ทุกตัวจำเป็นต้องติดตามข้อมูลสำหรับเซสชันของผู้ใช้ ASP.NET ให้คลาส HttpSessionState เพื่อจัดเก็บค่าสถานะเซสชัน อินสแตนซ์ของคลาส HttpSessionState สำหรับแต่ละคำขอ HTTP สามารถเข้าถึงได้ทั่วทั้งแอปพลิเคชันของคุณโดยใช้คุณสมบัติ HttpContext.Current.Session แบบคงที่ การเข้าถึงอินสแตนซ์เดียวกันนั้นทำได้ง่ายขึ้นในทุกเพจและ UserControl โดยใช้คุณสมบัติเซสชันของเพจหรือ UserControl
คลาส HttpSessionState จัดเตรียมคอลเลกชันของคู่คีย์/ค่า โดยที่คีย์เป็นประเภท String และค่าเป็นประเภท Object ซึ่งหมายความว่าเซสชันมีความยืดหยุ่นอย่างมาก และคุณสามารถจัดเก็บข้อมูลประเภทใดก็ได้ในเซสชัน
แต่ (มักจะมี แต่) ความยืดหยุ่นนี้ไม่ได้มาโดยไม่มีค่าใช้จ่าย ค่าใช้จ่ายคือความง่ายในการนำจุดบกพร่องมาสู่แอปพลิเคชันของคุณ จุดบกพร่องหลายอย่างที่สามารถนำมาใช้ได้จะไม่พบโดยการทดสอบหน่วย และอาจไม่พบโดยการทดสอบแบบมีโครงสร้างใดๆ จุดบกพร่องเหล่านี้มักจะปรากฏเมื่อมีการปรับใช้แอปพลิเคชันกับสภาพแวดล้อมการใช้งานจริงเท่านั้น เมื่อพวกมันปรากฏบนพื้นผิว มักจะเป็นเรื่องยากมาก หรือเป็นไปไม่ได้เลยที่จะพิจารณาว่ามันเกิดขึ้นได้อย่างไรและสามารถแพร่พันธุ์แมลงได้ ซึ่งหมายความว่ามีราคาแพงมากในการซ่อมแซม
บทความนี้นำเสนอกลยุทธ์เพื่อช่วยป้องกันจุดบกพร่องประเภทนี้ โดยจะใช้รูปแบบการออกแบบที่เรียกว่า Facade โดยจะรวมอินเทอร์เฟซฟรีที่คลาส HttpSessionState มอบให้ (ซึ่งสามารถตอบสนองความต้องการของแอปพลิเคชันใดๆ ก็ได้) ด้วยอินเทอร์เฟซที่ได้รับการออกแบบและควบคุมอย่างดีซึ่งสร้างขึ้นโดยมีจุดประสงค์สำหรับแอปพลิเคชันเฉพาะ หากคุณไม่คุ้นเคยกับรูปแบบการออกแบบหรือลวดลาย Facade การค้นหา "รูปแบบการออกแบบ Facade" ในอินเทอร์เน็ตอย่างรวดเร็วจะช่วยให้คุณมีข้อมูลพื้นหลังมากมาย อย่างไรก็ตาม คุณไม่จำเป็นต้องเข้าใจรูปแบบการออกแบบเพื่อที่จะเข้าใจบทความนี้
โค้ดตัวอย่างที่แสดงในบทความนี้เขียนด้วยภาษา C# แต่แนวคิดนี้สามารถใช้ได้กับภาษา .NET ใดๆ
ปัญหาคืออะไร?
ในบทความนี้ในส่วนนี้ ฉันจะอธิบายปัญหาเกี่ยวกับการเข้าถึงคลาส HttpSessionState โดยตรง โดยไม่มีส่วนหน้า ฉันจะอธิบายประเภทของข้อบกพร่องที่สามารถนำมาใช้ได้
ข้อมูลต่อไปนี้แสดงโค้ดทั่วไปที่เขียนขึ้นเพื่อเข้าถึงตัวแปรสถานะเซสชัน
// บันทึกตัวแปรเซสชัน
เซสชัน ["สตริงบางส่วน"] = anyOldObject;
// อ่านตัวแปรเซสชัน
DateTime startDate = (DateTime) เซสชัน ["StartDate"];
ปัญหาเกิดขึ้นจากอินเทอร์เฟซที่ยืดหยุ่นซึ่งจัดทำโดย HttpSessionState: คีย์เป็นเพียงสตริงและค่าไม่ได้พิมพ์อย่างรุนแรง
การใช้ตัวอักษรสตริงเป็นคีย์
หากใช้ตัวอักษรสตริงเป็นคีย์ คอมไพเลอร์จะไม่ตรวจสอบค่าสตริงของคีย์ เป็นเรื่องง่ายที่จะสร้างค่าเซสชันใหม่โดยการพิมพ์ผิดพลาด
เซสชัน["ได้รับ"] = 27;...
เซสชัน ["ได้รับ"] = 32;
ในโค้ดด้านบน มีการบันทึกค่าเซสชันที่แยกกันสองค่า
จุดบกพร่องส่วนใหญ่เช่นนี้จะถูกระบุโดยการทดสอบหน่วย แต่ก็ไม่เสมอไป อาจไม่ชัดเจนเสมอไปว่าค่าไม่เปลี่ยนแปลงตามที่คาดไว้
เราสามารถหลีกเลี่ยงข้อผิดพลาดประเภทนี้ได้โดยใช้ค่าคงที่:
private const string gets = "received";...
เซสชัน[ได้รับ] = 27;...
เซสชัน[ได้รับ] = 32;
ไม่มีการตรวจสอบประเภท
ไม่มีการตรวจสอบประเภทของค่าที่จัดเก็บไว้ในตัวแปรเซสชัน คอมไพเลอร์ไม่สามารถตรวจสอบความถูกต้องของสิ่งที่จัดเก็บได้
พิจารณารหัสต่อไปนี้:
Session["maxValue"] = 27;...
int maxValue = (int) เซสชัน ["maxValue"];
ในส่วนอื่นรหัสต่อไปนี้จะใช้ในการอัพเดตค่า
เซสชัน["maxValue"] = 56.7;
หากโค้ดสำหรับอ่านตัวแปรเซสชัน "maxValue" ลงในตัวแปร maxValue int ถูกดำเนินการอีกครั้ง จะมีการโยน InvalidCastException ออกไป
จุดบกพร่องส่วนใหญ่เช่นนี้จะถูกระบุโดยการทดสอบหน่วย แต่ก็ไม่เสมอไป
การใช้คีย์ซ้ำโดยไม่ได้ตั้งใจ
แม้ว่าเราจะกำหนดค่าคงที่ในแต่ละหน้าสำหรับคีย์เซสชัน ก็เป็นไปได้ที่จะใช้คีย์เดียวกันข้ามหน้าโดยไม่ได้ตั้งใจ พิจารณาตัวอย่างต่อไปนี้:
โค้ดในหน้าเดียว:
private const string edit = "edit";...
เซสชัน[แก้ไข] = จริง;
โค้ดในหน้าที่สอง แสดงหลังหน้าแรก:
private const string edit = "edit";...
ถ้า ((บูล)เซสชัน[แก้ไข])
-
-
-
รหัสในหน้าที่สามที่ไม่เกี่ยวข้อง:
private const string edit = "edit";...
เซสชั่น[แก้ไข] = เท็จ;
หากหน้าที่สามแสดงขึ้นด้วยเหตุผลบางประการก่อนที่จะแสดงหน้าที่สอง ค่าอาจไม่เป็นไปตามที่คาดไว้ โค้ดอาจจะยังคงทำงานอยู่ แต่ผลลัพธ์จะผิดพลาด
โดยปกติแล้วข้อผิดพลาดนี้จะไม่ถูกหยิบยกขึ้นมาในการทดสอบ เฉพาะเมื่อผู้ใช้ใช้การนำทางเพจร่วมกัน (หรือเปิดหน้าต่างเบราว์เซอร์ใหม่) เท่านั้นที่ข้อบกพร่องจะแสดงออกมา
ที่แย่ที่สุด ไม่มีใครรู้ว่ามีข้อบกพร่องเกิดขึ้น เราอาจลงเอยด้วยการแก้ไขข้อมูลเป็นค่าที่ไม่ได้ตั้งใจ
การใช้คีย์ซ้ำโดยไม่ได้ตั้งใจ - อีกครั้ง
ในตัวอย่างข้างต้น ข้อมูลประเภทเดียวกันถูกจัดเก็บไว้ในตัวแปรเซสชัน เนื่องจากไม่มีการตรวจสอบประเภทของสิ่งที่ถูกจัดเก็บ ปัญหาของประเภทข้อมูลที่เข้ากันไม่ได้ก็อาจเกิดขึ้นได้เช่นกัน
โค้ดในหน้าเดียว:
Session["FollowUp"] = "true";
รหัสในหน้าที่สอง:
เซสชัน ["ติดตามผล"] = 1;
รหัสในหน้าที่สาม:
Session["FollowUp"] = true;
เมื่อข้อผิดพลาดปรากฏขึ้น จะมีการโยน InvalidCastException ออกไป
โดยปกติแล้วข้อผิดพลาดนี้จะไม่ถูกหยิบยกขึ้นมาในการทดสอบ เฉพาะเมื่อผู้ใช้ใช้การนำทางเพจร่วมกัน (หรือเปิดหน้าต่างเบราว์เซอร์ใหม่) เท่านั้นที่ข้อบกพร่องจะแสดงออกมา
เราทำอะไรได้บ้าง?
การแก้ไขด่วนครั้งแรก
สิ่งแรกและง่ายที่สุดที่เราสามารถทำได้คือต้องแน่ใจว่าเราจะไม่ใช้ตัวอักษรสตริงสำหรับคีย์เซสชัน ใช้ค่าคงที่เสมอ ดังนั้นหลีกเลี่ยงการพิมพ์ผิดพลาดง่ายๆ
ขีดจำกัดสตริง const ส่วนตัว = "ขีดจำกัด";...
เซสชัน[จำกัด] = 27;...
เซสชั่น[จำกัด] = 32;
อย่างไรก็ตาม เมื่อมีการกำหนดค่าคงที่ในเครื่อง (เช่น ในระดับเพจ) เราอาจยังคงใช้คีย์เดิมซ้ำโดยไม่ได้ตั้งใจ
การแก้ไขด่วนที่ดีกว่า
แทนที่จะกำหนดค่าคงที่ในแต่ละเพจ ให้จัดกลุ่มค่าคงที่คีย์เซสชันทั้งหมดไว้ในตำแหน่งเดียว และจัดเตรียมเอกสารที่จะปรากฏใน Intellisense เอกสารประกอบควรระบุอย่างชัดเจนว่าตัวแปรเซสชันใช้ทำอะไร ตัวอย่างเช่น กำหนดคลาสเฉพาะสำหรับคีย์เซสชัน:
คลาสคงที่สาธารณะ SessionKeys
-
/// <สรุป>
/// สูงสุด ...
/// สรุป>
สาธารณะ const สตริงจำกัด = "จำกัด";
}
...
เซสชัน [SessionKeys.Limit] = 27;
เมื่อคุณต้องการตัวแปรเซสชันใหม่ หากคุณเลือกชื่อที่มีการใช้งานแล้ว คุณจะทราบสิ่งนี้เมื่อคุณเพิ่มค่าคงที่ให้กับคลาส SessionKeys คุณสามารถดูวิธีการใช้งานในปัจจุบันและระบุได้ว่าคุณควรใช้คีย์อื่นหรือไม่
อย่างไรก็ตาม เรายังคงไม่รับประกันความสอดคล้องของประเภทข้อมูล
วิธีที่ดีกว่ามาก - การใช้ซุ้ม
เข้าถึง HttpSessionState จากภายในคลาสคงที่เดียวในแอปพลิเคชันของคุณเท่านั้น - ด้านหน้า จะต้องไม่มีการเข้าถึงโดยตรงไปยังคุณสมบัติเซสชันจากภายในโค้ดบนเพจหรือส่วนควบคุม และไม่มีการเข้าถึงโดยตรงไปยัง HttpContext.Current.Session นอกเหนือจากภายในส่วนหน้า
ตัวแปรเซสชันทั้งหมดจะถูกเปิดเผยเป็นคุณสมบัติของคลาสส่วนหน้า
สิ่งนี้มีข้อดีเช่นเดียวกับการใช้คลาสเดียวสำหรับคีย์เซสชันทั้งหมด บวกกับข้อดีดังต่อไปนี้:
การพิมพ์สิ่งที่ใส่เข้าไปในตัวแปรเซสชันอย่างรัดกุม
ไม่จำเป็นต้องส่งโค้ดที่ใช้ตัวแปรเซสชัน
ประโยชน์ทั้งหมดของตัวตั้งค่าคุณสมบัติในการตรวจสอบสิ่งที่ใส่เข้าไปในตัวแปรเซสชัน (มากกว่าแค่การพิมพ์)
ประโยชน์ทั้งหมดของตัวรับคุณสมบัติเมื่อเข้าถึงตัวแปรเซสชัน ตัวอย่างเช่น การเริ่มต้นตัวแปรในครั้งแรกที่มีการเข้าถึง
ตัวอย่างคลาส Facade ของเซสชัน
นี่คือคลาสตัวอย่างเพื่อใช้ส่วนหน้าของเซสชันสำหรับแอปพลิเคชันที่เรียกว่า MyApplication
ทรุด
/// <สรุป>
/// MyApplicationSession ให้ส่วนหน้าแก่วัตถุเซสชัน ASP.NET
/// การเข้าถึงตัวแปรเซสชันทั้งหมดจะต้องผ่านคลาสนี้
/// สรุป>
MyApplicationSession คลาสคงที่สาธารณะ
-
# ค่าคงที่ส่วนตัวของภูมิภาค
-
สตริง const ส่วนตัว userAuthorisation = "UserAuthorisation";
สตริง const ส่วนตัว teamManagementState = "TeamManagementState";
สตริง const ส่วนตัว startDate = "StartDate";
สตริง const ส่วนตัว endDate = "EndDate";
-
# endregion
# ทรัพย์สินสาธารณะของภูมิภาค
-
/// <สรุป>
/// Username คือ ชื่อโดเมนและชื่อผู้ใช้ของผู้ใช้ปัจจุบัน
/// สรุป>
ชื่อผู้ใช้สตริงคงที่สาธารณะ
-
รับ { กลับ HttpContext.Current.User.Identity.Name; -
-
/// <สรุป>
/// UserAuthorisation มีข้อมูลการอนุญาตสำหรับ
/// ผู้ใช้ปัจจุบัน
/// สรุป>
UserAuthorisation UserAuthorisation แบบคงที่สาธารณะ
-
รับ
-
การอนุญาตผู้ใช้ userAuth
= (การอนุญาตผู้ใช้)HttpContext.Current.Session[การอนุญาตผู้ใช้];
// ตรวจสอบว่า UserAuthorisation หมดอายุแล้วหรือไม่
ถ้า (
userAuth == null ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< วันที่และเวลาตอนนี้
-
-
userAuth = UserAuthorisation.GetUserAuthorisation (ชื่อผู้ใช้);
การอนุญาตผู้ใช้ = userAuth;
}
ส่งคืน userAuth;
}
ชุดส่วนตัว
-
HttpContext.Current.Session[userAuthorisation] = ค่า;
-
}
/// <สรุป>
/// TeamManagementState ใช้เพื่อจัดเก็บสถานะปัจจุบันของ
/// หน้า TeamManagement.aspx
/// สรุป>
TeamManagementState TeamManagementState แบบคงที่สาธารณะ
-
รับ
-
กลับ (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
ชุด
-
HttpContext.Current.Session[teamManagementState] = ค่า;
-
}
/// <สรุป>
/// StartDate คือวันที่แรกสุดที่ใช้ในการกรองบันทึก
/// สรุป>
DateTime StartDate สาธารณะแบบคงที่
-
รับ
-
ถ้า (HttpContext.Current.Session[startDate] == null)
กลับ DateTime.MinValue;
อื่น
กลับ (DateTime) HttpContext.Current.Session [startDate];
}
ชุด
-
HttpContext.Current.Session[startDate] = ค่า;
-
}
/// <สรุป>
/// EndDate คือวันที่ล่าสุดที่ใช้ในการกรองบันทึก
/// สรุป>
DateTime EndDate แบบคงที่สาธารณะ
-
รับ
-
ถ้า (HttpContext.Current.Session[endDate] == null)
กลับ DateTime.MaxValue;
อื่น
กลับ (DateTime) HttpContext.Current.Session [endDate];
}
ชุด
-
HttpContext.Current.Session[endDate] = ค่า;
-
-
-
#สิ้นสุดภูมิภาค
-
คลาสสาธิตการใช้ตัวรับคุณสมบัติที่สามารถจัดเตรียมค่าเริ่มต้นได้ หากค่าไม่ได้ถูกจัดเก็บไว้อย่างชัดเจน ตัวอย่างเช่น คุณสมบัติ StartDate จัดเตรียม DateTime.MinValue เป็นค่าเริ่มต้น
ตัวรับคุณสมบัติสำหรับคุณสมบัติ UserAuthorisation จัดเตรียมแคชอย่างง่ายของอินสแตนซ์คลาส UserAuthorisation เพื่อให้แน่ใจว่าอินสแตนซ์ในตัวแปรเซสชันจะได้รับการอัปเดตอยู่เสมอ คุณสมบัตินี้ยังแสดงการใช้ตัวตั้งค่าส่วนตัว เพื่อให้สามารถตั้งค่าในตัวแปรเซสชันได้ภายใต้การควบคุมของคลาสส่วนหน้าเท่านั้น
คุณสมบัติ Username แสดงให้เห็นค่าที่ครั้งหนึ่งเคยถูกจัดเก็บเป็นตัวแปรเซสชัน แต่ไม่ได้จัดเก็บในลักษณะนี้อีกต่อไป
รหัสต่อไปนี้แสดงวิธีการเข้าถึงตัวแปรเซสชันผ่านส่วนหน้า โปรดทราบว่าไม่จำเป็นต้องทำการแคสต์ใดๆ ในโค้ดนี้
// บันทึกตัวแปรเซสชัน
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// อ่านตัวแปรเซสชัน
DateTime startDate = MyApplicationSession.StartDate;
สิทธิประโยชน์เพิ่มเติม
ประโยชน์เพิ่มเติมของรูปแบบการออกแบบส่วนหน้าอาคารคือการซ่อนการใช้งานภายในจากส่วนที่เหลือของแอปพลิเคชัน บางทีในอนาคตคุณอาจตัดสินใจใช้กลไกอื่นในการนำ session-state ไปใช้ นอกเหนือจากคลาส ASP.NET HttpSessionState ในตัว คุณจะต้องเปลี่ยนการตกแต่งภายในของส่วนหน้าเท่านั้น - คุณไม่จำเป็นต้องเปลี่ยนแปลงสิ่งอื่นใดในส่วนที่เหลือของแอปพลิเคชัน
สรุป
การใช้ส่วนหน้าสำหรับ HttpSessionState มอบวิธีที่มีประสิทธิภาพมากขึ้นในการเข้าถึงตัวแปรเซสชัน นี่เป็นเทคนิคที่ง่ายมากในการนำไปใช้ แต่ให้ประโยชน์อย่างมาก