Dostęp do sesji z pomocą ASP.NET web API

rozumiem, że sesja i wakacje nie do końca idą w parze, ale nie można uzyskać dostęp do stanu sesji za pomocą nowego interfejsu API sieci web? HttpContext.Current.Session zawsze równa null.


MVC

dla projektu MVC, wprowadź następujące zmiany (WebForms i Dot Net Core odpowiedź 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 }
        );
    }
}

globalne.асакс.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, który możemy uzyskać adres URL w javascript do wykonywania połączeń AJAX:

plik _Layout.cshtml

<body>
    @RenderBody()

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

    @RenderSection("scripts", required: false) 

a potem w naszym JavaScript-pliki/kod możemy zrobić z naszym web API-połączeń który może uzyskać dostęp do sesji:

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

WebForms

zrób wyżej, ale zmień WebApiConfig.Zarejestruj funkcję, aby wziąć RouteCollection zamiast:

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

i następnie wywołać W następujący Application_Start:

WebApiConfig.Register(RouteTable.Routes);

Punkt Net Core

dodaj Microsoft.AspNetCore.Sesja pakietu NuGet, a następnie dodać następujący kod zmiany:

Autostart.cs

rozmowy AddDistributedMemoryCache a AddSession metody na obiekcie services w ramach funkcji ConfigureServices:

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

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

i w opcje Configure dodaj wyzwanie UseSession:

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

SessionController.cs

w swój kontroler, dodaj operator using w górnej części:

using Microsoft.AspNetCore.Http;

a następnie użyć HttpContext.Obiekt sesji w kodzie wygląda to 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

i następnie klikając na ten adres URL, wyciągnie go:

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

dużo więcej informacji na temat uzyskiwania dostępu do danych sesji w dot net core tutaj:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state

Problemy Z Wydajnością

przeczytaj odpowiedź Simona Уивера poniżej względem przedstawienie. Jeśli masz dostęp do danych sesji wewnątrz projektu do WebApi, to może mieć bardzo poważne konsekwencje dla wydajności widziałem ASP.NET zastosowanie opóźnienie 200 ms dla jednoczesnych żądań. To może powstać i stać się katastrofalne, jeśli masz wiele jednoczesnych żądań.


Problemy Bezpieczeństwa

upewnij się, że blokujecie zasoby dla każdego użytkownika-uwierzytelniony użytkownik nie powinien mieć możliwość otrzymywać dane z WebApi, które nie są mieć dostęp.

przeczytaj artykuł Microsoft dotyczących uwierzytelniania i autoryzacji w ASP.NET Web APIhttps://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

