Доступ к сеансу с помощью веб-API ASP.NET - PullRequest
254 голосов
/ 07 марта 2012

Я понимаю, что сессия и REST точно не идут рука об руку, но нельзя ли получить доступ к состоянию сеанса с помощью нового веб-API? HttpContext.Current.Session всегда равно нулю.

Ответы [ 12 ]

318 голосов
/ 09 июля 2013

MVC

Для проекта MVC внесите следующие изменения (ответ WebForms и Dot Net Core ниже):

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

}

Это решение имеет дополнительный бонус, заключающийся в том, что мы можем получить базовый URL в javascript для выполнения вызовов AJAX:

_Layout.cshtml

<body>
    @RenderBody()

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

    @RenderSection("scripts", required: false) 

и затем в наших файлах / коде Javascript мы можем выполнять наши вызовы webapi, которые могут получить доступ к сеансу:

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

WebForms

Сделайте вышеописанное, но измените функцию WebApiConfig.Register, чтобы вместо нее использовать RouteCollection:

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

И затем вызовите следующее в Application_Start:

WebApiConfig.Register(RouteTable.Routes);

Dot Net Core

Добавьте пакет Microsoft.AspNetCore.Session NuGet и внесите следующие изменения кода:

Startup.cs

Вызовите методы AddDistributedMemoryCache и AddSession для объекта служб в функции ConfigureServices:

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

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

и в функции Configure добавьте вызов UseSession :

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

SessionController.cs

В вашем контроллере добавьте оператор using вверху:

using Microsoft.AspNetCore.Http;

, а затемиспользуйте объект HttpContext.Session в вашем коде, например, так:

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

Теперь вы можете нажать:

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

, а затем переход по этому URL выведет его:

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

Здесь вы найдете больше информации о доступе к данным сеанса в ядре dot net: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state

Проблемы производительности

Прочитайте Simon Weaver'sответ ниже относительно производительности.Если вы обращаетесь к данным сеанса внутри проекта WebApi, это может иметь очень серьезные последствия для производительности - я видел, как ASP.NET применяет задержку 200 мс для одновременных запросов.Это может привести к катастрофическим последствиям, если у вас много одновременных запросов.

Проблемы безопасности

Убедитесь, что вы блокируете ресурсы для каждого пользователя - аутентифицированный пользователь не должен иметь возможность получать данные из вашего WebApi, к которым у них нет доступа.

Прочтите статью Microsoft об аутентификации и авторизации в ASP.NET Web API - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

Прочтите статью Microsoft о том, как избежать хакерских атак на межсайтовые запросы.(Короче, проверьте метод AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks

65 голосов
/ 07 декабря 2012

Вы можете получить доступ к состоянию сеанса с помощью пользовательского 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;
    }
}

Найдено здесь: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html

45 голосов
/ 28 мая 2015

Почему бы не использовать Session в WebAPI?

Производительность, производительность, производительность!

Есть очень хорошая и часто упускаемая из виду причина, по которой вам не следуетвообще использовать Session в WebAPI.

В ASP.NET, когда Session используется, сериализует все запросы, полученные от одного клиента .Сейчас я не говорю о сериализации объектов - но запускаю их в порядке поступления и жду завершения каждого из них, прежде чем запускать следующий.Это сделано для того, чтобы избежать неприятных условий потока / гонки, если два запроса каждый пытаются получить доступ к сеансу одновременно.

Параллельные запросы и состояние сеанса

Доступ к ASP.NETсостояние сеанса является исключительным для каждого сеанса, что означает, что если два разных пользователя делают параллельные запросы, доступ к каждому отдельному сеансу предоставляется одновременно.Однако , если два одновременных запроса сделаны для одного и того же сеанса (с использованием одного и того же значения SessionID), первый запрос получает эксклюзивный доступ к информации сеанса.Второй запрос выполняется только после того, как первый запрос завершен. (Второй сеанс также может получить доступ, если исключительная блокировка информации освобождается, поскольку первый запрос превышает время ожидания блокировки.) Если значение EnableSessionState вДиректива @ Page имеет значение ReadOnly, запрос информации о сеансе только для чтения не приводит к исключительной блокировке данных сеанса.Однако запросы только для чтения для данных сеанса, возможно, все еще должны ждать блокировки, установленной запросом на чтение и запись, для очистки данных сеанса.

