Accediendo a la sesión usando ASP.NET Web API

Me doy cuenta de que la sesión y el REST no van de la mano pero ¿no es posible acceder al estado de la sesión utilizando la nueva API web? HttpContext.Current.Session siempre es nulo.

MVC

Para un proyecto MVC realice los siguientes cambios (respuesta de WebForms y Dot Net Core a continuación):

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

Esta solución tiene la ventaja añadida de que podemos buscar la URL base en javascript para realizar llamadas AJAX:

_Layout.cshtml

  @RenderBody()  @RenderSection("scripts", required: false) 

y luego dentro de nuestros archivos / código Javascript podemos hacer nuestras llamadas webapi que pueden acceder a la sesión:

 $.getJSON(apiBaseUrl + '/MyApi') .done(function (data) { alert('session data received: ' + data.whatever); }) ); 

WebForms

Haga lo anterior pero cambie la función WebApiConfig.Register para tomar una RouteCollection en su lugar:

 public static void Register(RouteCollection routes) { routes.MapHttpRoute( name: "DefaultApi", routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

Y luego llame a lo siguiente en Application_Start:

 WebApiConfig.Register(RouteTable.Routes); 

Dot Net Core

Agregue el paquete Microsoft.AspNetCore.Session NuGet y luego realice los siguientes cambios de código:

Startup.cs

Llame a los métodos AddDistributedMemoryCache y AddSession en el objeto services dentro de la función ConfigureServices:

 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); ... services.AddDistributedMemoryCache(); services.AddSession(); 

y en la función Configurar agregar una llamada a UseSession :

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseSession(); app.UseMvc(); 

SessionController.cs

Dentro de su controlador, agregue una instrucción using en la parte superior:

 using Microsoft.AspNetCore.Http; 

y luego usa el objeto HttpContext.Session dentro de tu código de esta forma:

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

ahora deberías poder golpear:

 http://localhost:1234/api/session/set/thisissomedata 

y luego ir a esta URL lo sacará:

 http://localhost:1234/api/session/get 

Mucha más información sobre cómo acceder a los datos de la sesión dentro del núcleo de red de puntos aquí: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state

Preocupaciones de rendimiento

Lee la respuesta de Simon Weaver a continuación sobre el rendimiento. Si está accediendo a los datos de la sesión dentro de un proyecto de WebApi, puede tener consecuencias de rendimiento muy serias: he visto que ASP.NET impone un retraso de 200 ms para las solicitudes simultáneas. Esto podría sumrse y volverse desastroso si tiene muchas solicitudes simultáneas.


Preocupaciones de seguridad

Asegúrese de bloquear recursos por usuario: un usuario autenticado no debería poder recuperar datos de su WebApi a los que no tiene acceso.

Lea el artículo de Microsoft sobre Autenticación y Autorización en ASP.NET Web API – https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

Lea el artículo de Microsoft sobre cómo evitar los ataques de piratería de solicitudes entre sitios. (En resumen, consulte el método AntiForgery.Validate) – https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks

Puede acceder al estado de la sesión utilizando un RouteHandler personalizado.

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

Encontrado aquí: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html

¿Por qué evitar usar Session en WebAPI?

Rendimiento, rendimiento, rendimiento!

Hay una muy buena, y muchas veces olvidada, razón por la que no debes usar Session en WebAPI en absoluto.

La forma en que funciona ASP.NET cuando Session está en uso es serializar todas las solicitudes recibidas desde un único cliente . Ahora no estoy hablando de serialización de objetos, sino de ejecutarlos en el orden recibido y esperar a que se complete cada uno antes de ejecutar el siguiente. Esto es para evitar condiciones desagradables de subprocesos / carreras si dos solicitudes intentan acceder a la sesión simultáneamente.

Solicitudes concurrentes y estado de sesión

El acceso al estado de la sesión de ASP.NET es exclusivo por sesión, lo que significa que si dos usuarios diferentes realizan solicitudes concurrentes, se otorga el acceso a cada sesión por separado. Sin embargo, si se realizan dos solicitudes simultáneas para la misma sesión (utilizando el mismo valor de ID de sesión), la primera solicitud obtiene acceso exclusivo a la información de la sesión. La segunda solicitud se ejecuta solo después de que finaliza la primera solicitud. (La segunda sesión también puede obtener acceso si se libera el locking exclusivo de la información porque la primera solicitud excede el tiempo de espera de locking). Si el valor de EnableSessionState en la directiva @ Page se establece en ReadOnly, una solicitud de solo lectura la información de la sesión no da como resultado un locking exclusivo en los datos de la sesión. Sin embargo, las solicitudes de solo lectura para datos de sesión aún tienen que esperar a que se elimine un locking establecido por una solicitud de lectura y escritura para datos de sesión.

Entonces, ¿qué significa esto para la API web? Si tiene una aplicación que ejecuta muchas solicitudes AJAX, solo podrá ejecutarse UNA a la vez. Si tiene una solicitud más lenta, bloqueará todas las demás de ese cliente hasta que se complete. En algunas aplicaciones esto podría llevar a un rendimiento muy notablemente lento.

Por lo tanto, probablemente debería usar un controlador MVC si necesita absolutamente algo de la sesión de los usuarios y evitar la penalización de rendimiento no imprescindible de habilitarlo para WebApi.

Puede probar esto fácilmente simplemente colocando Thread.Sleep(5000) en un método WebAPI y habilite Session. Ejecute 5 solicitudes y le tomará un total de 25 segundos para completar. Sin sesión tomarán un total de poco más de 5 segundos.

(Este mismo razonamiento se aplica a SignalR).

Bueno, tienes razón, REST no tiene estado. Si utiliza una sesión, el procesamiento se convertirá en estado, las solicitudes posteriores podrán usar el estado (de una sesión).

Para rehidratar una sesión, deberá proporcionar una clave para asociar el estado. En una aplicación asp.net normal esa clave se proporciona mediante el uso de una cookie (cookie-sessions) o un parámetro url (sesiones sin cookies).

Si necesita una sesión, olvídese del descanso, las sesiones son irrelevantes en los diseños basados ​​en REST. Si necesita una sesión para la validación, utilice un token o autorice por direcciones IP.

Mark, si revisas el ejemplo de nerdrinner MVC, la lógica es más o menos la misma.

Solo necesita recuperar la cookie y configurarla en la sesión actual.

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 

Deberá definir su clase “SampleIdentity”, que puede tomar prestada del proyecto nerddinner .

El último no funciona ahora, toma este, funcionó para mí.

en WebApiConfig.cs en 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); } 

