c# - 将 HttpContext.Current.User 与异步等待一起使用的正确方法

标签 c# asp.net-mvc asp.net-mvc-5 async-await httpcontext

我正在使用异步操作并像这样使用 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<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);
    }

    // …
}

我最近重构了代码,以前我必须在每次调用该服务时向用户提供。 现在我读这篇文章https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/而且我不确定我的代码是否仍然有效。 有没有人有更好的方法来处理这个问题?我不想向用户提供服务的每个请求。

最佳答案

只要你的web.config settings are correct , async/awaitHttpContext.Current 完美配合。我建议将 httpRuntime targetFramework 设置为 4.5 以删除所有“怪癖模式”行为。

一旦完成,普通的 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 当前方法的其余部分不需要上下文,然后它会立即访问该上下文。但是,当您开始在接口(interface)实现中使用上下文值时,问题就不那么明显了:

async Task MyMethodAsync()
{
  await Task.Delay(1000).ConfigureAwait(false);
  var user = _userService.Current;
}

这段代码同样错误,但没有那么明显错误,因为上下文隐藏在接口(interface)后面。

因此,一般准则是:如果您知道该方法不依赖于它的上下文(直接或间接),则使用ConfigureAwait(false);否则,请勿使用 ConfigureAwait。如果在您的设计中让接口(interface)实现在其实现中使用上下文是可以接受的,那么调用接口(interface)方法的任何方法都应该使用ConfigureAwait(false):

async Task MyMethodAsync()
{
  await Task.Delay(1000);
  var user = _userService.Current; // works fine
}

只要您遵循该准则,async/await 将与 HttpContext.Current 完美配合。

关于c# - 将 HttpContext.Current.User 与异步等待一起使用的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28427675/

相关文章:

c# - 如何在按钮 onclick 事件 mvc4 中调用方法?

c# - 使用 RouteAttribute 时无法提交表单,提交到错误的 Action

c# - ASP.NET MVC ActionFilter 不会阻止其他过滤器运行

javascript - 从下拉列表中选择项目时的 Firebug 行为

c# - LINQ 项目属性转换为包含的新匿名类型

c# - 查看线程未正确结束

c# - Windows Phone 8.1 自定义键盘

asp.net-mvc - ASP.NET MVC中的安静的bot检测和筛选

asp.net-mvc - 我的问题是 "Server cannot set status after HTTP headers have been sent."

c# - 通过 C# 应用程序连接到 SAP