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
具体应该重写哪一个就需要根据具体的业务具体分析了。