使用ASP.NET Web API访问会话
我意识到会话和REST并不完全一致,但是使用新的Web API访问会话状态是不可能的吗? HttpContext.Current.Session
始终为空。
MVC
对于MVC项目,进行以下更改(下面的WebForms和Dot Net Core答案):
WebApiConfig.cs
public static class WebApiConfig { public static string UrlPrefix { get { return "api"; } } public static string UrlPrefixRelative { get { return "~/api"; } } public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
的Global.asax.cs
public class MvcApplication : System.Web.HttpApplication { ... protected void Application_PostAuthorizeRequest() { if (IsWebApiRequest()) { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); } } private bool IsWebApiRequest() { return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative); } }
这个解决scheme有额外的好处,我们可以在JavaScript中获取基地址来进行AJAX调用:
_Layout.cshtml
<body> @RenderBody() <script type="text/javascript"> var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)'; </script> @RenderSection("scripts", required: false)
然后在我们的Javascript文件/代码中,我们可以使我们的webapi调用可以访问会话:
$.getJSON(apiBaseUrl + '/MyApi') .done(function (data) { alert('session data received: ' + data.whatever); }) );
的WebForms
做上面的,但改变WebApiConfig.Register函数采取一个RouteCollection:
public static void Register(RouteCollection routes) { routes.MapHttpRoute( name: "DefaultApi", routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
然后在Application_Start中调用以下内容:
WebApiConfig.Register(RouteTable.Routes);
Dot Net核心
添加Microsoft.AspNetCore.Session NuGet包,然后进行以下代码更改:
Startup.cs
在ConfigureServices函数内的服务对象上调用AddDistributedMemoryCache和AddSession方法:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); ... services.AddDistributedMemoryCache(); services.AddSession();
并在Configure函数中添加对UseSession的调用:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseSession(); app.UseMvc();
SessionController.cs
在您的控制器中,在顶部添加使用语句:
using Microsoft.AspNetCore.Http;
然后在代码中使用HttpContext.Session对象,如下所示:
[HttpGet("set/{data}")] public IActionResult setsession(string data) { HttpContext.Session.SetString("keyname", data); return Ok("session data set"); } [HttpGet("get")] public IActionResult getsessiondata() { var sessionData = HttpContext.Session.GetString("keyname"); return Ok(sessionData); }
你现在应该能够击中:
http://localhost:1234/api/session/set/thisissomedata
然后去这个URL将它拉出来:
http://localhost:1234/api/session/get
点击这里访问会话数据的更多信息: https : //docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
性能问题
请阅读Simon Weaver关于performance的回答。 如果你正在访问WebApi项目中的会话数据,它可能会有非常严重的性能后果 – 我已经看到ASP.NET为并发请求强制延迟200毫秒。 如果你有很多并发请求,这可能会加起来并且变成灾难性的。
安全问题
确保你locking每个用户的资源 – 一个经过身份validation的用户不应该能够从他们无权访问的WebApi中检索数据。
阅读微软有关ASP.NET Web API中身份validation和授权的文章 – https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
阅读微软有关避免跨站请求伪造黑客攻击的文章。 (简而言之,检查AntiForgery.Validate方法) – https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
您可以使用自定义RouteHandler访问会话状态。
// In global.asax public class MvcApp : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { var route = routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); route.RouteHandler = new MyHttpControllerRouteHandler(); } } // Create two new classes public class MyHttpControllerHandler : HttpControllerHandler, IRequiresSessionState { public MyHttpControllerHandler(RouteData routeData) : base(routeData) { } } public class MyHttpControllerRouteHandler : HttpControllerRouteHandler { protected override IHttpHandler GetHttpHandler( RequestContext requestContext) { return new MyHttpControllerHandler(requestContext.RouteData); } } // Now Session is visible in your Web API public class ValuesController : ApiController { public string Get(string input) { var session = HttpContext.Current.Session; if (session != null) { if (session["Time"] == null) session["Time"] = DateTime.Now; return "Session Time: " + session["Time"] + input; } return "Session is not availabe" + input; } }
在这里find: http : //techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
为什么要避免在WebAPI中使用Session?
性能,性能,性能!
有一个非常好的,经常被忽视的原因,为什么你不应该在WebAPI中使用Session。
当Session使用时,ASP.NET的工作方式是序列化从一个客户端收到的所有请求。 现在我不是在谈论对象序列化,而是按照收到的顺序运行它们,并在运行下一个完成之前等待每个完成。 这是为了避免讨厌的线程/竞争条件,如果两个请求每个尝试同时访问会话。
并发请求和会话状态
访问ASP.NET会话状态是每个会话独占的,这意味着如果两个不同的用户发出并发请求,则同时授予对每个单独会话的访问权限。 但是, 如果为同一会话创build了两个并发请求(通过使用相同的SessionID值),则第一个请求将获得对会话信息的独占访问权限。 第二个请求仅在第一个请求完成后执行。 (如果由于第一个请求超过了locking超时而对信息的排他锁被释放,则第二个会话也可以访问。)如果@ Page指令中的EnableSessionState值设置为ReadOnly,则只读请求会话信息不会导致对会话数据的独占locking。 但是,会话数据的只读请求可能仍然需要等待由会话数据的读写请求设置的locking清除。
那么这对Web API意味着什么呢? 如果你有一个应用程序运行多个AJAX请求,那么只有一个将能够运行在一个时间。 如果您的请求较慢,则会阻止所有其他请求,直到完成。 在某些应用中,这可能会导致非常缓慢的性能。
所以你应该使用MVC控制器,如果你绝对需要用户会话中的某些东西,并避免为WebApi启用它的不必要的性能损失。
只需将Thread.Sleep(5000)
放在WebAPI方法中,然后启用Session,就可以轻松地对此进行testing。 运行5个请求,总共需要25秒才能完成。 没有会话,他们将总共花费超过5秒钟。
(这同样适用于SignalR)。
那么你是对的,REST是无状态的。 如果使用会话,处理将变为有状态,随后的请求将能够使用状态(来自会话)。
为了让会话重新进行,您需要提供一个密钥来关联状态。 在一个正常的asp.net应用程序中,通过使用cookie(cookie-sessions)或url参数(无Cookie会话)来提供密钥。
如果你需要一个会话忘记rest,那么会话在基于REST的devise中是不相关的。 如果您需要validation会话,请使用令牌或IP地址授权。
马克,如果你检查nerddinner MVC的例子 ,逻辑几乎是一样的。
您只需要检索cookie并将其设置在当前会话中。
的Global.asax.cs
public override void Init() { this.AuthenticateRequest += new EventHandler(WebApiApplication_AuthenticateRequest); base.Init(); } void WebApiApplication_AuthenticateRequest(object sender, EventArgs e) { HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName]; FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); SampleIdentity id = new SampleIdentity(ticket); GenericPrincipal prin = new GenericPrincipal(id, null); HttpContext.Current.User = prin; } enter code here
你必须定义你的“SampleIdentity”类,你可以从nerddinner项目中借用。
最后一个现在不工作,拿这个,它为我工作。
在App_Start的WebApiConfig.cs中
public static string _WebApiExecutionPath = "api"; public static void Register(HttpConfiguration config) { var basicRouteTemplate = string.Format("{0}/{1}", _WebApiExecutionPath, "{controller}"); // Controller Only // To handle routes like `/api/VTRouting` config.Routes.MapHttpRoute( name: "ControllerOnly", routeTemplate: basicRouteTemplate//"{0}/{controller}" ); // Controller with ID // To handle routes like `/api/VTRouting/1` config.Routes.MapHttpRoute( name: "ControllerAndId", routeTemplate: string.Format ("{0}/{1}", basicRouteTemplate, "{id}"), defaults: null, constraints: new { id = @"^\d+$" } // Only integers );
Global.asax中
protected void Application_PostAuthorizeRequest() { if (IsWebApiRequest()) { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); } } private static bool IsWebApiRequest() { return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(_WebApiExecutionPath); }
fournd这里: http ://forums.asp.net/t/1773026.aspx/1
要解决这个问题:
protected void Application_PostAuthorizeRequest() { System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required); }
在Global.asax.cs中
继LachlanB的回答之后,如果您的ApiController不在特定的目录(如/ api)中,您可以使用RouteTable.Routes.GetRouteData来testing请求,例如:
protected void Application_PostAuthorizeRequest() { // WebApi SessionState var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current)); if (routeData != null && routeData.RouteHandler is HttpControllerRouteHandler) HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); }
我在asp.net mvc中也遇到了同样的问题,我通过将这个方法放在我的基本api控制器中来解决这个问题:我所有的api控制器都inheritance自这个控制器:
/// <summary> /// Get the session from HttpContext.Current, if that is null try to get it from the Request properties. /// </summary> /// <returns></returns> protected HttpContextWrapper GetHttpContextWrapper() { HttpContextWrapper httpContextWrapper = null; if (HttpContext.Current != null) { httpContextWrapper = new HttpContextWrapper(HttpContext.Current); } else if (Request.Properties.ContainsKey("MS_HttpContext")) { httpContextWrapper = (HttpContextWrapper)Request.Properties["MS_HttpContext"]; } return httpContextWrapper; }
然后在你的API调用中,你想访问你刚刚做的会话:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper(); var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
我也有这样在我的Global.asax.cs文件像其他人已经发布,不知道你是否仍然需要使用上面的方法,但在这里,以防万一:
/// <summary> /// The following method makes Session available. /// </summary> protected void Application_PostAuthorizeRequest() { if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api")) { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); } }
你也可以创build一个自定义的filter属性,你可以在你的api调用中保留你需要的session,然后你可以在你的api调用中使用session,就像你通常通过HttpContext.Current.Session [“SomeValue”]:
/// <summary> /// Filter that gets session context from request if HttpContext.Current is null. /// </summary> public class RequireSessionAttribute : ActionFilterAttribute { /// <summary> /// Runs before action /// </summary> /// <param name="actionContext"></param> public override void OnActionExecuting(HttpActionContext actionContext) { if (HttpContext.Current == null) { if (actionContext.Request.Properties.ContainsKey("MS_HttpContext")) { HttpContext.Current = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).ApplicationInstance.Context; } } } }
希望这可以帮助。
我遵循@LachlanB的方法,当会话cookie出现在请求中时,会话确实可用。 缺less的部分是Session cookie如何首次发送给客户端?
我创build了一个HttpModule,它不仅启用了HttpSessionState的可用性,还在创build新会话时将cookie发送给客户端。
public class WebApiSessionModule : IHttpModule { private static readonly string SessionStateCookieName = "ASP.NET_SessionId"; public void Init(HttpApplication context) { context.PostAuthorizeRequest += this.OnPostAuthorizeRequest; context.PostRequestHandlerExecute += this.PostRequestHandlerExecute; } public void Dispose() { } protected virtual void OnPostAuthorizeRequest(object sender, EventArgs e) { HttpContext context = HttpContext.Current; if (this.IsWebApiRequest(context)) { context.SetSessionStateBehavior(SessionStateBehavior.Required); } } protected virtual void PostRequestHandlerExecute(object sender, EventArgs e) { HttpContext context = HttpContext.Current; if (this.IsWebApiRequest(context)) { this.AddSessionCookieToResponseIfNeeded(context); } } protected virtual void AddSessionCookieToResponseIfNeeded(HttpContext context) { HttpSessionState session = context.Session; if (session == null) { // session not available return; } if (!session.IsNewSession) { // it's safe to assume that the cookie was // received as part of the request so there is // no need to set it return; } string cookieName = GetSessionCookieName(); HttpCookie cookie = context.Response.Cookies[cookieName]; if (cookie == null || cookie.Value != session.SessionID) { context.Response.Cookies.Remove(cookieName); context.Response.Cookies.Add(new HttpCookie(cookieName, session.SessionID)); } } protected virtual string GetSessionCookieName() { var sessionStateSection = (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState"); return sessionStateSection != null && !string.IsNullOrWhiteSpace(sessionStateSection.CookieName) ? sessionStateSection.CookieName : SessionStateCookieName; } protected virtual bool IsWebApiRequest(HttpContext context) { string requestPath = context.Request.AppRelativeCurrentExecutionFilePath; if (requestPath == null) { return false; } return requestPath.StartsWith(WebApiConfig.UrlPrefixRelative, StringComparison.InvariantCultureIgnoreCase); } }
有一件事需要提及@LachlanB的答案。
protected void Application_PostAuthorizeRequest() { if (IsWebApiRequest()) { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); } }
如果你忽略这行if (IsWebApiRequest())
如果您的网站与网页表单页面混合在一起,整个网站将有页面加载缓慢的问题。
回到基础知识为什么不保持简单并将Session值存储在一个隐藏的html值中传递给您的API?
调节器
public ActionResult Index() { Session["Blah"] = 609; YourObject yourObject = new YourObject(); yourObject.SessionValue = int.Parse(Session["Blah"].ToString()); return View(yourObject); }
CSHTML
@model YourObject @{ var sessionValue = Model.SessionValue; } <input type="hidden" value="@sessionValue" id="hBlah" />
使用Javascript
$(document).ready(function(){
var sessionValue = $('#hBlah').val(); alert(sessionValue); /* Now call your API with the session variable */}
}