Ich stelle fest, dass Sitzung und REST nicht genau Hand in Hand gehen, aber ist es nicht möglich, über die neue Web-API auf den Sitzungsstatus zuzugreifen? HttpContext.Current.Session
ist immer null.
Ich stelle fest, dass Sitzung und REST nicht genau Hand in Hand gehen, aber ist es nicht möglich, über die neue Web-API auf den Sitzungsstatus zuzugreifen? HttpContext.Current.Session
ist immer null.
Antworten:
MVC
Nehmen Sie für ein MVC-Projekt die folgenden Änderungen vor (Antwort von WebForms und Dot Net Core weiter unten):
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);
}
}
Diese Lösung hat den zusätzlichen Vorteil, dass wir die Basis-URL in Javascript für die AJAX-Aufrufe abrufen können:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
und dann können wir in unseren Javascript-Dateien / Code unsere Webapi-Aufrufe tätigen, die auf die Sitzung zugreifen können:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
Führen Sie die obigen Schritte aus, ändern Sie jedoch die Funktion WebApiConfig.Register, um stattdessen eine RouteCollection zu verwenden:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Rufen Sie dann in Application_Start Folgendes auf:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
Fügen Sie das Microsoft.AspNetCore.Session NuGet-Paket hinzu und nehmen Sie die folgenden Codeänderungen vor:
Rufen Sie die Methoden AddDistributedMemoryCache und AddSession für das Services-Objekt in der Funktion ConfigureServices auf:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
und fügen Sie in der Funktion Konfigurieren einen Aufruf von UseSession hinzu :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
Fügen Sie in Ihrem Controller oben eine using-Anweisung hinzu:
using Microsoft.AspNetCore.Http;
und verwenden Sie dann das HttpContext.Session-Objekt in Ihrem Code wie folgt:
[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);
}
Sie sollten jetzt in der Lage sein zu schlagen:
http://localhost:1234/api/session/set/thisissomedata
Wenn Sie dann zu dieser URL gehen, wird Folgendes angezeigt:
http://localhost:1234/api/session/get
Weitere Informationen zum Zugriff auf Sitzungsdaten innerhalb des dot net core finden Sie hier: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Leistungsprobleme
Lesen Sie unten die Antwort von Simon Weaver zur Leistung. Wenn Sie in einem WebApi-Projekt auf Sitzungsdaten zugreifen, kann dies schwerwiegende Auswirkungen auf die Leistung haben. Ich habe gesehen, dass ASP.NET eine Verzögerung von 200 ms für gleichzeitige Anforderungen erzwingt. Dies kann sich summieren und katastrophal werden, wenn Sie viele gleichzeitige Anfragen haben.
Sicherheitsbedenken
Stellen Sie sicher, dass Sie Ressourcen pro Benutzer sperren. Ein authentifizierter Benutzer sollte keine Daten von Ihrem WebApi abrufen können, auf die er keinen Zugriff hat.
Lesen Sie den Artikel von Microsoft zur Authentifizierung und Autorisierung in der ASP.NET-Web-API - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Lesen Sie den Artikel von Microsoft zur Vermeidung von Hackangriffen auf Cross-Site Request Forgery. (Kurz gesagt, sehen Sie sich die AntiForgery.Validate-Methode an.) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
Sie können mit einem benutzerdefinierten RouteHandler auf den Sitzungsstatus zugreifen.
// 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;
}
}
Hier zu finden: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Leistung, Leistung, Leistung!
Es gibt einen sehr guten und oft übersehenen Grund, warum Sie Session in WebAPI überhaupt nicht verwenden sollten.
ASP.NET funktioniert bei Verwendung der Sitzung, indem alle von einem einzelnen Client empfangenen Anforderungen serialisiert werden . Jetzt spreche ich nicht über die Objekt-Serialisierung, sondern über das Ausführen in der empfangenen Reihenfolge und das Warten auf den Abschluss, bevor die nächste ausgeführt wird. Dies dient dazu, unangenehme Thread- / Race-Bedingungen zu vermeiden, wenn jeweils zwei Anforderungen versuchen, gleichzeitig auf die Sitzung zuzugreifen.
Gleichzeitige Anforderungen und Sitzungsstatus
Der Zugriff auf den ASP.NET-Sitzungsstatus ist pro Sitzung exklusiv. Wenn also zwei verschiedene Benutzer gleichzeitig Anforderungen stellen, wird der Zugriff auf jede einzelne Sitzung gleichzeitig gewährt. Allerdings , wenn zwei gleichzeitige Anforderungen für die gleiche Sitzung gemacht werden (durch den gleichen SessionID - Wert verwendet wird ), die erste Anforderung erhält exklusiven Zugriff auf die Sitzungsinformationen. Die zweite Anforderung wird erst ausgeführt, nachdem die erste Anforderung abgeschlossen wurde.(Die zweite Sitzung kann auch Zugriff erhalten, wenn die exklusive Sperre für die Informationen aufgehoben wird, da die erste Anforderung das Zeitlimit für die Sperre überschreitet.) Wenn der Wert EnableSessionState in der @ Page-Direktive auf ReadOnly festgelegt ist, wird eine Anforderung für das schreibgeschützte Element festgelegt Sitzungsinformationen führen nicht zu einer exklusiven Sperre der Sitzungsdaten. Schreibgeschützte Anforderungen für Sitzungsdaten müssen jedoch möglicherweise noch auf eine Sperre warten, die durch eine Lese- / Schreibanforderung zum Löschen von Sitzungsdaten festgelegt wurde.
Was bedeutet das für die Web-API? Wenn in einer Anwendung viele AJAX-Anforderungen ausgeführt werden, kann jeweils nur EINE ausgeführt werden. Wenn Sie eine langsamere Anforderung haben, werden alle anderen von diesem Client blockiert, bis sie abgeschlossen ist. In einigen Anwendungen kann dies zu einer merklich schleppenden Leistung führen.
Daher sollten Sie wahrscheinlich einen MVC-Controller verwenden, wenn Sie unbedingt etwas aus der Benutzersitzung benötigen, und den unnötigen Leistungsverlust vermeiden, wenn Sie ihn für WebApi aktivieren.
Sie können dies ganz einfach selbst testen, indem Sie einfach Thread.Sleep(5000)
eine WebAPI-Methode eingeben und die Sitzung aktivieren. Führen Sie 5 Anfragen aus, und es dauert insgesamt 25 Sekunden, bis sie abgeschlossen sind. Ohne Sitzung dauern sie insgesamt etwas mehr als 5 Sekunden.
(Dieselbe Argumentation gilt für SignalR).
Du hast recht, REST ist staatenlos. Wenn Sie eine Sitzung verwenden, wird die Verarbeitung statusbehaftet, nachfolgende Anforderungen können den Status (aus einer Sitzung) verwenden.
Damit eine Sitzung rehydriert werden kann, müssen Sie einen Schlüssel angeben, um den Status zuzuordnen. In einer normalen asp.net-Anwendung wird dieser Schlüssel mithilfe eines Cookies (Cookie-Sessions) oder eines URL-Parameters (Cookieless-Sessions) bereitgestellt.
Wenn Sie eine Sitzung benötigen, vergessen Sie die Ruhezeiten. Sitzungen sind in REST-basierten Designs irrelevant. Wenn Sie eine Sitzung zur Validierung benötigen, verwenden Sie ein Token oder autorisieren Sie nach IP-Adressen.
Mark, wenn Sie das nerddinner MVC-Beispiel überprüfen, ist die Logik ziemlich gleich.
Sie müssen nur das Cookie abrufen und in der aktuellen Sitzung festlegen.
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
Sie müssen Ihre "SampleIdentity" -Klasse definieren, die Sie aus dem nerddinner-Projekt ausleihen können .
So beheben Sie das Problem:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
in Global.asax.cs
Letzteres funktioniert jetzt nicht, nimm dieses, es hat bei mir funktioniert.
in WebApiConfig.cs bei 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);
}
Viertens hier: http://forums.asp.net/t/1773026.aspx/1
Wenn sich Ihr ApiController nach der Antwort von LachlanB nicht in einem bestimmten Verzeichnis befindet (wie / api), können Sie die Anforderung stattdessen mit RouteTable.Routes.GetRouteData testen, zum Beispiel:
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);
}
Ich hatte das gleiche Problem in asp.net mvc. Ich habe es behoben, indem ich diese Methode in meinen Basis-API-Controller eingefügt habe, von dem alle meine API-Controller erben:
/// <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;
}
Dann in Ihrem API-Aufruf, dass Sie auf die Sitzung zugreifen möchten, die Sie gerade tun:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
Ich habe dies auch in meiner Global.asax.cs-Datei, wie andere Leute es gepostet haben. Ich bin mir nicht sicher, ob Sie es mit der oben beschriebenen Methode noch benötigen, aber hier ist es nur für den Fall:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Sie können auch einfach ein benutzerdefiniertes Filterattribut erstellen, mit dem Sie Ihre API-Aufrufe festhalten können, die Sie benötigen. Anschließend können Sie die Sitzung in Ihrem API-Aufruf verwenden, wie Sie es normalerweise über HttpContext.Current.Session ["SomeValue"] tun würden:
/// <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;
}
}
}
}
Hoffe das hilft.
Ich folgte dem @ LachlanB-Ansatz und tatsächlich war die Sitzung verfügbar, als das Sitzungscookie auf der Anfrage vorhanden war. Der fehlende Teil ist, wie das Sitzungscookie zum ersten Mal an den Client gesendet wird.
Ich habe ein HttpModule erstellt, das nicht nur die Verfügbarkeit von HttpSessionState aktiviert, sondern auch das Cookie an den Client sendet, wenn eine neue Sitzung erstellt wird.
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);
}
}
Eines muss in der Antwort von @LachlanB erwähnt werden.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Wenn Sie die Zeile weglassen if (IsWebApiRequest())
Auf der gesamten Website tritt ein Problem mit der Langsamkeit beim Laden von Seiten auf, wenn Ihre Website mit Webformularseiten gemischt wird.
Ja, die Sitzung geht nicht mit der Rest-API einher, und wir sollten diese Vorgehensweise auch vermeiden. Aber gemäß den Anforderungen müssen wir die Sitzung irgendwie so aufrechterhalten, dass der Client-Server bei jeder Anfrage den Status oder die Daten austauschen oder verwalten kann. Der beste Weg, dies zu erreichen, ohne die REST-Protokolle zu beschädigen, ist die Kommunikation über ein Token wie JWT.
Zurück zu den Grundlagen, warum nicht einfach halten und den Sitzungswert in einem versteckten HTML-Wert speichern, um ihn an Ihre API zu übergeben?
Regler
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
$ (Dokument) .ready (Funktion () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}}
[SessionState(SessionStateBehavior.Required)]
auf demApiController
macht den Trick (oder.ReadOnly
wo angebracht).