Acceder a la Sesión Usando ASP.NET API WEB


Me doy cuenta de que la sesión y el RESTO no van exactamente de la mano, pero ¿no es posible acceder al estado de la sesión utilizando la nueva API Web? HttpContext.Current.Session es siempre null.

Author: Mark, 2012-03-07

12 answers

MVC

Para un proyecto MVC realice los siguientes cambios (WebForms y Dot Net Core answer abajo):

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 obtener la URL base en javascript para hacer las llamadas AJAX:

_Layout.cshtml

<body>
    @RenderBody()

    <script type="text/javascript">
        var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
    </script>

    @RenderSection("scripts", required: false) 

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

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

Formularios web

Haga lo anterior pero cambie el WebApiConfig.Función de registro 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 Microsoft.AspNetCore.Session NuGet paquete y luego hacer los siguientes cambios de código:

Inicio.cs

Llame al AddDistributedMemoryCache y AddSession métodos 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 Configure agregue 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 use el HttpContext.Objeto de sesión dentro de su código así:

    [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ía ser capaz de golpear:

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

Y luego ir a esta URL lo tirará fuera:

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

Mucha más información sobre el acceso a los datos de la sesión dentro de dot net core aquí: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state

Problemas de rendimiento

Lea la respuesta de Simon Weaver a continuación con respecto al rendimiento. Si está accediendo a los datos de sesión dentro de un proyecto WebAPI, puede tener consecuencias de rendimiento muy graves - he visto ASP.NET aplique un retraso de 200 ms para solicitudes simultáneas. Esto podría sumar y convertirse en desastroso si tiene muchas solicitudes concurrentes.


Problemas de seguridad

Asegúrese de que está bloqueando los recursos por usuario: un usuario autenticado no debería poder recuperar datos de su WebAPI a los que no tenga acceso.

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

Leer Artículo de Microsoft sobre cómo evitar los ataques de hackeo de Falsificación de solicitudes entre sitios. (En resumen, echa un vistazo al AntiForgery.Validar método) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks

 283
Author: Rocklan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-19 01:01:43

Puede acceder al estado de la sesión usando un enrutador 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;
    }
}

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

 61
Author: warrickh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-21 11:57:28

¿Por qué evitar usar Session en WebAPI?

Rendimiento, rendimiento, rendimiento!

Hay una razón muy buena, y a menudo pasada por alto, por la que no debería usar Session en WebAPI en absoluto.

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

Solicitudes concurrentes y Estado de sesión

Acceso a ASP.NET estado de la sesión es exclusivo por sesión, lo que significa que si dos usuarios diferentes hacen solicitudes concurrentes, se concede acceso a cada sesión separada simultáneamente. Sin embargo, si se realizan dos solicitudes simultáneas para el misma sesión (usando el mismo valor SessionID), la primera solicitud obtiene acceso exclusivo a la información de la sesión. La segunda petición se ejecuta solo después de que la primera solicitud haya finalizado. (El segundo período de sesiones también puede obtener acceso si se libera el bloqueo exclusivo de la información porque la primera solicitud excede el tiempo de espera del bloqueo.) Si el EnableSessionState valor en la directiva @ Page se establece en ReadOnly, un la solicitud de información de sesión de solo lectura no da lugar a un bloqueo exclusivo de los datos de la sesión. Sin embargo, solo lectura solicitudes de es posible que los datos de la sesión aún tengan que esperar un bloqueo establecido por una lectura-escritura solicitar que se borren los datos de la sesión.

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

Así que probablemente deberías usar un MVC controller si es absolutamente necesario algo de la sesión de usuarios y evitar la penalización de rendimiento unncesessary de habilitarlo para WebAPI.

Puede probar esto fácilmente por sí mismo simplemente poniendo Thread.Sleep(5000) en un método WebAPI y habilitar la sesión. Ejecute 5 solicitudes y tardarán un total de 25 segundos en completarse. Sin sesión tardarán un total de poco más de 5 segundos.

(Este mismo razonamiento se aplica a SignalR).

 38
Author: Simon_Weaver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-13 19:56:01

Bueno, tienes razón, REST es apátrida. Si usa una sesión, el procesamiento tendrá estado, las solicitudes posteriores podrán usar estado (de una sesión).

Para que una sesión se rehidrate, deberá proporcionar una clave para asociar el estado. En una normal asp.net aplicación que la clave se proporciona mediante el uso de una cookie (cookie-sessions) o parámetro de url (cookieless sessions).

Si necesita una sesión olvidar rest, las sesiones son irrelevantes en diseños basados en REST. Si necesita una sesión para la validación, luego use un token o autorice por direcciones IP.

 21
Author: Nickz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-07 23:32:03

Marca, si revisas el ejemplo nerddinner 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

Tendrás que definir tu clase "SampleIdentity", que puedes tomar prestada del proyecto nerddinner.

 20
Author: JSancho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-27 15:55:37

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

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

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

 10
Author: Cruiser KID,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-03 01:52:24

Para solucionar el problema:

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

En Global.asax.cs

 9
Author: Suresh Muttagi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-26 09:34:59

Siguiendo con la respuesta de LachlanB, si su ApiController no se encuentra dentro de un directorio en particular (como /api), puede probar la solicitud usando RouteTable.Ruta.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);
    }
 8
Author: Stumblor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-17 16:19:30

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

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

Luego, en la llamada a la api que desea acceder a la sesión, simplemente haga:

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

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

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

También podría crear un atributo de filtro personalizado que pueda pegue en sus llamadas de api que necesita sesión, luego puede usar sesión en su llamada de api como lo haría normalmente a través de HttpContext.Actual.Sesión ["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;
        }
      }
    }
  }

Espero que esto ayude.

 7
Author: Treyphor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-04 21:26:53

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 se envía la cookie de sesión al cliente la primera vez?

He creado 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);
    }
}
 6
Author: JCallico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-29 20:48:05

Hay que mencionar una cosa en la respuesta de @LachlanB.

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

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

Todo el sitio tendrá un problema de lentitud de carga de páginas si su sitio se mezcla con páginas de formularios web.

 3
Author: maxisam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-31 14:30:04

Volviendo a lo básico ¿por qué no mantenerlo simple y almacenar el valor de la sesión en un valor html oculto para pasarlo 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;
}

<input type="hidden" value="@sessionValue" id="hBlah" />

Javascript

$(documento).ready(function () {

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

    alert(sessionValue);

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

}

 -4
Author: Andy A.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-23 15:35:12