正确使用HttpContext.Current.User与异步等待的方法

Mag*_*agu 32 c# asp.net-mvc httpcontext async-await asp.net-mvc-5

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

    // …
}
Run Code Online (Sandbox Code Playgroud)

我有最近重构的代码,以前我必须在每次调用服务时给用户.现在我读到这篇文章http://trycatchfail.com/blog/post/Using-HttpContext-Safely-After-Async-in-ASPNET-MVC-Applications.aspx,我不确定我的代码是否仍然有效.有谁有更好的方法来处理这个?我不想在每次请求时向用户提供服务.

Ste*_*ary 55

只要您的web.config设置正确,async/就await可以很好地使用HttpContext.Current.我建议设置httpRuntime targetFramework4.5删除所有"怪癖模式"行为.

一旦完成,普通async/ await将完美地工作.如果您正在另一个线程上工作或者您的await代码不正确,您将遇到问题.


一,"其他线程"问题; 这是您链接到的博客文章中的第二个问题.像这样的代码当然不能正常工作:

async Task FakeAsyncMethod()
{
  await Task.Run(() =>
  {
    var user = _userService.Current;
    ...
  });
}
Run Code Online (Sandbox Code Playgroud)

这个问题实际上与异步代码无关; 它与从(非请求)线程池线程中检索上下文变量有关.如果您尝试同步执行,则会出现完全相同的问题.

核心问题是异步版本使用异步.这不合适,特别是在ASP.NET上.解决方案是简单地删除伪异步代码并使其同步(或者真正异步,如果它实际上有真正的异步工作要做):

void Method()
{
  var user = _userService.Current;
  ...
}
Run Code Online (Sandbox Code Playgroud)

链接博客中推荐的技术(包装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.
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,问题是显而易见的.ConfigureAwait(false)告诉ASP.NET当前方法的其余部分不需要上下文,然后它立即访问该上下文.但是,当您在接口实现中开始使用上下文值时,问题并不那么明显:

async Task MyMethodAsync()
{
  await Task.Delay(1000).ConfigureAwait(false);
  var user = _userService.Current;
}
Run Code Online (Sandbox Code Playgroud)

这个代码同样错误但不是很明显错误,因为上下文隐藏在接口后面.

所以,一般的原则是:使用ConfigureAwait(false),如果你知道该方法不依赖于它的上下文(直接或间接); 否则,请勿使用ConfigureAwait.如果它在你的设计中可以接受的接口实现使用调用的接口方法应该在其实施的背景下,那么任何方法使用ConfigureAwait(false):

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

只要您遵循该指南,async/ await将与之完美配合HttpContext.Current.

  • 如果您想知道您的 ASP.NET 应用程序在哪个运行时下运行,请点击断点并使用静态属性:`HttpRuntime.TargetFramework`。 (2认同)