web-dev-qa-db-fra.com

Accéder à la session à l'aide de l'API Web ASP.NET

Je me rends compte que session et REST ne vont pas exactement de pair, mais est-il impossible d'accéder à l'état de la session à l'aide de la nouvelle API Web? HttpContext.Current.Session est toujours nul.

258
Mark

MVC

Pour un projet MVC, apportez les modifications suivantes (WebForms et Dot Net Core répondent ci-dessous):

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

}

Cette solution a l'avantage supplémentaire de pouvoir récupérer l'URL de base en javascript pour effectuer les appels AJAX:

_Layout.cshtml

<body>
    @RenderBody()

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

    @RenderSection("scripts", required: false) 

et ensuite dans nos fichiers/code Javascript, nous pouvons faire nos appels webapi pouvant accéder à la session:

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

WebForms

Procédez comme ci-dessus, mais changez la fonction WebApiConfig.Register pour prendre un RouteCollection à la place:

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

Et appelez ensuite le suivant dans Application_Start:

WebApiConfig.Register(RouteTable.Routes);

Dot Net Core

Ajoutez le package NuGet Microsoft.AspNetCore.Session , puis apportez les modifications de code suivantes:

Startup.cs

Appelez les méthodes AddDistributedMemoryCache et AddSession de l'objet services dans la fonction ConfigureServices. :

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

    services.AddDistributedMemoryCache();
    services.AddSession();

et dans la fonction Configurer, ajoutez un appel à UseSession :

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

SessionController.cs

Dans votre contrôleur, ajoutez une instruction using en haut:

using Microsoft.AspNetCore.Http;

puis utilisez l'objet HttpContext.Session dans votre code comme suit:

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

vous devriez maintenant pouvoir frapper:

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

et puis aller à cette URL va le sortir:

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

Vous trouverez ici de nombreuses autres informations sur l'accès aux données de session dans le noyau net de base: https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/app-state

Problèmes de performance

Lisez la réponse de Simon Weaver ci-dessous concernant les performances. Si vous accédez à des données de session dans un projet WebApi, cela peut avoir des conséquences très graves sur les performances. J'ai vu ASP.NET appliquer un délai de 200 ms pour les demandes simultanées. Cela pourrait s’additionner et devenir désastreux si vous avez plusieurs demandes simultanées.


Problèmes de sécurité

Assurez-vous de verrouiller les ressources par utilisateur - un utilisateur authentifié ne devrait pas être en mesure de récupérer des données de votre WebApi auxquelles il n'a pas accès.

Lisez l'article de Microsoft sur l'authentification et l'autorisation dans les API Web ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

Lisez l'article de Microsoft sur la prévention des attaques de piratage de type Cross-Site Request Forgery. (En bref, consultez la méthode AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks =

322
Rocklan

Vous pouvez accéder à l'état de la session à l'aide d'un RouteHandler personnalisé.

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

Trouvé ici: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html

65
warrickh

Pourquoi éviter d'utiliser Session dans WebAPI?

Performance, performance, performance!

Il existe une très bonne raison, souvent négligée, de ne pas utiliser du tout Session dans WebAPI.

Le fonctionnement d’ASP.NET lorsque Session est utilisée consiste à sérialiser toutes les demandes reçues d’un seul client . Maintenant, je ne parle pas de sérialisation d'objet - mais de les exécuter dans l'ordre reçu et d'attendre que chacun se termine avant d'exécuter le suivant. Ceci permet d'éviter les conditions de concurrence/thread si deux requêtes tentent chacune d'accéder à Session simultanément.

demandes simultanées et état de session

L'accès à l'état de session ASP.NET est exclusif par session, ce qui signifie que si deux utilisateurs différents effectuent des demandes simultanées, l'accès à chaque session distincte est accordé simultanément. Cependant, si deux demandes simultanées sont effectuées pour la même session (en utilisant la même valeur SessionID), la première demande obtient un accès exclusif aux informations de session. La deuxième requête ne s'exécute qu'après la fin de la première requête. (La deuxième session peut également avoir accès si le verrou exclusif sur les informations est libéré car la première demande dépasse le délai de verrouillage.) Si la valeur EnableSessionState de la directive @ Page est définie sur ReadOnly, une demande d'informations de session en lecture seule n'entraîne pas de verrouillage exclusif des données de session. Cependant, les demandes en lecture seule pour les données de session peuvent encore attendre un verrou défini par une demande en lecture-écriture pour que les données de session soient effacées.

