[水煮ASP.NETWebAPI2方法论](1-8)添加Session状态

问题

专注于为中小企业提供成都网站制作、做网站服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业隆化免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上千多家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。

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。我们可以在应用程序启动的地方使用自己的类似 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(() => 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/


网页标题:[水煮ASP.NETWebAPI2方法论](1-8)添加Session状态
浏览地址:http://scyanting.com/article/gojdoi.html