您好,登錄后才能下訂單哦!
問題
ASP.NET Web API 構建 Web 應用程序時,要求使用 Session 在服務器存儲一些用戶特定的信息
解決方案
ASP.NET Web API 不支持 Session,因為 API 根本不依賴于System.Web。他想試圖擺脫偽造 Session,非 HTTP 這樣的概念。
然而,如果我們 在 ASP.NET 運行時中運行 ASP.NET Web API,還想啟用 Session。我們可以通過兩種方式來做:
全局:應用于整個 API
局部:應用于指定路由
啟用全局方式,我們需要在 Global.asax 中 通過 SesssionStateBehavior.Required顯示的設置啟用 Session 行為。
protected void Application_PostAuthorizeRequest() { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); }
啟用指定路由(局部方式),我們可以通過過使用路由處理器,讓路由處理器繼承自 IRequiresSessionState。然后,我們可以在指定的路由上附加處理器,這個就在請求指定路由的時候啟用了 Session。
工作原理
默認的 ASP.NET Web API 模板,會幫我們在 WebApiConfig 靜態類中使用 HttpConfiguration 定義默認路由,因為,框架附帶的擴展方法是支持我們使用 System.Web.RouteCollection,在定義 MVC 路由的地方定義 Web API 路由。
雖然 MapHttpRoute 的多個重載方法經常被使用,但是這些重載方法都是 void (無返回值)方法,實際上,方法還是返回了一個最新聲明路由的實例,只是方法的調用結果一般都是被拋棄掉的。在使用 Syste.Web.RouteCollection 直接定義路由的情況下,返回值是 System.Web.Route 的對象,我們可以將其賦值給 IrouteHandler。
當運行在 ASP.NET 的時候,ASP.NET Web API 框架使用同樣的機制來確保 Api 請求可以準確到達,他會賦值 HttpControllerRouteHandler 給每一個 Web API 路由,HttpControllerRouteHandler 是 GetHttpHandler 方法返回的一個HttpControllerHandler 實例,這是 ASP.NET Web API 管道的入口點。HttpControllerHandler (WEB API 的核心)雖然很復雜,但是究其原理也就是一個傳統的 IHttpAsyncHandler(舊的 IHttpHandler 的一個異步的版本)。
我們可以通過實現IRequiresSessionState 的接口,來強制在 IHttpHandler 中使用 Session。ASP.NET 將會顯示的為每一個實現了這個接口路由啟用 Session。
另外要在全局范圍內調用 HttpContext.Current.SetSessionStateBehavior 方法和傳遞 SessionStateBehavior,需要為當前的 HttpContext 顯示的啟用 Session。SetSessionStateBehavior方法必須在 AcquireRequestState 事件之前調用。
代碼演示
繼承兩個類:
HttpControllerHandler
HttpControllerRouteHandler
我們將創建兩個自定義類
SessionHttpControllerHandler:實現 IRequiresSessionState
SessionHttpControllerRouteHandler:只是代替默認類型,來充當返回 SessionHttpControllerHandler 的工廠
如清單 1-26 所示。
清單 1-26. 定制 HttpControllerHandler和 HttpControllerRouteHandler
public class SessionControllerHandler : HttpControllerHandler, IRequiresSessionState { public SessionControllerHandler(RouteData routeData) : base(routeData) { } } public class SessionHttpControllerRouteHandler : HttpControllerRouteHandler { protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { return new SessionControllerHandler(requestContext.RouteData); } }
現在我們需要將我們的注意力從 WebApiConfig 類轉移到 RouteConfig 類,因為我們需要執行 RouteCollection。接下來,我們應該在創建路由的時候,將 SessionHttpControllerRouteHandler 賦值給 RouteHandler。如清單 1-27 所示。
清單 1-27. 在System.Web.RouteCollection 中注冊 Web API 路由
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //Web API routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ).RouteHandler = new SessionHttpControllerRouteHandler(); //MVC routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
如果想激進一點,還是有其他的方式來執行這個功能的。不再需要跑到 RouteConfig 中注冊 Api 的路由,而是需要使用 WebApiConfig 中的 HttpConfiguration 做一些必要的處理,可以同樣達對所有 Web API 路由啟用 Session。
當我們通過 Web API 的配置注冊路由的時候,路由是被注冊在 RouteTable 中,同時,使用一個單利 HttpControllerRouteHandler.Instance 處理器來處理路由。這樣,我們可以讓 ASP.NET 所有調用轉到Web API 路由,進入到 Web API 的管道。這里說到的單例其實就是一個 Lazy<HttpControllerRputeHandler>。我們可以在應用程序啟動的地方使用自己的類似 SessionHttpControllerRouteHandler 的類實例,然后繼續注冊路由到 HttpConfiguration,同時,這樣可以確保每一個 Web API 路由都使用了SessionHttpControllerRouteHandler ,也就是說所有的路由都可以訪問Session。這個簡單的代碼如清單 1-28 所示。
清單 1-28. 配置 Session
public static class WebApiConfig { public static void Register(HttpConfiguration config) { var httpControllerRouteHandler = typeof(HttpControllerRouteHandler).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic); if (httpControllerRouteHandler != null) { httpControllerRouteHandler.SetValue(null, new Lazy<HttpControllerRouteHandler>(() => new SessionHttpControllerRouteHandler(), true)); } config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.MapHttpAttributeRoutes(); } }
現在,我們需要證明這個起作用了,需要做一個簡單模擬擲骰子的 ApiController 例子。首先,生成一個 1 到 6 之間的隨機數,將 Session 中上一次的值使用當前投擲的值賦值。
清單 1-29. 使用 Session 的 ApiController 簡單例子
public class DiceResult { public int NewValue { get; set; } public int LastValue { get; set; } } public class DiceController : ApiController { public DiceResult Get() { var newValue = new Random().Next(1, 7); object context; if (Request.Properties.TryGetValue("MS_HttpContext", out context)) { var httpContext = context as HttpContextBase; if (httpContext != null && httpContext.Session != null) { var lastValue = httpContext.Session["LastValue"] as int?; httpContext.Session["LastValue"] = newValue; return new DiceResult { NewValue = newValue, LastValue = lastValue ?? 0 }; } } return new DiceResult { NewValue = newValue }; } }
值得注意的是,我們剛剛獲取的 HttpContext 是從 HttpRequestMessage 屬性字典中通過“MS_HttpContextkey”獲取的。這個比直接從 System.HttpContext.Current中獲取更具可測性。
博客園:http://www.cnblogs.com/shuizhucode/
51 CTO:http://shuizhucode.blog.51cto.com/
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。