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