Так что это значит для Web API?Если у вас есть приложение, выполняющее много запросов AJAX, тогда только ОДИН сможет работать одновременно.Если у вас более медленный запрос, он будет блокировать всех остальных от этого клиента, пока он не будет завершен.В некоторых приложениях это может привести к очень заметной вялой производительности.

Поэтому вам, вероятно, следует использовать контроллер MVC, если вам абсолютно необходимо что-то из пользовательского сеанса и избежать ненужного снижения производительности при включении его для WebApi.

Вы можете легко проверить это сами, просто вставив Thread.Sleep(5000) в метод WebAPI и включив Session.Выполните 5 запросов к нему, и на их выполнение уйдет 25 секунд.Без сессии они в общей сложности займут чуть более 5 секунд.

(То же самое относится и к SignalR).

21 голосов
/ 07 марта 2012

Ну, вы правы, REST без гражданства Если вы используете сеанс, обработка станет с состоянием, последующие запросы смогут использовать состояние (из сеанса).

Для повторной гидратации сессии вам необходимо предоставить ключ, чтобы связать состояние. В обычном приложении asp.net этот ключ предоставляется с помощью cookie (cookie-сессий) или параметра url (сессий без cookie).

Если вам нужен сеанс, забудьте про отдых, сеансы не имеют отношения к проектам на основе REST. Если вам нужен сеанс для проверки, используйте токен или авторизуйтесь по IP-адресам.

20 голосов
/ 07 марта 2012

Отметьте, если вы проверите пример nerddinner MVC , логика будет почти такой же.

Вам нужно только извлечь cookie и установить его в текущем сеансе.

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

Вам необходимо определить свой класс "SampleIdentity", который вы можете позаимствовать из проекта nerddinner .

12 голосов
/ 25 июля 2015

Чтобы исправить проблему:

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

в Global.asax.cs

10 голосов
/ 03 февраля 2013

Последний не работает сейчас, возьми этот, он работал для меня.

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

здесь четвертое: http://forums.asp.net/t/1773026.aspx/1

8 голосов
/ 04 декабря 2013

У меня была такая же проблема в asp.net mvc, я исправил ее, поместив этот метод в свой базовый контроллер API, от которого наследуются все мои контроллеры API:

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

Затем в вашем вызове API, который выхотите получить доступ к сеансу, который вы только что сделали:

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

У меня также есть это в моем файле Global.asax.cs, как и другие люди, не уверен, если вам все еще нужно, используя метод, описанный выше, но здесьэто на всякий случай:

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

Вы также можете просто создать собственный атрибут фильтра, который вы можете прикрепить к вызовам API, для которого вам нужен сеанс, тогда вы можете использовать сеанс в вызове API, как обычно.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;
        }
      }
    }
  }

Надеюсь, это поможет.

8 голосов
/ 17 сентября 2013

Исходя из ответа LachlanB, если ваш ApiController не находится в определенном каталоге (например, / api), вы можете вместо этого протестировать запрос, используя RouteTable.Routes.GetRouteData, например:

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);
    }
6 голосов
/ 30 апреля 2014

Я следовал подходу @LachlanB, и действительно, сеанс был доступен, когда в запросе присутствовал файл cookie сеанса.Недостающая часть - как файл cookie сеанса отправляется клиенту в первый раз?

Я создал модуль HttpModule, который не только включает доступность HttpSessionState, но и отправляет файл cookie клиенту при создании нового сеанса.

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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...