Mi rendo conto che la sessione e REST non vanno esattamente di pari passo, ma non è possibile accedere allo stato della sessione utilizzando la nuova API Web? HttpContext.Current.Session
è sempre nullo.
Mi rendo conto che la sessione e REST non vanno esattamente di pari passo, ma non è possibile accedere allo stato della sessione utilizzando la nuova API Web? HttpContext.Current.Session
è sempre nullo.
Risposte:
MVC
Per un progetto MVC, apportare le seguenti modifiche (WebForms e Dot Net Core rispondono in basso):
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);
}
}
Questa soluzione ha l'ulteriore vantaggio di poter recuperare l'URL di base in javascript per effettuare le chiamate AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
e poi all'interno dei nostri file / codice Javascript possiamo effettuare le nostre chiamate webapi che possono accedere alla sessione:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
Fai quanto sopra ma modifica la funzione WebApiConfig.Register per prendere invece RouteCollection:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
E quindi chiama quanto segue in Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
Aggiungi Microsoft.AspNetCore.Session pacchetto NuGet e quindi apportare le seguenti modifiche al codice:
Chiamare i metodi AddDistributedMemoryCache e AddSession sull'oggetto servizi all'interno della funzione ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
e nella funzione Configura aggiungi una chiamata a UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
Nel controller, aggiungi un'istruzione using in alto:
using Microsoft.AspNetCore.Http;
e quindi usa l'oggetto HttpContext.Session nel tuo codice in questo modo:
[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);
}
ora dovresti essere in grado di colpire:
http://localhost:1234/api/session/set/thisissomedata
e poi andando a questo URL lo estrarrà:
http://localhost:1234/api/session/get
Molte altre informazioni su come accedere ai dati della sessione in dot net core qui: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Preoccupazioni per le prestazioni
Leggi la risposta di Simon Weaver di seguito in merito alle prestazioni. Se si accede ai dati della sessione all'interno di un progetto WebApi, ciò può avere conseguenze molto gravi sulle prestazioni: ho visto ASP.NET imporre un ritardo di 200 ms per le richieste simultanee. Questo potrebbe sommarsi e diventare disastroso se hai molte richieste simultanee.
Problemi di sicurezza
Assicurati di bloccare le risorse per utente: un utente autenticato non dovrebbe essere in grado di recuperare dati dal tuo WebApi a cui non ha accesso.
Leggi l'articolo di Microsoft sull'autenticazione e l'autorizzazione nell'API Web ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Leggi l'articolo di Microsoft su come evitare attacchi di hacking contraffazione richiesta cross-site. (In breve, controlla il metodo AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
È possibile accedere allo stato della sessione utilizzando un RouteHandler personalizzato.
// 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;
}
}
Trovato qui: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Performance, performance, performance!
C'è una ragione molto buona e spesso trascurata per cui non dovresti usare Session in WebAPI.
Il modo in cui ASP.NET funziona quando Session è in uso è serializzare tutte le richieste ricevute da un singolo client . Ora non sto parlando della serializzazione degli oggetti, ma eseguendoli nell'ordine ricevuto e aspettando che ciascuno si completi prima di eseguire il successivo. Questo per evitare cattive condizioni di thread / gara se due richieste tentano di accedere contemporaneamente a Session.
Richieste simultanee e stato della sessione
L'accesso allo stato della sessione ASP.NET è esclusivo per sessione, il che significa che se due utenti diversi fanno richieste simultanee, l'accesso a ciascuna sessione separata viene concesso contemporaneamente. Tuttavia, se vengono effettuate due richieste simultanee per la stessa sessione (utilizzando lo stesso valore SessionID), la prima richiesta ottiene l'accesso esclusivo alle informazioni sulla sessione. La seconda richiesta viene eseguita solo al termine della prima richiesta.(La seconda sessione può anche ottenere l'accesso se viene liberato il blocco esclusivo delle informazioni perché la prima richiesta supera il timeout del blocco.) Se il valore EnableSessionState nella direttiva @ Page è impostato su ReadOnly, una richiesta di sola lettura le informazioni sulla sessione non comportano un blocco esclusivo dei dati della sessione. Tuttavia, è possibile che le richieste di sola lettura per i dati della sessione debbano ancora attendere un blocco impostato da una richiesta di lettura / scrittura per cancellare i dati della sessione.
Cosa significa questo per l'API Web? Se hai un'applicazione che esegue molte richieste AJAX, solo ONE sarà in grado di essere eseguito alla volta. Se hai una richiesta più lenta, bloccherà tutti gli altri da quel client fino al completamento. In alcune applicazioni ciò potrebbe portare a prestazioni notevolmente lente.
Quindi probabilmente dovresti usare un controller MVC se hai assolutamente bisogno di qualcosa dalla sessione degli utenti ed evitare la penalità delle prestazioni non necessaria di abilitarlo per WebApi.
Puoi facilmente provarlo da solo inserendo Thread.Sleep(5000)
un metodo WebAPI e abilitando Session. Esegui 5 richieste e ci vorranno un totale di 25 secondi per il completamento. Senza sessione impiegheranno un totale di poco più di 5 secondi.
(Lo stesso ragionamento si applica a SignalR).
Bene hai ragione, REST è apolide. Se si utilizza una sessione, l'elaborazione diventerà stateful, le richieste successive potranno utilizzare state (da una sessione).
Per reidratare una sessione, è necessario fornire una chiave per associare lo stato. In una normale applicazione asp.net tale chiave viene fornita utilizzando un cookie (sessioni cookie) o un parametro url (sessioni senza cookie).
Se hai bisogno di una sessione dimentica il riposo, le sessioni sono irrilevanti nei progetti basati su REST. Se è necessaria una sessione per la convalida, utilizzare un token o autorizzare tramite indirizzi IP.
Mark, se controlli l' esempio nerddinner MVC la logica è praticamente la stessa.
Hai solo bisogno di recuperare il cookie e impostarlo nella sessione corrente.
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
Dovrai definire la tua classe "SampleIdentity", che puoi prendere in prestito dal progetto nerddinner .
Per risolvere il problema:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
in Global.asax.cs
L'ultimo non funziona ora, prendi questo, ha funzionato per me.
in WebApiConfig.cs su 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);
}
quarta qui: http://forums.asp.net/t/1773026.aspx/1
Seguendo la risposta di LachlanB, se ApiController non si trova all'interno di una directory specifica (come / api), puoi invece testare la richiesta utilizzando RouteTable.Routes.GetRouteData, ad esempio:
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);
}
Ho avuto lo stesso problema in asp.net mvc, l'ho risolto inserendo questo metodo nel mio controller API di base che tutti i miei controller API ereditavano:
/// <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;
}
Quindi nella tua chiamata api che vuoi accedere alla sessione devi solo fare:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
Ho anche questo nel mio file Global.asax.cs come altre persone hanno pubblicato, non sono sicuro se ne hai ancora bisogno usando il metodo sopra, ma qui è nel caso:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Potresti anche creare un attributo di filtro personalizzato che puoi attaccare alle tue chiamate API che ti servono per la sessione, quindi puoi utilizzare la sessione nella tua chiamata API come faresti normalmente tramite 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;
}
}
}
}
Spero che questo ti aiuti.
Ho seguito l'approccio @LachlanB e in effetti la sessione era disponibile quando il cookie di sessione era presente sulla richiesta. La parte mancante è come il cookie di sessione viene inviato al client la prima volta?
Ho creato un HttpModule che non solo abilita la disponibilità di HttpSessionState ma invia anche il cookie al client quando viene creata una nuova sessione.
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);
}
}
una cosa deve essere menzionata nella risposta di @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Se si omette la linea if (IsWebApiRequest())
L'intero sito avrà problemi di rallentamento del caricamento della pagina se il tuo sito è mescolato con pagine di moduli web.
Sì, la sessione non va di pari passo con l'API Rest e dovremmo anche evitare queste pratiche. Ma secondo i requisiti dobbiamo mantenere la sessione in qualche modo tale che in ogni richiesta il server client possa scambiare o mantenere lo stato o i dati. Quindi, il modo migliore per raggiungere questo obiettivo senza rompere i protocolli REST è comunicare tramite token come JWT.
Tornando alle origini, perché non mantenerlo semplice e archiviare il valore Sessione in un valore html nascosto da passare all'API?
controllore
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)]
sulApiController
trucco (o.ReadOnly
dove appropriato).