Je me rends compte que session et REST ne vont pas exactement de pair, mais n'est-il pas possible d'accéder à l'état de session à l'aide de la nouvelle API Web? HttpContext.Current.Session
est toujours nul.
Je me rends compte que session et REST ne vont pas exactement de pair, mais n'est-il pas possible d'accéder à l'état de session à l'aide de la nouvelle API Web? HttpContext.Current.Session
est toujours nul.
Réponses:
MVC
Pour un projet MVC, apportez les modifications suivantes (WebForms et Dot Net Core répondent ci-dessous):
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);
}
}
Cette solution a le bonus supplémentaire que nous pouvons récupérer l'URL de base en javascript pour effectuer les appels AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
puis dans nos fichiers / code Javascript, nous pouvons effectuer nos appels webapi qui peuvent accéder à la session:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
Faites ce qui précède mais modifiez la fonction WebApiConfig.Register pour prendre un RouteCollection à la place:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Et puis appelez ce qui suit dans Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
Ajoutez le package NuGet Microsoft.AspNetCore.Session , puis apportez les modifications de code suivantes:
Appelez les méthodes AddDistributedMemoryCache et AddSession sur l'objet services dans la fonction ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
et dans la fonction Configurer, ajoutez un appel à UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
Dans votre contrôleur, ajoutez une instruction using en haut:
using Microsoft.AspNetCore.Http;
puis utilisez l'objet HttpContext.Session dans votre code comme ceci:
[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);
}
vous devriez maintenant pouvoir toucher:
http://localhost:1234/api/session/set/thisissomedata
puis accéder à cette URL le retirera:
http://localhost:1234/api/session/get
Beaucoup plus d'informations sur l'accès aux données de session dans dot net core ici: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Problèmes de performance
Lisez la réponse de Simon Weaver ci-dessous concernant les performances. Si vous accédez à des données de session dans un projet WebApi, cela peut avoir des conséquences très graves sur les performances - j'ai vu ASP.NET appliquer un délai de 200 ms pour les demandes simultanées. Cela pourrait s'additionner et devenir désastreux si vous avez plusieurs demandes simultanées.
Problèmes de sécurité
Assurez-vous de verrouiller les ressources par utilisateur - un utilisateur authentifié ne devrait pas être en mesure de récupérer des données de votre WebApi auxquelles il n'a pas accès.
Lisez l'article de Microsoft sur l'authentification et l'autorisation dans l'API Web ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Lisez l'article de Microsoft sur la façon d'éviter les attaques de piratage Cross-Site Request Forgery. (En bref, consultez la méthode AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
Vous pouvez accéder à l'état de la session à l'aide d'un RouteHandler personnalisé.
// 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;
}
}
Trouvé ici: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Performance, performance, performance!
Il y a une très bonne raison, et souvent négligée, pour laquelle vous ne devriez pas du tout utiliser Session dans WebAPI.
La façon dont ASP.NET fonctionne lorsque Session est en cours d'utilisation consiste à sérialiser toutes les demandes reçues d'un seul client . Maintenant, je ne parle pas de sérialisation d'objet - mais de les exécuter dans l'ordre reçu et d'attendre que chacun se termine avant d'exécuter la suivante. C'est pour éviter les conditions de thread / race désagréables si deux requêtes essaient chacune d'accéder à Session simultanément.
Demandes simultanées et état de la session
L'accès à l'état de session ASP.NET est exclusif par session, ce qui signifie que si deux utilisateurs différents effectuent des demandes simultanées, l'accès à chaque session distincte est accordé simultanément. Cependant, si deux demandes simultanées sont effectuées pour la même session (en utilisant la même valeur SessionID), la première demande obtient un accès exclusif aux informations de session. La deuxième demande s'exécute uniquement une fois la première demande terminée.(La deuxième session peut également obtenir l'accès si le verrou exclusif sur les informations est libéré car la première demande dépasse le délai de verrouillage.) Si la valeur EnableSessionState dans la directive @ Page est définie sur ReadOnly, une demande de lecture seule les informations de session n'entraînent pas un verrouillage exclusif sur les données de session. Cependant, les demandes en lecture seule pour les données de session peuvent encore devoir attendre un verrou défini par une demande de lecture-écriture pour que les données de session soient effacées.
Alors qu'est-ce que cela signifie pour l'API Web? Si vous avez une application exécutant de nombreuses requêtes AJAX, seule UNE pourra être exécutée à la fois. Si vous avez une demande plus lente, elle bloquera tous les autres de ce client jusqu'à ce qu'elle soit terminée. Dans certaines applications, cela pourrait conduire à des performances très lentes.
Vous devriez donc probablement utiliser un contrôleur MVC si vous avez absolument besoin de quelque chose de la session des utilisateurs et éviter la pénalité de performance inutile de l'activer pour WebApi.
Vous pouvez facilement tester cela par vous-même Thread.Sleep(5000)
en insérant simplement une méthode WebAPI et en activant Session. Exécutez-y 5 requêtes et elles prendront un total de 25 secondes. Sans session, cela prendra un peu plus de 5 secondes au total.
(Ce même raisonnement s'applique à SignalR).
Eh bien, vous avez raison, REST est apatride. Si vous utilisez une session, le traitement deviendra avec état, les requêtes suivantes pourront utiliser l'état (à partir d'une session).
Pour qu'une session soit réhydratée, vous devrez fournir une clé pour associer l'état. Dans une application asp.net normale, cette clé est fournie à l'aide d'un cookie (cookie-sessions) ou d'un paramètre url (sessions sans cookies).
Si vous avez besoin d'une session, oubliez le repos, les sessions ne sont pas pertinentes dans les conceptions basées sur REST. Si vous avez besoin d'une session pour la validation, utilisez un jeton ou autorisez par des adresses IP.
Mark, si vous vérifiez l' exemple nerddinner MVC, la logique est à peu près la même.
Il vous suffit de récupérer le cookie et de le définir dans la session en cours.
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
Vous devrez définir votre classe "SampleIdentity", que vous pourrez emprunter au projet nerddinner .
Pour résoudre le problème:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
dans Global.asax.cs
Le dernier ne fonctionne pas maintenant, prenez celui-ci, il a fonctionné pour moi.
dans WebApiConfig.cs sur 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);
}
fournd ici: http://forums.asp.net/t/1773026.aspx/1
Suite à la réponse de LachlanB, si votre ApiController ne se trouve pas dans un répertoire particulier (comme / api), vous pouvez à la place tester la demande en utilisant RouteTable.Routes.GetRouteData, par exemple:
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);
}
J'ai eu ce même problème dans asp.net mvc, je l'ai résolu en mettant cette méthode dans mon contrôleur d'api de base dont tous mes contrôleurs d'api héritent:
/// <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;
}
Ensuite, dans votre appel API, vous souhaitez accéder à la session que vous venez de faire:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
J'ai également cela dans mon fichier Global.asax.cs comme d'autres personnes l'ont posté, je ne sais pas si vous en avez toujours besoin en utilisant la méthode ci-dessus, mais ici, c'est juste au cas où:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Vous pouvez également créer un attribut de filtre personnalisé que vous pouvez coller sur vos appels api dont vous avez besoin de session, puis vous pouvez utiliser la session dans votre appel api comme vous le feriez normalement via 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;
}
}
}
}
J'espère que cela t'aides.
J'ai suivi l'approche @LachlanB et en effet la session était disponible lorsque le cookie de session était présent sur la demande. La partie manquante est de savoir comment le cookie de session est envoyé au client la première fois?
J'ai créé un HttpModule qui non seulement active la disponibilité HttpSessionState mais envoie également le cookie au client lorsqu'une nouvelle session est créée.
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);
}
}
une chose doit être mentionnée dans la réponse de @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Si vous omettez la ligne if (IsWebApiRequest())
Le site entier aura un problème de lenteur de chargement de page si votre site est mélangé avec des pages de formulaire Web.
Oui, la session ne va pas de pair avec l'API Rest et nous devons également éviter ces pratiques. Mais selon les exigences, nous devons maintenir la session d'une manière ou d'une autre de telle sorte que dans chaque demande, le serveur client puisse échanger ou maintenir l'état ou les données. Ainsi, la meilleure façon d'y parvenir sans rompre les protocoles REST est de communiquer via un jeton comme JWT.
Pour en revenir aux bases, pourquoi ne pas rester simple et stocker la valeur de la session dans une valeur html cachée pour la transmettre à votre API?
Manette
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 (fonction () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
sur leApiController
fait l'astuce (ou le.ReadOnly
cas échéant).