przeczytaj artykuł Microsoft o unikaniu krzyżowych zapytań na fałszywe ataki hakerów. (Krótko mówiąc, sprawdź AntiForgery.Metoda validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks


możesz uzyskać dostęp do stanu sesji przy użyciu niestandardowego 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


dlaczego nie skorzystać z sesji do WebAPI?

wydajność, produktywność, produktywność!

jest bardzo dobra i często упускаемая powodem, dla którego nie powinno się używać sesję do WebAPI w ogóle.

ścieżka ASP.NET działa, Gdy sesja jest używany, to serializować wszystkie wnioski otrzymane od jednego klienta. Teraz nie mówię o serializacji obiektów - ale uruchomiłem je w otrzymanym porządku i czekam na zakończenie każdego przed uruchomieniem następnego. To musi uniknąć nieprzyjemnych warunków przepływu / wyścigi, jeśli dwa żądania każdy próbuje uzyskać dostęp do sesji jednocześnie.

równoległe wnioski i stan sesji

dostęp do ASP.NET stan sesji jest wyłącznie dla każdej sesji, co oznacza, że jeśli dwóch użytkowników wnioski jednocześnie, dostęp do każdej poszczególnej sesji dostępny jednocześnie. Jednak,jeśli dwóch jednoczesnych zapytań do sam sesja (z wykorzystaniem tego samego wartości SessionID), pierwsze żądanie otrzymuje dostęp do informacji o sesji. Drugi wniosek odbywa się dopiero po zakończeniu pierwszego zapytania. (druga sesja można również uzyskać dostęp, jeśli wyjątkowa blokada informacji zwolniona bo pierwsze żądanie przekracza limit czasu blokady.) Jeśli Wartość EnableSessionState w dyrektywie @ Page i tak ReadOnly, a żądanie informacji o sesji tylko do odczytu nie prowadzi do blokada wyłączna danych sesji. Jednak odpowiadać tylko do odczytu dane sesji mogą nadal czekać blokady ustalonej metodą odczytu i zapisu żądanie czyszczenia danych sesji.

Więc co to oznacza dla web API? Jeśli masz aplikację, która wykonuje wiele zapytań AJAX, to tylko jeden może pracować jednocześnie. Jeśli masz więcej niż powolny wniosek, że zablokuje wszystkie inne z tego klienta, dopóki nie zostanie zakończony. W niektórych aplikacjach może to spowodować bardzo wyraźnie kiepski występ.

dlatego, prawdopodobnie, należy użyć kontrolera MVC, jeśli całkowicie trzeba coś z sesji użytkowników i uniknąć niepotrzebnego grzywny za wydajność, włączając go do WebApi.

możesz łatwo sprawdzić to sami, po prostu umieszczając Thread.Sleep(5000) w metodzie WebAPI i włączyć sesję. Wykonać 5 zapytań do niego, i będą one miały zaledwie 25 sekund. Bez sesji zajmą w sumie nieco ponad 5 sekund.

(ta to samo odnosi się i do SignalR).


No, masz rację, wakacje bez obywatelstwa. Jeśli używasz sesję, przetwarzanie stanie statusu, kolejne wnioski będą mogli korzystać stan (z sesji).

poza tym, na sesji zostaną przywrócone, należy umieścić klucz, aby połączyć państwo. W normalnym asp.net aplikacja, która klucz jest dostarczany za pomocą cookie (cookie sesji) lub parametr adresu url (cookieless sesje).

Jeśli potrzebujesz sesja zapomnieć odpoczynek, rozmowy nie mają znaczenia w REST na podstawie design. Jeśli potrzebujesz sesję w celu sprawdzenia, użyj znacznika lub авторизируйте na podstawie adresów IP.


zaznacz, jeśli sprawdzasz przykład nerddinner w MVC logika jest taka sama.

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

globalne.асакс.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

trzeba będzie określić swoją klasę" SampleIdentity", który można pożyczyć z projekt nerddinner.


ostatni teraz nie działa, weź ten, pracował dla mnie.

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

globalne.эйсакс

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

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

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


rozwiązać problem:

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

w skali globalnej.асакс.cs


zgodnie odpowiedzi LachlanB, jeśli twój ApiController nie znajduje się w określonym katalogu (np. /api), można zamiast tego sprawdzić żądanie za pomocą RouteTable.Trasa.GetRouteData, np.:

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

miałem ten sam problem w asp.net mvc, poprawiłem to, umieszczając tę metodę w mój podstawowy kontroler interfejsu api, który wszystkie moje sterowniki api dziedziczą od:

    /// <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 swoim wywołania api, który chcesz uzyskać dostęp do sesji, po prostu robisz:

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

u mnie również jest to w moim globalnym.асакс.cs-plik, jak i inni ludzie, opublikował, nie jestem pewien, że wciąż potrzebujesz, stosując metodę wyżej, ale 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żesz też po prostu zrobić porządek atrybut filtra, który można trzymać w wywołaniach api, które są potrzebne, a następnie można użyć sesja wywołanie api, jak zwykle, przez HttpContext.Bieżący.Sesja["Wartosc"]:

  /// <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 pomaga.


ja po podejściu @LachlanB, i naprawdę sesji był dostępny, gdy plik cookie sesji był obecny na żądanie. Brakująca część-to jest to, jak plik cookie sesji jest wysyłany do klienta po raz pierwszy?

założyłem HttpModule, który nie tylko pozwala na dostępność HttpSessionState, ale i wysyła cookie klienta podczas tworzenia 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);
    }
}

jedną rzeczą, należy wspomnieć o tym w odpowiedzi @LachlanB.

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

Jeśli można zanurzyć pasek if (IsWebApiRequest())

cała strona będzie mieć problem powolnego ładowania strony, jeśli twoja strona miesza się ze stronami sieci web form.


wracając do podstaw, dlaczego nie zachować to proste i zapisać wartość sesji w ukrytym znaczeniu html wysłać swoje API?

sterownik

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

$(document).ready (function () {

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

    alert(sessionValue);

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

}