ASP.NET MVC4中引入的Web API可以說是進行REST軟件開發的利器(個人意見),但是最近在web form中混入web api時,發現一個問題:由於以前的web form項目中,使用到了session(包括那些復雜的底層邏輯),所以為了最小改動,必須保證web api能支持session。而web api默認情況下,是不支持session的。
問題重現
重現這個不支持session的問題,其實很簡單。只需要新建一個web api項目,然后修改ValuesController的Get方法為:
public string Get() { return HttpContext.Current.Session == null ? "Session is null" : string.Format("SessionID is '{0}'", HttpContext.Current.Session.SessionID); }
在運行后,發現頁面輸出為:"Session is null"。
解決方案
經過一番努力,發現Route中有一個IRouteHandler類型的屬性RouteHandler,而默認情況下,這個RouteHandler是System.Web.Http.WebHost.HttpControllerRouteHandler,而HttpControllerRouteHandler中的GetHttpHandler會返回一個IHttpHandler的對象,來對web api的請求進行預處理。對於IHttpHandler其實大家應該不陌生,這個正是asp.net web form中,經常使用到的iis管道處理的一個環節。那么為了讓IHttpHandler支持session,我們知道只需要實現一個標記接口IRequiresSessionState。而默認的System.Web.Http.WebHost.HttpControllerHandler只實現了IHttpHandler並沒有實現IRequiresSessionState。所以問題就變成了如何讓IRouteHandler的GetHttpHandler方法返回一個既實現了IHttpHandler,又實現了IRequiresSessionState對象。所以有了下面的類:
public class WebApiSessionControllerHandler : HttpControllerHandler, IRequiresSessionState { public WebApiSessionControllerHandler(RouteData routeData) : base(routeData) { } }
為了使用WebApiSessionControllerHandler,還需要實現一個IRouteHandler自定義類:
public class WebApiSessionRouteHandler : HttpControllerRouteHandler { protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { return new WebApiSessionControllerHandler(requestContext.RouteData); } }
接下來,就是要讓WebApiSessionRouteHandler真正起作用的時候了,只需要在你RegisterRoutes時,把MapHttpRoute修改為:
var route = routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); route.RouteHandler = new WebApiSessionRouteHandler();
現在再來看下/api/values的輸出:"SessionID is 'me4exrjgv4lecvixa0ab2z1g'",發現session不在為null。
優化方案
為了在map route操作時,能夠有一個統一的寫法,可以增加一個擴展方法:
public static class HttpRouteExtensions { public static Route MapHttpRoute(this RouteCollection routes, string name, string routeTemplate, object defaults, IRouteHandler routeHandler) { object constraints = null; HttpMessageHandler handler = null; var route = routes.MapHttpRoute(name, routeTemplate, defaults, constraints, handler); if (routeHandler != null) { route.RouteHandler = routeHandler; } return route; } }
這樣,MapHttpRoute就可以改為:
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, routeHandler: new WebApiSessionRouteHandler() );
結論
對HttpControllerHandler使用,不僅僅局限於是web api支持session。前面已經提到我們可以對web api的請求進行預處理,也可以創建自定義類,重現HttpControllerHandler中的:
1、BeginProcessRequest
2、EndProcessRequest
3、ProcessRequest
具體應該重寫哪一個就需要根據具體的業務具體分析了。