我知道Session和REST不太相关,但使用新的Web API访问会话状态是否不可能?HttpContext.Current.Session
始终为空。
我知道Session和REST不太相关,但使用新的Web API访问会话状态是否不可能?HttpContext.Current.Session
始终为空。
MVC
对于一个MVC项目,进行如下更改(WebForms和Dot Net Core的答案在下面):
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 }
);
}
}
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);
}
}
这个解决方案的额外好处是,我们可以在JavaScript中获取基本URL以便进行AJAX调用:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
然后,在我们的 Javascript 文件/代码中,我们可以进行 Web API 调用,访问会话:
$.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);
Add the Microsoft.AspNetCore.Session NuGet package and then call the AddDistributedMemoryCache and AddSession methods on the services object within the ConfigureServices function in Startup.cs:
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();
在您的控制器顶部添加一个 using 语句:
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
在dot net core中访问会话数据的更多信息,请参阅此处:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state
有关性能问题,请查看Simon Weaver在下面的回答。如果您在WebApi项目中访问会话数据,可能会导致非常严重的性能后果 - 我曾经看到ASP.NET对并发请求强制执行200ms的延迟。如果您有许多并发请求,这可能会累加并变得灾难性。
确保根据用户锁定资源 - 已验证的用户不应该能够从您的WebApi检索他们无权访问的数据。
请阅读Microsoft在ASP.NET Web API中的身份验证和授权文章 - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
请阅读Microsoft关于避免跨站点请求伪造攻击的文章。 (简而言之,请查看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;
}
}
在这里找到:http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
性能、性能、性能!
有一个很好的理由,通常被忽视了,这就是为什么你根本不应该在WebAPI中使用Session。
当Session被使用时,ASP.NET会序列化来自单个客户端的所有请求。我不是指对象序列化,而是按收到顺序运行它们,并等待每个请求完成后再运行下一个。这是为了避免两个请求同时尝试访问Session时出现丑陋的线程/竞态条件。
并发请求和会话状态
访问ASP.NET会话状态是独占的,这意味着如果两个不同的用户进行并发请求,则分别授予对每个单独会话的访问权限。但是,如果两个并发请求使用相同的SessionID值请求相同的会话,则第一个请求获得对会话信息的独占访问权。只有在第一个请求完成之后,第二个请求才会执行。(如果第一个请求超过锁定超时,则第二个会话也可以获得访问权限。)如果在@ Page指令中设置了EnableSessionState值为ReadOnly,则对只读会话信息的请求不会导致对会话数据的独占锁定。但是,对于会话数据的只读请求可能仍然需要等待由读/写请求设置的锁定清除。
那么这对Web API意味着什么呢?如果你有一个运行多个AJAX请求的应用程序,那么只有一个请求能够同时运行。如果你有一个较慢的请求,那么它将阻塞来自该客户端的所有其他请求,直到该请求完成。在某些应用程序中,这可能会导致非常明显的性能下降。
因此,如果你确实需要从用户会话中获取一些内容,则应使用MVC控制器,并避免为WebAPI启用Session带来的不必要的性能惩罚。
您可以轻松地通过在WebAPI方法中加入Thread.Sleep(5000)
并启用Session来自行测试。向其发送5个请求,它们将总共花费25秒钟才能完成。如果没有启用Session,则它们将只需稍微超过5秒钟即可完成。(此相同的推理适用于SignalR)。没错,REST是无状态的。如果使用会话,则处理将变为有状态,随后的请求将能够使用状态(来自会话)。
为了重新激活会话,您需要提供一个与状态相关联的键。在普通的asp.net应用程序中,通过使用cookie(cookie会话)或url参数(无cookie会话)来提供该键。
如果您需要会话,则忘记REST,在基于REST的设计中,会话不相关。如果您需要会话以进行验证,那么请使用令牌或按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项目中借鉴。
修复问题的方法:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
在 Global.asax.cs 文件中
上一个现在不起作用了,请使用这个,它对我有效。
在 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);
}
继LachlanB的答案之后,如果你的ApiController不属于特定的目录(比如/api),你可以使用RouteTable.Routes.GetRouteData来测试请求,例如:
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控制器中添加以下方法解决了这个问题:
/// <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);
}
}
您也可以创建一个自定义的过滤器属性,在需要会话的 API 调用上使用它,然后您可以像通常一样通过 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时,会话才会可用。遗漏的部分是第一次将Session cookie发送给客户端的方式是什么?
我创建了一个HttpModule,不仅启用了HttpSessionState的可用性,而且在创建新会话时还向客户端发送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);
}
}
ApiController
上使用[SessionState(SessionStateBehavior.Required)]
(或在适当的情况下使用.ReadOnly
)可以解决这个问题。 - Roman Starkov