从 ASP.NET MVC 控制器构造函数访问时 IPrincipal 属性 为空

IPrincipal property is null when accessed from ASP.NET MVC controller constructor

以下示例代码抛出 NullReferenceException 消息 Object reference not set to an instance of an object.。错误指的是 User 属性 即 null

基地控制器

public class BaseController : Controller
{
    public string UserName
    {
        get
        {
            return User.Identity.Name;
        }
    }
}

家庭控制器

[Authorize]
public class HomeController : BaseController
{
    private string username { get; set; }

    public HomeController()
    {
        username = UserName;
    }
    // GET: Home
    public ActionResult Index()
    {
        ViewBag.UserName = username;
        return View();
    }
}

另一方面,如果直接在操作中访问 UserName 属性 那么它工作正常。因此,如果将代码更改为:

家庭控制器

[Authorize]
public class HomeController : BaseController
{
    // GET: Home
    public ActionResult Index()
    {
        ViewBag.UserName = this.UserName;
        return View();
    }
}

知道为什么吗?

更新:

所以,真正的问题是在构建控制器的过程中HttpContext不可用。我真的想知道为什么?有什么解释吗?另外,如果我需要使用构造函数 DI 来实例化依赖项 class 又需要知道当前登录的用户怎么办,例如:

[Authorize]
public class HomeController : BaseController
{
    private IMyService service;

    public HomeController(IMyService service)
    {
        this.service = service;
        this.service.UserName = UserName;
    }

    // GET: Home
    public ActionResult Index()
    {
        // use service here ...
        return View();
    }
}

是因为你的构造函数中没有HttpContext。直到实例化控制器后,才会分配用户 属性。

在那里尝试使用 System.Web.HttpContext.Current.User

您无权访问构造函数中的 HttpContext。如果你想在一个地方添加这样的检索逻辑,你可以通过以下方式之一来完成。


直接调用Base Controller

从您的基本控制器访问 UserName 属性,无需将其重置为派生控制器上的 属性 或字段。

[Authorize]
public class HomeController : BaseController
{
    // GET: Home
    public ActionResult Index()
    {
        ViewBag.UserName = UserName;
        return View();
    }
}

挂接到 OnActionExecuting 而不是 ctor

Controller.OnActionExecuting 中设置值而不是构造函数,因为此时您可以访问 HttpContext。在方法中添加一个 override 来执行此操作。


扩展方法

改为添加方法扩展。我更喜欢这种方法,因为我不喜欢控制器继承,而是从标准 Mvc 控制器继承。

public static class ControllerExtension {
  public static string UserName(this Controller controller) {
    return controller.User.Identity.Name;
  }
}

你的代码变成

[Authorize]
public class HomeController : Controller
{
    // GET: Home
    public ActionResult Index()
    {
        ViewBag.UserName = this.UserName();
        return View();
    }
}

编辑

... where constructor DI is used to instantiate a parameter which in turn has a UserName property that needs to be set...

您可能希望将 HttpContextBase 实例注入到您的服务中,并让它以这种方式检索经过身份验证的用户名。您不希望 Controller 实例需要向服务提供用户名,因为这会创建紧密耦合的代码并破坏 SoC(关注点分离)。

class MyService : IMyService
{
    public string UserName {get;}
    // inject HttpContextBase
    public MyService(HttpContextBase context){
        this.UserName = context.User.Identity.Name;
    }
}