Я усвідомлюю, що сеанс і REST точно не йдуть на руку, але чи не можна отримати доступ до стану сесії за допомогою нового веб-API? HttpContext.Current.Session
завжди недійсний.
Я усвідомлюю, що сеанс і REST точно не йдуть на руку, але чи не можна отримати доступ до стану сесії за допомогою нового веб-API? HttpContext.Current.Session
завжди недійсний.
Відповіді:
MVC
Для проекту 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);
Dot Net Core
Додайте пакет Microsoft.AspNetCore.Session NuGet і введіть такі зміни коду:
Виклик методів AddDistributedMemoryCache та AddSession на об’єкті послуг в рамках функції ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
і у функції Налаштування додайте виклик у UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
У своєму контролері додайте використовувальний оператор вгорі:
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
Тут можна отримати більше інформації про доступ до даних сеансу в точці чистого ядра: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Побоювання щодо продуктивності
Прочитайте відповідь Саймона Вівер нижче щодо продуктивності. Якщо ви отримуєте доступ до даних сеансу всередині проекту WebApi, це може мати дуже серйозні наслідки для продуктивності - я бачив, як ASP.NET застосовує затримку в 200 мс для одночасних запитів. Це може скластись і стати згубним, якщо у вас буде багато одночасних запитів.
Проблеми безпеки
Переконайтеся, що ви блокуєте ресурси на кожного користувача - автентифікований користувач не повинен мати можливість отримувати дані з вашого WebApi, до яких вони не мають доступу.
Прочитайте статтю Microsoft про автентифікацію та авторизацію у веб-API ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Прочитайте статтю Майкрософт про уникнення хакерських атак між сайтом Request Forgery. (Коротше, ознайомтеся з методом 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
Продуктивність, продуктивність, продуктивність!
Є дуже хороша і часто недооцінена причина, чому ви взагалі не повинні використовувати сесію в WebAPI.
Те, як ASP.NET працює під час використання сесії - це серіалізація всіх запитів, отриманих від одного клієнта . Зараз я не говорю про серіалізацію об'єктів - але запускаю їх у отриманому порядку та чекаю, коли кожне завершиться перед запуском наступного. Це дозволяє уникнути неприємних умов потоку / гонки, якщо два запити намагаються отримати доступ до сесії одночасно.
Одночасні запити та стан сесії
Доступ до стану сеансу ASP.NET є виключним на сеанс, що означає, що якщо два різні користувачі роблять одночасні запити, доступ до кожного окремого сеансу надається одночасно. Однак якщо два одночасні запити зроблені для одного сеансу (використовуючи одне і те ж значення SessionID), перший запит отримує ексклюзивний доступ до інформації сеансу. Другий запит виконується лише після завершення першого запиту.(Другий сеанс також може отримати доступ, якщо ексклюзивний блокування інформації звільнено, оскільки перший запит перевищує тайм-аут блокування.) Якщо значення EnableSessionState в директиві @ Page встановлено на ReadOnly, запит на режим лише для читання інформація про сеанс не призводить до виключного блокування даних сеансу. Однак запити даних про сеанс лише для читання, можливо, доведеться чекати блокування, встановленого запитом читання-запису, щоб очистити дані сеансу.
То що це означає для веб-API? Якщо у вас є програма, що працює з багатьма запитами AJAX, тоді одночасно може працювати лише ONE. Якщо у вас є повільніший запит, він заблокує всіх інших від цього клієнта до його завершення. У деяких програмах це може призвести до дуже помітної швидкості роботи.
Тому, ймовірно, ви повинні використовувати контролер MVC, якщо вам абсолютно потрібне щось із сеансу роботи з користувачами, і уникати необхідного покарання ефективності включення його для WebApi.
Ви можете легко перевірити це на собі, просто Thread.Sleep(5000)
ввівши метод WebAPI і включивши сеанс. Запустіть до нього 5 запитів, і на їх виконання знадобиться всього 25 секунд. Без сесії вони займуть загалом трохи більше 5 секунд.
(Це ж міркування стосується SignalR).
Ну ви праві, REST без громадянства. Якщо ви використовуєте сеанс, обробка стане справжньою, наступні запити зможуть використовувати стан (з сеансу).
Для того, щоб сеанс був зволожений, вам потрібно надати ключ, щоб пов’язати стан. У звичайній програмі asp.net цей ключ надається за допомогою файлу cookie (cookie-сесії) або URL-адреси (без cookie сесій).
Якщо вам потрібен сеанс, забудьте відпочинок, сесії не мають значення в проектах на основі REST. Якщо вам потрібен сеанс для перевірки, тоді використовуйте маркер або авторизуйте IP-адреси.
Зауважте , якщо ви перевірите приклад MVC nerddinner, логіка майже однакова.
Вам потрібно лише завантажити файл 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 .
Щоб вирішити проблему:
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);
}
четверте тут: http://forums.asp.net/t/1773026.aspx/1
Виходячи з відповіді 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 Session надсилається клієнту вперше?
Я створив 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);
}
}
одне потрібно згадати у відповіді @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Якщо опустити рядок if (IsWebApiRequest())
Весь сайт матиме проблему повільності завантаження сторінок, якщо ваш сайт змішаний зі сторінками веб-форм.
Так, сеанс не йде на руку з API відпочинку, а також ми повинні уникати цієї практики. Але відповідно до вимог нам потрібно підтримувати сеанс якось таким, щоб у кожному запиті сервер клієнта міг обмінюватися або підтримувати стан або дані. Отже, найкращий спосіб досягти цього, не порушуючи протоколи REST, - це комунікація через маркер, як JWT.
Повертаючись до основ, чому б не зробити це простим і зберегти значення 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
$ (документ) .ready (функція () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
наApiController
трюк (або.ReadOnly
де це доречно).