fournd aquí: http://forums.asp.net/t/1773026.aspx/1

Para solucionar el problema:

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

en Global.asax.cs

Siguiendo con la respuesta de LachlanB, si su ApiController no se encuentra dentro de un directorio particular (como / api), en su lugar puede probar la solicitud usando RouteTable.Routes.GetRouteData, por ejemplo:

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

Tuve el mismo problema en asp.net mvc, lo arreglé poniendo este método en mi controlador de API base del que heredaron todos mis controladores de API:

  ///  /// Get the session from HttpContext.Current, if that is null try to get it from the Request properties. ///  ///  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; } 

Luego, en su llamada de API, quiere acceder a la sesión que acaba de hacer:

 HttpContextWrapper httpContextWrapper = GetHttpContextWrapper(); var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"]; 

También tengo esto en mi archivo Global.asax.cs como lo han publicado otras personas, no estoy seguro si aún lo necesita utilizando el método anterior, pero aquí está por las dudas:

 ///  /// The following method makes Session available. ///  protected void Application_PostAuthorizeRequest() { if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api")) { HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); } } 

También podría simplemente hacer un atributo de filtro personalizado que pueda conservar en sus llamadas de API que necesita una sesión, luego puede usar la sesión en su llamada de API como lo haría normalmente a través de HttpContext.Current.Session [“SomeValue”]:

  ///  /// Filter that gets session context from request if HttpContext.Current is null. ///  public class RequireSessionAttribute : ActionFilterAttribute { ///  /// Runs before action ///  ///  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; } } } } 

Espero que esto ayude.

Seguí el enfoque de @LachlanB y, de hecho, la sesión estaba disponible cuando la cookie de sesión estaba presente en la solicitud. La parte que falta es cómo la cookie de sesión se envía al cliente la primera vez.

Creé un HttpModule que no solo habilita la disponibilidad de HttpSessionState sino que también envía la cookie al cliente cuando se crea una nueva sesión.

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

Una cosa debe mencionarse en la respuesta de @LachlanB.

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

Si omite la línea if (IsWebApiRequest())

Todo el sitio tendrá problemas de lentitud de carga de la página si su sitio se mezcla con páginas de formularios web.

Volviendo a lo básico, ¿por qué no mantenerlo simple y almacenar el valor de la sesión en un valor html oculto para pasar a su API?

Controlador

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

Javascript

$ (document) .ready (function () {

  var sessionValue = $('#hBlah').val(); alert(sessionValue); /* Now call your API with the session variable */} 

}