Dostęp do sesji za pomocą ASP.NET Web API

Zdaję sobie sprawę, że sesja i reszta nie idą w parze, ale czy nie jest możliwe uzyskanie dostępu do stanu sesji przy użyciu nowego Web API? HttpContext.Current.Session jest zawsze null.

Author: Mark, 2012-03-07

12 answers

MVC

Dla projektu MVC wprowadź następujące zmiany (WebForms i odpowiedź Dot Net Core poniżej):

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

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

}

To rozwiązanie ma dodatkowy bonus, że możemy pobrać podstawowy adres URL w javascript do wykonywania połączeń AJAX:

_Layout.cshtml

<body>
    @RenderBody()

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

    @RenderSection("scripts", required: false) 

A następnie w naszych plikach/kodzie Javascript możemy wykonać nasze wywołania webapi, które mogą uzyskać dostęp do sesja:

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

WebForms

Wykonaj powyższe, ale zmień WebApiConfig.Funkcja Register, aby zamiast tego przyjąć RouteCollection:

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

A następnie wywołaj następujące w Application_Start:

WebApiConfig.Register(RouteTable.Routes);

Dot Net Core

Dodaj Microsoft.AspNetCore.Session NuGet pakiet, a następnie dokonać następujących zmian w kodzie:

Startup.cs

Call the AddDistributedMemoryCache i metody AddSession w obiekcie services w funkcji ConfigureServices:

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

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

I w funkcji Configure dodaj wywołanie do UseSession :

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

SessionController.cs

Wewnątrz kontrolera dodaj instrukcję using u góry:

using Microsoft.AspNetCore.Http;

A następnie użyj HttpContext.Obiekt sesji w Twoim kodzie tak:

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

Teraz powinieneś być w stanie trafić:

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

A następnie przejście do tego adresu URL pociągnie go out:

http://localhost:1234/api/session/get
Więcej informacji o dostępie do danych sesji w dot net core znajdziesz tutaj: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state

Problemy Wydajności

Przeczytaj odpowiedź Simona Weavera poniżej dotyczącą wydajności. Jeśli uzyskujesz dostęp do danych sesji wewnątrz projektu WebApi, może to mieć bardzo poważne konsekwencje wydajności - widziałem ASP.NET Wymuś opóźnienie 200ms dla jednoczesnych żądań. To może się sumować i stać się katastrofalne, jeśli masz wiele jednoczesnych żądań.


Problemy Bezpieczeństwa

Upewnij się, że blokujesz zasoby na użytkownika - uwierzytelniony użytkownik nie powinien być w stanie odzyskać danych z Twojej WebApi, do których nie ma dostępu.

Przeczytaj artykuł Microsoftu o uwierzytelnianiu i autoryzacji w ASP.NET Web API - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

Czytaj Artykuł Microsoftu o unikaniu ataków hakerskich typu Cross-Site Request Forgery. (W skrócie, sprawdź Antyforgery.Metoda walidacji) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks{[70]

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-19 01:01:43

Możesz uzyskać dostęp do stanu sesji za pomocą niestandardowego programu obsługi 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;
    }
}

Znaleziono tutaj: 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-21 11:57:28

Dlaczego unikać używania sesji w WebAPI?

Wydajność, wydajność, wydajność!

Jest bardzo dobry i często pomijany powód, dla którego w ogóle nie powinieneś używać sesji w WebAPI.

The way ASP.NET działa, gdy sesja jest w użyciu, Aby serializować wszystkie żądania otrzymane od jednego klienta . Teraz nie mówię o serializacji obiektów - ale uruchamianie ich w otrzymanej kolejności i oczekiwanie na zakończenie każdego z nich przed uruchomieniem następnego. To jest aby uniknąć nieprzyjemnych warunków wątku / wyścigu, jeśli po dwa żądania każda próba dostępu do sesji jednocześnie.

Współbieżne żądania i stan sesji

Dostęp do ASP.NET stan sesji jest wyłączne na sesję, co oznacza, że jeśli dwóch różnych użytkowników jednoczesnych żądań, dostęp do każdej osobnej sesji jest przyznawany / align = "left" / Jednakże Jeśli dwa równoległe wnioski są składane dla tej samej sesji (używając tej samej wartości SessionID), pierwsza wniosek otrzymuje wyłączny dostęp do informacji o sesji. Drugi wniosek wykonuje dopiero po zakończeniu pierwszego żądania. (druga sesja może również uzyskać dostęp, jeśli wyłączna blokada informacji jest zwolniona ponieważ pierwsze żądanie przekracza czas blokady.) Jeśli EnableSessionState wartość w dyrektywie @ Page jest ustawiona na ReadOnly, a żądanie informacji o sesji tylko do odczytu nie powoduje wyłączna blokada danych sesji. Jednak tylko do odczytu wnioski o dane sesji mogą nadal czekać na blokadę ustawioną przez odczyt-zapis Prośba o wyczyszczenie danych sesji.

