我意识到会话和REST并不能完全并行,但是使用新的Web API是否无法访问会话状态?HttpContext.Current.Session
始终为null。
我意识到会话和REST并不能完全并行,但是使用新的Web API是否无法访问会话状态?HttpContext.Current.Session
始终为null。
Answers:
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);
})
);
Web表格
执行上述操作,但将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);
点网核心
添加Microsoft.AspNetCore.Session NuGet程序包,然后进行以下代码更改:
在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();
在控制器内,在顶部添加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
有关在点网核心内访问会话数据的更多信息,请访问:https : //docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
性能问题
阅读下面有关性能的Simon Weaver的答案。如果您在WebApi项目中访问会话数据,则可能会导致非常严重的性能后果-我已经看到ASP.NET对并发请求强制执行200ms的延迟。如果您有许多并发请求,这可能加起来并造成灾难性的后果。
安全问题
确保您锁定每个用户的资源-经过身份验证的用户不应从他们无法访问的WebApi中检索数据。
阅读Microsoft关于ASP.NET Web API中的身份验证和授权的文章- //www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
阅读Microsoft关于避免跨站点请求伪造hack攻击的文章。(总之,检查出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启用它带来不必要的性能损失。
您只需放入Thread.Sleep(5000)
WebAPI方法并启用Session ,就可以轻松地自己进行测试。运行5个请求,总共需要25秒。如果没有会话,则总共需要5秒钟以上的时间。
(同样的道理也适用于SignalR)。
好吧,您是对的,REST是无状态的。如果您使用会话,则处理将变为有状态,后续请求将能够使用状态(来自会话)。
为了使会话重新水化,您需要提供一个密钥来关联状态。在普通的asp.net应用程序中,通过使用cookie(cookie会话)或url参数(无cookie会话)来提供密钥。
如果您需要会话忘记休息,则会话在基于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);
}
在这里第四位:http ://forums.asp.net/t/1773026.aspx/1
根据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”]那样在api调用中使用会话:
/// <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时,确实可以使用该会话。缺少的部分是如何将会话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);
}
}
是的,会话与Rest API不能同时使用,我们也应避免这种做法。但是根据要求,我们需要以某种方式维护会话,以便在每个请求中客户端服务器都可以交换或维护状态或数据。因此,在不破坏REST协议的情况下实现此目标的最佳方法是通过令牌(如JWT)进行通信。
回到基础上,为什么不保持简单,将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" />
Java脚本
$(document).ready(function(){
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
在ApiController
技巧上(或.ReadOnly
在适当的地方)。