我意识到会话和REST并不完全齐头并进,但是使用新的Web API无法访问会话状态吗? HttpContext.Current.Session
始终为空。
答案 0 :(得分:313)
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文件/代码中,我们可以进行可以访问会话的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 Core
添加 Microsoft.AspNetCore.Session NuGet包,然后进行以下代码更改:
在ConfigureServices函数中的服务对象上调用 AddDistributedMemoryCache 和 AddSession 方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
并在配置功能中添加对 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
然后转到此网址会将其拉出来:
http://localhost:1234/api/session/get
有关在dot net core中访问会话数据的更多信息,请访问:https://docs.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
阅读微软关于避免跨站点请求伪造黑客攻击的文章。 (简而言之,请查看AntiForgery.Validate方法) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
答案 1 :(得分:63)
您可以使用自定义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
答案 2 :(得分:44)
性能,性能,性能!
为什么你根本不应该在WebAPI中使用Session,这是一个非常好的,经常被忽视的原因。
ASP.NET在使用Session时的工作方式是序列化从单个客户端收到的所有请求。现在我不是在谈论对象序列化 - 而是按照收到的顺序运行它们并等待每个完成后再运行下一个。这是为了避免令人讨厌的线程/竞争条件,如果两个请求都试图同时访问Session。
Concurrent Requests and Session State
访问ASP.NET会话状态 是每个会话独占,这意味着如果两个不同的用户 并发请求,授予对每个单独会话的访问权限 同时。但是,如果有两个并发请求 同一会话(通过使用相同的SessionID值),第一个请求 获得会话信息的独占访问权限。第二个请求 仅在第一个请求完成后执行。(第二个会话 如果释放信息的独占锁,也可以访问 因为第一个请求超过了锁定超时。)如果 @ Page指令中的EnableSessionState值设置为ReadOnly,a 请求只读会话信息不会导致 对会话数据的独占锁定。但是,只读请求 会话数据可能仍然必须等待读写设置的锁定 请求清除会话数据。
那么这对Web API意味着什么呢?如果您有一个运行许多AJAX请求的应用程序,那么一次只能运行一个。如果您的请求较慢,则会阻止该客户端中的所有其他请求,直到完成为止。在某些应用中,这可能导致非常明显的低迷性能。
所以你应该使用一个MVC控制器,如果你绝对需要来自用户会话的东西,并避免为WebApi启用它的不必要的性能损失。
只需将Thread.Sleep(5000)
放入WebAPI方法并启用Session即可轻松自行测试。运行5个请求,总共需要25秒才能完成。没有Session,他们总共需要5秒钟。
(同样的理由适用于SignalR)。
答案 3 :(得分:21)
你是对的,REST是无国籍的。如果您使用会话,则处理将变为有状态,后续请求将能够使用状态(来自会话)。
为了使会话重新水合,您需要提供一个关键状态。在普通的asp.net应用程序中,密钥是使用cookie(cookie-sessions)或url参数(无cookie会话)提供的。
如果您需要会话忘记休息,则会话与基于REST的设计无关。如果您需要会话进行验证,请使用令牌或通过IP地址进行授权。
答案 4 :(得分:20)
马克,如果你检查nerddinner MVC example,逻辑几乎是一样的。
您只需要检索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 project借用它。
答案 5 :(得分:12)
解决问题:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
在Global.asax.cs
中答案 6 :(得分:10)
最后一个现在不工作,拿这个,它对我有用。
在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);
}
答案 7 :(得分:8)
根据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);
}
答案 8 :(得分:7)
我在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”]那样在api调用中使用session:
/// <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;
}
}
}
}
希望这有帮助。
答案 9 :(得分:6)
我遵循@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);
}
}
答案 10 :(得分:3)
有一件事需要提及@LachlanB的答案。
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
如果省略第if (IsWebApiRequest())
行
如果您的网站与网页表单混合,整个网站都会出现网页加载缓慢问题。
答案 11 :(得分:0)
是的,会话与Rest API不能同时进行,我们也应避免这种做法。但是根据要求,我们需要以某种方式维护会话,以便在每个请求中客户端服务器都可以交换或维护状态或数据。因此,在不破坏REST协议的情况下实现此目标的最佳方法是通过JWT之类的令牌进行通信。
答案 12 :(得分:-4)
回到基础知识为什么不保持简单并将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 */}
}