【问题标题】:IPrincipal property is null when accessed from ASP.NET MVC controller constructor从 ASP.NET MVC 控制器构造函数访问时,IPrincipal 属性为空
【发布时间】:2017-05-25 03:44:17
【问题描述】:

以下示例代码抛出 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 来实例化一个依赖类,而该依赖类又需要知道当前登录的用户,例如:

[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();
    }
}

【问题讨论】:

  • Controller 类的User 属性可能在构造函数运行时尚未设置,但在调用Index() 时已设置。
  • 关于为什么的任何想法或文档链接?

标签: c# asp.net asp.net-mvc iprincipal


【解决方案1】:

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


直接调用基本控制器

从您的基本控制器访问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();
    }
}

编辑

...其中构造函数 DI 用于实例化一个参数,该参数又具有需要设置的 UserName 属性...

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

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

【讨论】:

  • 感谢您提供的替代方案。诚然,在我提供的代码中重置 UserName 属性没有多大意义,仅用于演示目的。 FWIW,我更新了原始问题以展示构造函数 DI 用于实例化参数的真实场景,该参数又具有需要设置的 UserName 属性。 service 然后用于许多操作。你会如何处理这种情况? @伊戈尔
  • @dpant - 您可能希望将HttpContextBase 注入您的服务并让它以这种方式检索经过身份验证的用户名。控制器不应该提供它,因为它会破坏 SOC。请参阅上面的更新以获取说明。
  • 很好的建议。我刚刚用 StructureMap 对其进行了测试,它工作正常。 FWIW,这是HttpContextBase的DI注册:For<HttpContextBase>().Use(() => new HttpContextWrapper(HttpContext.Current));
【解决方案2】:

这是因为你在构造函数中没有HttpContext。直到实例化 Controller 之后才分配 User 属性。

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

【讨论】:

    猜你喜欢
    • 2010-11-03
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 2018-05-25
    • 1970-01-01
    相关资源
    最近更新 更多