Co to oznacza dla Web API? Jeśli masz aplikację z wieloma żądaniami AJAX, tylko jedna będzie mogła działać na raz. Jeśli masz wolniejsze żądanie, zablokuje ono wszystkie inne od tego klienta, dopóki nie zostanie ukończone. W niektórych zastosowaniach może to prowadzić do bardzo zauważalnie powolnej wydajności.

Więc prawdopodobnie powinieneś użyć MVC controller jeśli absolutnie potrzebujesz czegoś z sesji użytkowników i unikniesz kary za unncesessary performance polegającej na włączeniu go dla WebApi.

Możesz łatwo przetestować to samodzielnie, po prostu umieszczając Thread.Sleep(5000) w metodzie WebAPI i włącz sesję. Uruchom 5 żądań do niego, a ich wykonanie zajmie w sumie 25 sekund. Bez sesji zajmą w sumie nieco ponad 5 sekund.

(to samo rozumowanie dotyczy 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/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-13 19:56:01

No masz rację, reszta jest bezpaństwowa. Jeśli używasz sesji, przetwarzanie stanie się stanowe, kolejne żądania będą mogły używać stanu (z sesji).

W celu ponownego nawodnienia sesji, musisz podać klucz do powiązania stanu. W normalnym asp.net aplikacja klucz jest dostarczany za pomocą pliku cookie (cookie-sessions) lub parametru url (cookieless sessions).

Jeśli potrzebujesz sesji forget rest, sesje są nieistotne w projektach opartych na REST. Jeśli potrzebujesz sesji do walidacji, a następnie użyj tokena lub Autoryzuj przez adresy 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 23:32:03

Zaznacz, jeśli sprawdzisz przykład nerddinner MVC logika jest prawie taka sama.

Wystarczy pobrać plik cookie i ustawić go w bieżącej sesji.

Globalny.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

Będziesz musiał zdefiniować klasę "SampleIdentity" , którą możesz wypożyczyć z projektu 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-27 15:55:37

Ostatni nie działa, weź ten, u mnie zadziałał.

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

Globalny.asax

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

private static bool IsWebApiRequest()
{
  return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(_WebApiExecutionPath);
}

Fournd here: 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-03 01:52:24

Aby Rozwiązać Problem:

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

In 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-26 09:34:59

Idąc dalej od odpowiedzi Lachlanba, jeśli twój kontroler ApiController nie znajduje się w określonym katalogu (np. /api), możesz zamiast tego przetestować żądanie za pomocą RouteTable.Trasy.GetRouteData, na przykład:

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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-17 16:19:30

Miałem ten sam problem w asp.net mvc, naprawiłem to umieszczając tę metodę w moim podstawowym kontrolerze api, od którego wszystkie kontrolery API dziedziczą:

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

Następnie w wywołaniu api, które chcesz uzyskać dostęp do sesji, po prostu wykonaj:

HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
Mam to również w moim Global.asax.plik cs jak inni ludzie napisali, Nie wiem czy nadal jest potrzebny przy użyciu powyższej metody, ale tutaj jest na wszelki wypadek:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
  if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
  {
    HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
  }
}

Można również po prostu zrobić własny atrybut filtra, który można trzymaj się wywołań api, które potrzebujesz sesji, a następnie możesz użyć sesji w wywołaniu api, jak zwykle za pośrednictwem HttpContext.Aktualne.Sesja ["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;
        }
      }
    }
  }
Mam nadzieję, że to pomoże.
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-04 21:26:53

Zastosowałem podejście @ LachlanB i rzeczywiście sesja była dostępna, gdy plik cookie sesji był obecny na żądaniu. Brakującą częścią jest sposób, w jaki plik cookie sesji jest wysyłany do klienta po raz pierwszy?

Utworzyłem HttpModule, który nie tylko włącza dostępność HttpSessionState, ale także wysyła plik cookie do klienta po utworzeniu nowej sesji.

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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-29 20:48:05

Jedna rzecz trzeba wspomnieć w odpowiedzi @LachlanB.

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

Jeśli pominiesz linię if (IsWebApiRequest())

Cała witryna będzie miała problem z powolnym ładowaniem strony, jeśli Twoja witryna jest mieszana ze stronami formularza internetowego.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-31 14:30:04

Wracając do podstaw dlaczego nie zachować prostoty i zapisać wartość sesji w ukrytej wartości html, aby przekazać ją do API?

Controller

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

$(dokument).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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-23 15:35:12