example - Acceso a la sesión utilizando la API web de ASP.NET
web api return json example (12)
Me doy cuenta de que la sesión y el 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.
MVC
Para un proyecto MVC, realice los siguientes cambios (a continuación, WebForms y Dot Net Core responden a continuación):
WebApiConfig.cs
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 }
);
}
}
Global.asax.cs
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:
_Layout.cshtml
<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);
})
);
Formas 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:
Startup.cs
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();
SessionController.cs
Dentro de su controlador, agregue una declaración usando en la parte superior:
using Microsoft.AspNetCore.Http;
y luego use el objeto HttpContext.Session dentro de su código así:
[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
Aquí encontrará más información sobre cómo acceder a los datos de la sesión en dot net core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Preocupaciones de rendimiento
Lea la respuesta de Simon Weaver a continuación con respecto al rendimiento. Si está accediendo a los datos de la sesión dentro de un proyecto WebApi, puede tener consecuencias muy graves en el rendimiento; he visto que ASP.NET impone un retraso de 200 ms para las solicitudes simultáneas. Esto podría sumarse y volverse desastroso si tiene muchas solicitudes concurrentes.
Preocupaciones de seguridad
Asegúrese de que está bloqueando los recursos por usuario: un usuario autenticado no debería poder recuperar datos de su WebApi a los que no tienen acceso.
Lea el artículo de Microsoft sobre autenticación y autorización en ASP.NET Web API - 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 pirateo de falsificación de solicitudes 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
¿Por qué evitar el uso de Session en WebAPI?
Rendimiento, rendimiento, rendimiento!
Hay una razón muy buena y, a menudo, pasada por alto, por la que no debería usar Session en WebAPI.
La forma en que funciona ASP.NET cuando la sesión está en uso 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 complete cada uno antes de ejecutar el siguiente. Esto es para evitar condiciones desagradables de subprocesos / carreras 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 de ASP.NET es exclusivo por sesión, lo que significa que si dos usuarios diferentes realizan solicitudes simultáneas, 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 se libera el bloqueo exclusivo de la información porque la primera solicitud supera el tiempo de espera de bloqueo). Si el valor de EnableSessionState en la directiva @ Página se establece en Solo lectura, 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 UNO podrá ejecutarse a la vez. Si tiene una solicitud más lenta, bloqueará todos los demás de ese cliente hasta que se complete. En algunas aplicaciones, esto podría llevar a un rendimiento muy perceptiblemente lento.
Por lo tanto, probablemente debería usar un controlador MVC si absolutamente necesita algo de la sesión de los usuarios y evitar la penalización de rendimiento innecesaria de habilitarlo para WebApi.
Puede probar esto fácilmente por sí mismo simplemente poniendo Thread.Sleep(5000)
en un método WebAPI y habilitando la sesión. Ejecuta 5 solicitudes y tomarán un total de 25 segundos para completar. Sin sesión tomarán un total de poco más de 5 segundos.
(Este mismo razonamiento se aplica a SignalR).
El último no está funcionando ahora, toma éste, 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);
}
Aquí se encuentra: http://forums.asp.net/t/1773026.aspx/1
Luego de la respuesta de LachlanB, si su ApiController no se encuentra dentro de un directorio en particular (como / api), puede probar la solicitud utilizando 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);
}
Mark, si verificas 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
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;
}
}
Se encuentra aquí: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Pues tienes razón, REST es apátrida. Si utiliza una sesión, el procesamiento pasará a ser de estado, las solicitudes posteriores podrán usar el estado (desde una sesión).
Para que la 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 url (sesiones sin cookies).
Si necesita una sesión, olvídese del descanso, 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.
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 de 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);
}
}
Tuve este mismo problema en asp.net mvc, lo arreglé poniendo este método en mi controlador base de API que todos mis controladores de API heredaron 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, desea acceder a la sesión que acaba de hacer:
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 de si todavía lo necesitas usando el método anterior, pero aquí es solo en el 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);
}
}
También puede hacer un atributo de filtro personalizado que pueda pegar en sus llamadas a la API que necesita sesión, luego puede usar la sesión en su llamada de la 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.
Una cosa hay que mencionar en la respuesta de @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Si omite la línea if (IsWebApiRequest())
Todo el sitio tendrá un problema de lentitud en la carga de páginas si su sitio está mezclado con páginas de formularios web.
Volviendo a lo básico, ¿por qué no mantenerlo simple y almacenar el valor de Sesión en un valor html oculto para transferirlo 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 */}
}