c# - Manera correcta de usar HttpContext.Current.User con async await
asp.net-mvc asp.net-mvc-5 (3)
Estoy trabajando con acciones asíncronas y uso el HttpContext.Current.User como este
public class UserService : IUserService
{
public ILocPrincipal Current
{
get { return HttpContext.Current.User as ILocPrincipal; }
}
}
public class ChannelService : IDisposable
{
// In the service layer
public ChannelService()
: this(new Entities.LocDbContext(), new UserService())
{
}
public ChannelService(Entities.LocDbContext locDbContext, IUserService userService)
{
this.LocDbContext = locDbContext;
this.UserService = userService;
}
public async Task<ViewModels.DisplayChannel> FindOrDefaultAsync(long id)
{
var currentMemberId = this.UserService.Current.Id;
// do some async EF request …
}
}
// In the controller
[Authorize]
[RoutePrefix("channel")]
public class ChannelController : BaseController
{
public ChannelController()
: this(new ChannelService()
{
}
public ChannelController(ChannelService channelService)
{
this.ChannelService = channelService;
}
// …
[HttpGet, Route("~/api/channels/{id}/messages")]
public async Task<ActionResult> GetMessages(long id)
{
var channel = await this.ChannelService
.FindOrDefaultAsync(id);
return PartialView("_Messages", channel);
}
// …
}
Tengo el código recientemente refactorizado, anteriormente tuve que dar al usuario en cada llamada al servicio. Ahora leí este artículo http://trycatchfail.com/blog/post/Using-HttpContext-Safely-After-Async-in-ASPNET-MVC-Applications.aspx y no estoy seguro de si mi código sigue funcionando. ¿Alguien tiene un mejor enfoque para manejar esto? No quiero dar al usuario en cada solicitud al servicio.
Async está bien. El problema es cuando publicas el trabajo en un hilo diferente. Si su aplicación está configurada como 4.5+, la devolución de llamada asíncrona se publicará en el contexto original, por lo que también tendrá el HttpContext
apropiado, etc.
De todos modos, no desea acceder al estado compartido en un subproceso diferente, y con las Task
, rara vez necesita manejarlo explícitamente; solo asegúrese de colocar todas sus entradas como argumentos, y solo devolver una respuesta, en lugar de leer o escribir a un estado compartido (por ejemplo, HttpContext
, campos estáticos, etc.)
No hay problema, si su ViewModels.DisplayChannel
es un objeto simple sin lógica adicional.
Se puede producir un problema, si el resultado de su Task
referencia a "algunos objetos de contexto", fe a HttpContext.Current
. Tales objetos a menudo se adjuntan al hilo, pero el código completo después de await
puede ejecutarse en otro hilo.
Tenga en cuenta que UseTaskFriendlySynchronizationContext
no resuelve todos sus problemas. Si estamos hablando de ASP.NET MVC, esta configuración garantiza que Controller.HttpContext
contenga el valor correcto como antes lo await
como después. Pero no garantiza que HttpContext.Current
contenga el valor correcto y, después de await
, aún puede ser nulo .
Siempre que la configuración de web.config
sea correcta , async
/ await
funciona perfectamente bien con HttpContext.Current
. Recomiendo establecer httpRuntime
targetFramework
en 4.5
para eliminar todo el comportamiento de "modo de peculiaridades".
Una vez hecho esto, el simple async
/ await
funcionará perfectamente bien. Solo se encontrará con problemas si está trabajando en otro hilo o si su código de await
es incorrecto.
Primero, el problema del "otro hilo"; Este es el segundo problema en la publicación del blog al que te vinculaste. Código como este, por supuesto, no funcionará correctamente:
async Task FakeAsyncMethod()
{
await Task.Run(() =>
{
var user = _userService.Current;
...
});
}
Este problema en realidad no tiene nada que ver con el código asíncrono; tiene que ver con recuperar una variable de contexto de un subproceso de agrupación de hebras (sin solicitud). El mismo problema exacto ocurriría si tratas de hacerlo de forma síncrona.
El problema principal es que la versión asíncrona está usando una asincronía falsa . Esto es inapropiado, especialmente en ASP.NET. La solución es simplemente eliminar el código falso-asíncrono y hacerlo sincrónico (o realmente asíncrono, si realmente tiene un verdadero trabajo asíncrono que hacer):
void Method()
{
var user = _userService.Current;
...
}
La técnica recomendada en el blog vinculado (envolver el HttpContext
y proporcionarlo al hilo del trabajador) es extremadamente peligrosa. HttpContext
está diseñado para ser accedido solo desde un subproceso a la vez y AFAIK no es seguro para subprocesos. Así que compartirlo entre diferentes hilos es pedir un mundo de dolor.
Si el código de await
es incorrecto, entonces causa un problema similar. ConfigureAwait(false)
es una técnica comúnmente utilizada en el código de biblioteca para notificar al tiempo de ejecución que no es necesario que regrese a un contexto específico. Considere este código:
async Task MyMethodAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
var context = HttpContext.Current;
// Note: "context" is not correct here.
// It could be null; it could be the correct context;
// it could be a context for a different request.
}
En este caso, el problema es obvio. ConfigureAwait(false)
le dice a ASP.NET que el resto del método actual no necesita el contexto, y luego accede a ese contexto de inmediato. Sin embargo, cuando empiezas a usar valores de contexto en tus implementaciones de interfaz, el problema no es tan obvio:
async Task MyMethodAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
var user = _userService.Current;
}
Este código es igual de incorrecto pero no tan obvio, ya que el contexto está oculto detrás de una interfaz.
Por lo tanto, la pauta general es: use ConfigureAwait(false)
si sabe que el método no depende de su contexto (directa o indirectamente); de lo contrario, no utilice ConfigureAwait
. Si en su diseño es aceptable que las implementaciones de interfaz usen el contexto en su implementación, entonces cualquier método que llame a un método de interfaz no debe usar ConfigureAwait(false)
:
async Task MyMethodAsync()
{
await Task.Delay(1000);
var user = _userService.Current; // works fine
}
Mientras sigas esa guía, async
/ await
funcionará perfectamente con HttpContext.Current
.