使用ASP访问会话。NET Web API

我知道会话和REST并不完全是齐头并进的,但是使用新的Web API访问会话状态是不可能的吗?HttpContext.Current.Session总是空的。

298535 次浏览

你说得对,REST是无状态的。如果您使用会话,处理将变成有状态的,后续请求将能够使用状态(来自会话)。

为了给会话补水,您需要提供一个键来关联状态。在普通的asp.net应用程序中,该密钥是通过使用cookie (cookie-sessions)或url参数(无cookie会话)来提供的。

如果你需要一个会话而不是休息,那么会话在基于rest的设计中是无关紧要的。如果您需要一个会话进行验证,则使用令牌或通过IP地址授权。

Mark,如果你检查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项目中借用它。

您可以使用自定义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

最后一个不行,拿这个,我用过。

在WebApiConfig.cs中的App_Start

    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

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);
}


}

这个解决方案有额外的好处,我们可以在javascript中获取基本URL来进行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。寄存器函数来代替一个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包,然后进行以下代码更改:

Startup.cs

在ConfigureServices函数中的服务对象上调用AddDistributedMemoryCacheAddSession方法:

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语句:

using Microsoft.AspNetCore.Http;

然后使用HttpContext。会话对象在你的代码中,如下所示:

    [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

关于在。net core中访问会话数据的更多信息:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state

性能问题

下面是西蒙·韦弗关于性能的回答。如果你在WebApi项目中访问会话数据,可能会有非常严重的性能后果。NET对并发请求强制执行200ms延迟。如果您有许多并发请求,这可能会导致灾难性的后果。


安全问题

确保你为每个用户锁定了资源——一个经过身份验证的用户不应该能够从你的WebApi中检索他们没有权限访问的数据。

阅读微软关于ASP中的身份验证和授权的文章。NET Web API - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

阅读微软关于避免跨站点请求伪造黑客攻击的文章。(简而言之,看看AntiForgery。验证方法)- https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks

根据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);
}

回到基础,为什么不保持它的简单和存储会话值在一个隐藏的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

美元(文档)。Ready (function () {

    var sessionValue = $('#hBlah').val();


alert(sessionValue);


/* Now call your API with the session variable */}

我在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调用,你需要会话,然后你可以在你的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时,会话确实是可用的。缺失的部分是会话cookie是如何第一次发送到客户端?

我创建了一个HttpModule,它不仅启用httpessionstate可用性,而且还在创建新会话时向客户端发送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);
}
}

为什么在WebAPI中避免使用Session ?

业绩,业绩,业绩!

有一个很好的,但经常被忽视的原因,为什么你不应该在WebAPI中使用Session。

ASP。当Session在使用时,NET工作是序列化从单个客户端接收的所有请求。现在我不是在谈论对象序列化——而是按照接收到的顺序运行它们,并在运行下一个之前等待每个对象完成。这是为了避免当两个请求同时访问Session时出现糟糕的线程/竞争情况。

并发请求和会话状态

访问ASP。NET会话状态 是独占的每个会话,这意味着如果两个不同的用户使 并发请求,授予对每个单独会话的访问权 同时。但是,如果有两个并发请求,则 同一会话(通过使用相同的SessionID值),第一个请求 获取对会话信息的独占访问权。第二个请求 仅在第一个请求完成后执行。(第二次会话 如果释放了信息的排他锁,还可以访问吗 因为第一个请求超过了锁定超时。)如果 @ Page指令中的EnableSessionState值被设置为ReadOnly 对只读会话信息的请求不会导致 会话数据的排他锁定。但是,只读请求 会话数据可能仍然需要等待读写操作设置的锁 请求清除会话数据

那么这对Web API意味着什么呢?如果您的应用程序运行许多AJAX请求,那么一次只能运行一个。如果你有一个较慢的请求,那么它将阻止来自该客户端的所有其他请求,直到它完成。在某些应用程序中,这可能会导致非常明显的缓慢性能。

因此,如果你绝对需要用户会话中的某些东西,你可能应该使用MVC控制器,以避免为WebApi启用它所带来的不必要的性能损失。

你可以通过将Thread.Sleep(5000)放在WebAPI方法中并启用Session来轻松测试。向它运行5个请求,总共需要25秒才能完成。如果没有塞申斯,他们总共只需要5秒多一点。

(同样的道理也适用于SignalR)。

要解决这个问题:

protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}

在Global.asax.cs

@LachlanB的回答中有一件事需要提一下。

protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}

如果省略了if (IsWebApiRequest())

如果你的网站混合了网页表单,整个网站都会有页面加载缓慢的问题。

是的,会话与Rest API并不是紧密相连的,而且我们应该避免这种做法。但是根据需求,我们需要以某种方式维护会话,以便在每个请求中客户端服务器可以交换或维护状态或数据。因此,在不破坏REST协议的情况下实现这一点的最佳方法是通过JWT这样的令牌进行通信。

https://jwt.io/