Me doy cuenta de que la sesión y REST no van exactamente de la mano, pero ¿no es posible acceder al estado de la sesión utilizando la nueva API web? HttpContext.Current.Session
siempre es nulo.
Me doy cuenta de que la sesión y REST no van exactamente de la mano, pero ¿no es posible acceder al estado de la sesión utilizando la nueva API web? HttpContext.Current.Session
siempre es nulo.
Respuestas:
MVC
Para un proyecto MVC, realice los siguientes cambios (WebForms y Dot Net Core responden a continuación):
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);
}
}
Esta solución tiene la ventaja adicional de que podemos obtener la URL base en JavaScript para realizar las llamadas AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
y luego dentro de nuestros archivos / código Javascript podemos hacer nuestras llamadas webapi que pueden acceder a la sesión:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
Formularios web
Haga lo anterior pero cambie la función WebApiConfig.Register para tomar una RouteCollection en su lugar:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Y luego llame a lo siguiente en Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
Agregue el paquete Microsoft.AspNetCore.Session NuGet y luego realice los siguientes cambios de código:
Llame a los métodos AddDistributedMemoryCache y AddSession en el objeto de servicios dentro de la función ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
y en la función Configurar, agregue una llamada a UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
Dentro de su controlador, agregue una instrucción using en la parte superior:
using Microsoft.AspNetCore.Http;
y luego use el objeto HttpContext.Session dentro de su código de la siguiente manera:
[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);
}
ahora deberías poder golpear:
http://localhost:1234/api/session/set/thisissomedata
y luego ir a esta URL lo sacará:
http://localhost:1234/api/session/get
Mucha más información sobre cómo acceder a los datos de sesión dentro de dot net core aquí: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Preocupaciones de rendimiento
Lea la respuesta de Simon Weaver a continuación sobre el rendimiento. Si está accediendo a los datos de la sesión dentro de un proyecto WebApi, puede tener consecuencias muy serias en el rendimiento: he visto que ASP.NET impone un retraso de 200 ms para solicitudes concurrentes. Esto podría sumar y volverse desastroso si tiene muchas solicitudes simultáneas.
Preocupaciones de seguridad
Asegúrese de bloquear recursos por usuario: un usuario autenticado no debería poder recuperar datos de su WebApi a los que no tenga acceso.
Lea el artículo de Microsoft sobre Autenticación y autorización en la API web ASP.NET: https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Lea el artículo de Microsoft sobre cómo evitar los ataques de piratería de solicitudes falsas entre sitios (En resumen, consulte el método AntiForgery.Validate): https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
Puede acceder al estado de la sesión utilizando un RouteHandler personalizado.
// 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;
}
}
Encontrado aquí: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Rendimiento, rendimiento, rendimiento!
Hay una razón muy buena, y a menudo pasada por alto, por la que no deberías usar Session en WebAPI.
La forma en que ASP.NET funciona cuando se usa la sesión es serializar todas las solicitudes recibidas de un solo cliente . Ahora no estoy hablando de la serialización de objetos, sino de ejecutarlos en el orden recibido y esperar a que se completen antes de ejecutar el siguiente. Esto es para evitar condiciones desagradables de subproceso / carrera si dos solicitudes intentan acceder a la sesión simultáneamente.
Solicitudes concurrentes y estado de sesión
El acceso al estado de sesión ASP.NET es exclusivo por sesión, lo que significa que si dos usuarios diferentes realizan solicitudes concurrentes, el acceso a cada sesión por separado se otorga simultáneamente. Sin embargo, si se realizan dos solicitudes simultáneas para la misma sesión (utilizando el mismo valor de SessionID), la primera solicitud obtiene acceso exclusivo a la información de la sesión. La segunda solicitud se ejecuta solo después de que la primera solicitud haya finalizado.(La segunda sesión también puede obtener acceso si el bloqueo exclusivo de la información se libera porque la primera solicitud excede el tiempo de espera de bloqueo). Si el valor EnableSessionState en la directiva @ Page se establece en ReadOnly, una solicitud de solo lectura la información de la sesión no produce un bloqueo exclusivo en los datos de la sesión. Sin embargo, es posible que las solicitudes de solo lectura para los datos de la sesión aún tengan que esperar un bloqueo establecido por una solicitud de lectura y escritura para que se borren los datos de la sesión.
Entonces, ¿qué significa esto para la API web? Si tiene una aplicación que ejecuta muchas solicitudes AJAX, solo UNA podrá ejecutarse a la vez. Si tiene una solicitud más lenta, bloqueará a todos los demás de ese cliente hasta que se complete. En algunas aplicaciones, esto podría conducir a un rendimiento muy notablemente lento.
Por lo tanto, probablemente debería usar un controlador MVC si necesita absolutamente algo de la sesión de los usuarios y evitar la penalización de rendimiento incesante de habilitarlo para WebApi.
Puede probar esto fácilmente por sí mismo con solo poner Thread.Sleep(5000)
un método WebAPI y habilitar la Sesión. Ejecute 5 solicitudes y tardará un total de 25 segundos en completarse. Sin sesión, tomarán un total de poco más de 5 segundos.
(Este mismo razonamiento se aplica a SignalR).
Bueno, tienes razón, REST no tiene estado. Si usa una sesión, el procesamiento pasará a estado, las solicitudes posteriores podrán usar el estado (de una sesión).
Para que una sesión se rehidrate, deberá proporcionar una clave para asociar el estado. En una aplicación asp.net normal, esa clave se proporciona mediante el uso de una cookie (sesiones de cookies) o un parámetro de URL (sesiones sin cookies).
Si necesita una sesión, olvide descansar, las sesiones son irrelevantes en los diseños basados en REST. Si necesita una sesión para la validación, use un token o autorice por direcciones IP.
Mark, si revisas el ejemplo de nerddinner MVC, la lógica es más o menos la misma.
Solo necesita recuperar la cookie y configurarla en la sesión actual.
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
Tendrá que definir su clase "SampleIdentity", que puede tomar prestada del proyecto nerddinner .
Para solucionar el problema:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
en Global.asax.cs
El último no funciona ahora, toma este, funcionó para mí.
en WebApiConfig.cs en 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);
}
cuarto aquí: http://forums.asp.net/t/1773026.aspx/1
Siguiendo con la respuesta de LachlanB, si su ApiController no se encuentra dentro de un directorio particular (como / api), puede probar la solicitud usando RouteTable.Routes.GetRouteData, por ejemplo:
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);
}
Tuve este mismo problema en asp.net mvc, lo solucioné colocando este método en mi controlador api base que todos mis controladores api heredan de:
/// <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;
}
Luego, en su llamada a la API que desea acceder a la sesión, simplemente haga lo siguiente:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
También tengo esto en mi archivo Global.asax.cs como lo han publicado otras personas, no estoy seguro si todavía lo necesita utilizando el método anterior, pero aquí es por si acaso:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
También puede crear un atributo de filtro personalizado que pueda pegar en sus llamadas de API que necesite sesión, luego puede usar la sesión en su llamada de API como lo haría normalmente a través de 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;
}
}
}
}
Espero que esto ayude.
Seguí el enfoque de @LachlanB y, de hecho, la sesión estaba disponible cuando la cookie de sesión estaba presente en la solicitud. La parte que falta es cómo se envía la cookie de sesión al cliente la primera vez.
Creé un HttpModule que no solo habilita la disponibilidad HttpSessionState sino que también envía la cookie al cliente cuando se crea una nueva sesión.
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 debe mencionarse en la respuesta de @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Si omites la línea if (IsWebApiRequest())
Todo el sitio tendrá un problema de lentitud en la carga de la página si su sitio se combina con páginas de formularios web.
Sí, la sesión no va de la mano con Rest API y también debemos evitar estas prácticas. Pero de acuerdo con los requisitos, necesitamos mantener la sesión de alguna manera tal que en cada solicitud el servidor cliente pueda intercambiar o mantener el estado o los datos. Entonces, la mejor manera de lograr esto sin romper los protocolos REST es comunicarse a través de token como JWT.
Volviendo a lo básico, ¿por qué no mantenerlo simple y almacenar el valor de la sesión en un valor html oculto para pasar a su API?
Controlador
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
$ (documento) .ready (función () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
en elApiController
hace el truco (o.ReadOnly
en su caso).