Alors, qu'est-ce que cela signifie pour l'API Web? Si vous avez une application qui exécute de nombreuses AJAX requêtes, seule UNE pourra être exécutée à la fois. Si votre demande est plus lente, tous les autres de ce client seront bloqués jusqu'à ce qu'elle soit terminée. Dans certaines applications, cela pourrait entraîner des performances très lentes.

Donc, vous devriez probablement utiliser un contrôleur MVC si vous avez absolument besoin de quelque chose de la session des utilisateurs et éviter la pénalité de performances inutile de l'activer pour WebApi.

Vous pouvez facilement tester cela pour vous-même en mettant simplement Thread.Sleep(5000) dans une méthode WebAPI et en activant Session. Exécutez 5 demandes et cela prendra 25 secondes au total. Sans session, cela prendra un peu plus de 5 secondes.

(Ce même raisonnement s'applique à SignalR).

46
Simon_Weaver

Eh bien, vous avez raison, REST est apatride. Si vous utilisez une session, le traitement deviendra dynamique, les requêtes suivantes pourront utiliser l'état (d'une session).

Pour qu'une session soit réhydratée, vous devez fournir une clé permettant d'associer l'état. Dans une application asp.net normale, cette clé est fournie à l'aide d'un paramètre cookie (cookie-sessions) ou url (sessions sans cookie).

Si vous avez besoin d'une session, oubliez le repos, les sessions ne sont pas pertinentes dans les conceptions basées sur REST. Si vous avez besoin d'une session pour la validation, utilisez un jeton ou autorisez par les adresses IP.

21
Nickz

Mark, si vous vérifiez le exemple de MVC nerddinner la logique est à peu près la même.

Il vous suffit de récupérer le cookie et de le définir dans la session en cours.

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

Vous devrez définir votre classe "SampleIdentity", que vous pouvez emprunter auprès de projet nerddinner .

20
JSancho

Pour résoudre le problème:

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

dans Global.asax.cs

12
Suresh Muttagi

Le dernier ne fonctionne pas maintenant, prenez celui-ci, cela a fonctionné pour moi.

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

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

10
Cruiser KID

Pour faire suite à la réponse de LachlanB, si votre ApiController ne se trouve pas dans un répertoire particulier (comme/api), vous pouvez plutôt tester la demande à l'aide de RouteTable.Routes.GetRouteData, par exemple:

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
Stumblor

J'ai eu ce même problème dans asp.net mvc, je l'ai corrigé en mettant cette méthode dans mon contrôleur api de base dont tous mes contrôleurs api héritent:

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

Ensuite, dans votre appel API, vous souhaitez accéder à la session que vous venez de faire:

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

J'ai aussi ceci dans mon fichier Global.asax.cs comme d'autres personnes l'ont posté, je ne sais pas si vous en avez toujours besoin en utilisant la méthode ci-dessus, mais ici c'est juste au cas où:

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

Vous pouvez également créer un attribut de filtre personnalisé que vous pouvez coller sur les appels d'API dont vous avez besoin pour la session. Vous pouvez ensuite utiliser session dans votre appel d'API comme vous le feriez normalement via 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;
        }
      }
    }
  }

J'espère que cela t'aides.

8
Treyphor

J'ai suivi l'approche @LachlanB et en effet, la session était disponible lorsque le cookie de session était présent sur la demande. La partie manquante est comment le cookie de session est envoyé au client la première fois?

J'ai créé un HttpModule qui non seulement active la disponibilité de HttpSessionState, mais envoie également le cookie au client lors de la création d'une nouvelle session.

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
JCallico

une chose doit être mentionnée dans la réponse de @LachlanB.

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

Si vous omettez la ligne if (IsWebApiRequest())

Si votre site est mélangé à des pages de formulaire Web, le problème de lenteur du chargement des pages s’affichera sur l’ensemble du site.

3
maxisam