Sei que a sessão e o REST não andam exatamente de mãos dadas, mas não é possível acessar o estado da sessão usando a nova API da Web? HttpContext.Current.Session
é sempre nulo.
Sei que a sessão e o REST não andam exatamente de mãos dadas, mas não é possível acessar o estado da sessão usando a nova API da Web? HttpContext.Current.Session
é sempre nulo.
Respostas:
MVC
Para um projeto MVC, faça as seguintes alterações (WebForms e Dot Net Core respondem abaixo):
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 solução tem o bônus adicional de que podemos buscar o URL base em javascript para fazer as chamadas AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
e, em nossos arquivos / códigos Javascript, podemos fazer nossas chamadas de webapi que podem acessar a sessão:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
Faça o acima, mas altere a função WebApiConfig.Register para obter um RouteCollection:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
E, em seguida, chame o seguinte em Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
Adicione o pacote Microsoft.AspNetCore.Session NuGet e faça as seguintes alterações de código:
Chame os AddDistributedMemoryCache e AddSession métodos nos serviços objeto dentro da função ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
e na função Configurar, adicione uma chamada ao UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
No seu controlador, adicione uma instrução using na parte superior:
using Microsoft.AspNetCore.Http;
e use o objeto HttpContext.Session dentro do seu código da seguinte maneira:
[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);
}
agora você deve conseguir:
http://localhost:1234/api/session/set/thisissomedata
e, em seguida, acessar este URL o retirará:
http://localhost:1234/api/session/get
Muito mais informações sobre o acesso aos dados da sessão no dot net core aqui: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Preocupações com o desempenho
Leia a resposta de Simon Weaver abaixo sobre desempenho. Se você estiver acessando dados da sessão dentro de um projeto WebApi, isso poderá ter conseqüências muito sérias no desempenho - eu já vi o ASP.NET impor um atraso de 200ms para solicitações simultâneas. Isso pode aumentar e se tornar desastroso se você tiver muitas solicitações simultâneas.
Preocupações com segurança
Verifique se você está bloqueando recursos por usuário - um usuário autenticado não poderá recuperar dados de seus WebApi aos quais não tem acesso.
Leia o artigo da Microsoft sobre autenticação e autorização na API da Web do ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Leia o artigo da Microsoft sobre como evitar ataques de hackers de solicitação de falsificação entre sites. (Em resumo, confira o método AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
Você pode acessar o estado da sessão usando um 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 aqui: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Desempenho, desempenho, desempenho!
Há uma razão muito boa e muitas vezes esquecida pela qual você não deve usar a Sessão na WebAPI.
A maneira como o ASP.NET funciona quando a Sessão está em uso é serializar todas as solicitações recebidas de um único cliente . Agora não estou falando sobre serialização de objetos - mas executando-os na ordem recebida e aguardando a conclusão de cada um antes de executar o próximo. Isso evita condições desagradáveis de thread / corrida se duas solicitações tentarem acessar a sessão simultaneamente.
Solicitações simultâneas e estado da sessão
O acesso ao estado da sessão do ASP.NET é exclusivo por sessão, o que significa que, se dois usuários diferentes fizerem solicitações simultâneas, o acesso a cada sessão separada será concedido simultaneamente. No entanto, se duas solicitações simultâneas forem feitas para a mesma sessão (usando o mesmo valor de SessionID), a primeira solicitação obterá acesso exclusivo às informações da sessão. A segunda solicitação é executada somente após a conclusão da primeira solicitação.(A segunda sessão também pode obter acesso se o bloqueio exclusivo das informações for liberado porque a primeira solicitação exceder o tempo limite do bloqueio). Se o valor EnableSessionState na diretiva @ Page estiver definido como ReadOnly, uma solicitação para o somente leitura as informações da sessão não resultam em um bloqueio exclusivo nos dados da sessão. No entanto, as solicitações somente leitura para dados da sessão ainda precisam aguardar um bloqueio definido por uma solicitação de leitura e gravação para que os dados da sessão sejam limpos.
Então, o que isso significa para a API da Web? Se você tiver um aplicativo executando muitas solicitações AJAX, apenas um poderá executar por vez. Se você tiver uma solicitação mais lenta, ela bloqueará todas as outras desse cliente até sua conclusão. Em algumas aplicações, isso pode levar a um desempenho notavelmente lento.
Portanto, você provavelmente deve usar um controlador MVC se precisar absolutamente de algo da sessão de usuários e evitar a penalidade desnecessária de desempenho ao habilitá-lo para o WebApi.
Você pode facilmente testar isso sozinho Thread.Sleep(5000)
inserindo um método WebAPI e habilitando a Session. Execute 5 solicitações e elas levarão um total de 25 segundos para serem concluídas. Sem a sessão, eles levarão um total de pouco mais de 5 segundos.
(Esse mesmo raciocínio se aplica ao SignalR).
Bem, você está certo, o REST é sem estado. Se você usar uma sessão, o processamento se tornará estável, as solicitações subsequentes poderão usar o estado (de uma sessão).
Para que uma sessão seja reidratada, você precisará fornecer uma chave para associar o estado. Em um aplicativo asp.net normal, essa chave é fornecida usando um parâmetro cookie (sessões de cookie) ou url (sessões sem cookies).
Se você precisar de uma sessão, esqueça o resto, as sessões são irrelevantes nos designs baseados em REST. Se você precisar de uma sessão para validação, use um token ou autorize por endereços IP.
Marque, se você verificar o exemplo do nerddinner MVC, a lógica é praticamente a mesma.
Você só precisa recuperar o cookie e configurá-lo na sessão atual.
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
Você terá que definir sua classe "SampleIdentity", que pode ser emprestada do projeto nerddinner .
Para corrigir o problema:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
no Global.asax.cs
O último não está funcionando agora, pegue este, funcionou para mim.
no WebApiConfig.cs em 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);
}
quarto aqui: http://forums.asp.net/t/1773026.aspx/1
Seguindo a resposta de LachlanB, se o seu ApiController não estiver em um diretório específico (como / api), você poderá testar a solicitação usando RouteTable.Routes.GetRouteData, por exemplo:
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);
}
Eu tive esse mesmo problema no asp.net mvc, eu o corrigi colocando esse método no meu controlador api básico que todos os meus controladores api herdam:
/// <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;
}
Em sua chamada de API, você deseja acessar a sessão que acabou de fazer:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
Eu também tenho isso no meu arquivo Global.asax.cs, como outras pessoas postaram, não tenho certeza se você ainda precisa dele usando o método acima, mas aqui está o 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);
}
}
Você também pode criar um atributo de filtro personalizado que possa ser usado nas chamadas da API que você precisa, e poderá usar a sessão na sua chamada da API como faria normalmente 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;
}
}
}
}
Espero que isto ajude.
Eu segui a abordagem @LachlanB e, de fato, a sessão estava disponível quando o cookie da sessão estava presente na solicitação. A parte que falta é como o cookie de sessão é enviado ao cliente pela primeira vez?
Criei um HttpModule que não apenas habilita a disponibilidade do HttpSessionState, mas também envia o cookie ao cliente quando uma nova sessão é criada.
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);
}
}
uma coisa precisa ser mencionada na resposta de @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Se você omitir a linha if (IsWebApiRequest())
O site inteiro terá um problema de lentidão no carregamento da página se o site for misturado com páginas de formulário da web.
Sim, a sessão não anda de mãos dadas com a API Rest e também devemos evitar essas práticas. Mas, de acordo com os requisitos, precisamos manter a sessão de alguma forma, de modo que, em cada solicitação, o servidor cliente possa trocar ou manter estado ou dados. Portanto, a melhor maneira de conseguir isso sem quebrar os protocolos REST é se comunicar através de token como JWT.
Voltando ao básico, por que não mantê-lo simples e armazenar o valor da Sessão em um valor html oculto para passar para sua 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
$ (document) .ready (function () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
noApiController
faz o truque (ou.ReadOnly
onde apropriado).