正确使用HttpContext.Current.User与异步等待的方法
我正在使用异步操作并使用像这样的HttpContext.Current.User
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 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 GetMessages(long id) { var channel = await this.ChannelService .FindOrDefaultAsync(id); return PartialView("_Messages", channel); } // … }
我有最近重构的代码,以前我必须在每次调用服务时给用户。 现在我读到这篇文章http://trycatchfail.com/blog/post/Using-HttpContext-Safely-After-Async-in-ASPNET-MVC-Applications.aspx ,我不确定我的代码是否仍然有效。 有谁有更好的方法来处理这个? 我不想在每次请求时向用户提供服务。
只要您的web.config
设置正确 , async
/ await
可以很好地与HttpContext.Current
配合使用。 我建议将httpRuntime
targetFramework
设置为4.5
以删除所有“怪癖模式”行为。
一旦完成,plain async
/ await
将完美地运行。 如果您正在另一个线程上工作或await
代码不正确,您将遇到问题。
一,“其他线程”问题; 这是您链接到的博客文章中的第二个问题。 像这样的代码当然不能正常工作:
async Task FakeAsyncMethod() { await Task.Run(() => { var user = _userService.Current; ... }); }
这个问题实际上与异步代码无关; 它与从(非请求)线程池线程中检索上下文变量有关。 如果您尝试同步执行此操作,则会出现完全相同的问题。
核心问题是异步版本使用假异步。 这不合适,特别是在ASP.NET上。 解决方案是简单地删除伪异步代码并使其同步(或者真正异步,如果它实际上有真正的异步工作要做):
void Method() { var user = _userService.Current; ... }
链接博客中推荐的技术(包装HttpContext
并将其提供给工作线程)非常危险。 HttpContext
被设计为一次只能从一个线程访问,而AFAIK根本不是线程安全的。 因此,在不同的线程之间分享它是一个受伤的世界。
如果await
代码不正确,则会导致类似的问题。 ConfigureAwait(false)
是库代码中常用的一种技术,用于通知运行时它不需要返回到特定的上下文。 考虑以下代码:
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. }
在这种情况下,问题是显而易见的。 ConfigureAwait(false)
告诉ASP.NET当前方法的其余部分不需要上下文,然后它立即访问该上下文。 但是,当您在接口实现中开始使用上下文值时,问题并不那么明显:
async Task MyMethodAsync() { await Task.Delay(1000).ConfigureAwait(false); var user = _userService.Current; }
这个代码同样错误,但并不明显错误,因为上下文隐藏在接口后面。
因此,一般准则是: 如果您知道该方法不依赖于其上下文(直接或间接),请使用ConfigureAwait(false)
); 否则,请勿使用ConfigureAwait
。 如果您的设计中接口实现在其实现中使用上下文是可接受的,那么调用接口方法的任何方法都不应使用ConfigureAwait(false)
:
async Task MyMethodAsync() { await Task.Delay(1000); var user = _userService.Current; // works fine }
只要您遵循该指南, async
/ await
将与HttpContext.Current
完美配合。
异步很好。 问题是当您将工作发布到其他线程时。 如果您的应用程序设置为4.5+,则异步回调将在原始上下文中发布,因此您还将拥有正确的HttpContext
等。
您不希望在不同的线程中访问共享状态,而使用Task
,您很少需要明确处理 – 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入到共享状态(例如HttpContext
,静态字段等)
如果您的ViewModels.DisplayChannel
是一个没有附加逻辑的简单对象,则没有问题。
如果您的Task
的结果引用了“某些上下文对象”,则可能会出现问题,例如, HttpContext.Current
。 这些对象通常附加到线程,但是await
之后的整个代码可以在另一个线程中执行。
请记住, UseTaskFriendlySynchronizationContext
并不能解决您的所有问题。 如果我们讨论的是ASP.NET MVC,那么这个设置可以确保Controller.HttpContext
包含正确的值,就像之前一样。 但它不能确保HttpContext.Current
包含正确的值,并且在await
之后它仍然可以为null 。
- MVC5 ViewModelvalidation远程
- Visual Studio 2017,无法调试或运行该应用程序
- 尝试激活’AuthController’时无法解析类型’Microsoft.AspNetCore.Identity.UserManager`的服务
- 如何使用不允许异步控制器操作的CMS处理异步API调用?
- System.Web.Mvc.HtmlHelper 不包含的定义
- ASP.NET MVC 3是否已准备好用于业务应用程序
- 如何使用库存标准ASP.NET MVC3网站获得自定义404和500错误页面?
- ASP.NET MVC:在不影响性能的情况下路由自定义slug
- 如何扩展AuthorizeAttribute并检查用户的角色