Я понимаю, что сеанс и REST не идут рука об руку, но разве невозможно получить доступ к состоянию сеанса с помощью нового веб-API? HttpContext.Current.Session
всегда имеет значение null.
Доступ к сеансу с использованием веб-API ASP.NET
Ответ 1
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);
Точечное сетевое ядро
Добавьте пакет Microsoft.AspNetCore.Session NuGet, а затем выполните следующие изменения кода:
Startup.cs
Вызовите методы AddDistributedMemoryCache и AddSession для объекта services в функции 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
Проблемы с производительностью
Прочтите Симона Уивера ниже, касаясь производительности. Если вы получаете доступ к данным сеанса в проекте WebApi, это может иметь очень серьезные последствия для производительности. Я видел, что ASP.NET обеспечивает задержку в 200 мс для одновременных запросов. Это может привести к катастрофическим последствиям, если у вас много одновременных запросов.
Проблемы безопасности
Убедитесь, что вы блокируете ресурсы для каждого пользователя - аутентифицированный пользователь не должен извлекать данные из вашего WebApi, к которому у них нет доступа.
Прочитать статью Microsoft об аутентификации и авторизации в веб-интерфейсе ASP.NET - 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
Ответ 2
Вы можете получить доступ к состоянию сеанса с помощью настраиваемого 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
Ответ 3
Почему бы избежать использования сеанса в WebAPI?
Производительность, производительность, производительность!
Там очень хорошая и часто забываемая причина, почему вы вообще не должны использовать Session в WebAPI.
Способ ASP.NET, когда сеанс используется, - это сериализовать все запросы, полученные от одного клиента. Теперь я не говорю о сериализации объектов, но выполняю их в полученном порядке и ожидаю завершения каждого из них до запуска следующего. Это делается для того, чтобы избежать неприятных условий нити/гонки, если два запроса каждый раз пытаются получить доступ к сеансу одновременно.
Параллельные запросы и состояние сеанса
Доступ к состоянию сеанса ASP.NET является исключительным за сеанс, что означает, что если два разных пользователя одновременные запросы, предоставляется доступ к каждому отдельному сеансу одновременно. Однако , если два параллельных запроса сделаны для тот же сеанс (с использованием того же значения SessionID), первый запрос получает эксклюзивный доступ к информации о сеансе. Второй запрос выполняется только после завершения первого запроса. (второй сеанс также может получить доступ, если исключить исключительную блокировку информации потому что первый запрос превышает тайм-аут блокировки.) Если Значение EnableSessionState в директиве @Страница установлено в ReadOnly, a запрос для информации сеанса только для чтения не приводит к исключительная блокировка данных сеанса. Однако запросы только для чтения для сессионным данным все равно придется ждать блокировки, установленной чтением-записью запрос на очистку данных сеанса.
Итак, что это значит для Web API? Если у вас есть приложение, на котором запущено множество запросов AJAX, тогда только одно может работать одновременно. Если у вас более медленный запрос, он блокирует все остальные, пока он не будет завершен. В некоторых приложениях это может привести к очень заметной вялости производительности.
Таким образом, вы должны, вероятно, использовать контроллер MVC, если вам абсолютно нужно что-то из сеанса пользователей и избежать лишнего снижения производительности, чтобы включить его для WebApi.
Вы можете легко проверить это самостоятельно, просто поместив Thread.Sleep(5000)
в метод WebAPI и включите сеанс. Запустите 5 запросов, и они займут в общей сложности 25 секунд. Без сеанса они занимают в общей сложности чуть более 5 секунд.
(Это же рассуждение относится к SignalR).
Ответ 4
Хорошо, что вы правы, REST не имеет гражданства. Если вы используете сеанс, обработка будет выполнена с сохранением состояния, последующие запросы смогут использовать состояние (из сеанса).
Для того, чтобы сеанс был регидратирован, вам нужно предоставить ключ для связывания состояния. В обычном приложении asp.net этот ключ предоставляется с использованием файлов cookie (cookie-сессий) или параметров url (сеансы cookieless).
Если вам нужна сессия, забудьте отдохнуть, сеансы не имеют отношения к проектам на основе REST. Если вам нужна сессия для проверки, используйте маркер или авторизацию по IP-адресам.
Ответ 5
Отметьте, если вы проверите пример 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.
Ответ 6
Последний не работает сейчас, возьмите это, он сработал у меня.
в 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);
}
Ответ 7
Чтобы устранить проблему:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
в Global.asax.cs
Ответ 8
Следуя ответу 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);
}
Ответ 9
У меня была такая же проблема в 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, как обычно, через 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;
}
}
}
}
Надеюсь, что это поможет.
Ответ 10
Я следил за подходом @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 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);
}
}
Ответ 11
нужно упомянуть об ответе @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Если вы опустите строку if (IsWebApiRequest())
Весь сайт будет иметь проблему медленности загрузки страницы, если ваш сайт смешан с страницами веб-форм.
Ответ 12
Возвращаясь к основам, почему бы не сохранить его простым и сохранить значение Session в скрытом html-значении для перехода к вашему API?
контроллер
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 */}
}