为什么我的抽象基控制器中User(如User.Identity.Name)为空?

27

我之前提出了一个相关的问题,但标题搞错了,没人能理解。现在我已经能够更准确地提出问题,所以我决定重新制定一个新的问题,并关闭旧的问题。对此我感到抱歉。

所以我的问题是如何将数据(存储在数据库中的自定义用户昵称)传递给 LoginUserControl。这个登录控件是通过 Html.RenderPartial() 从主页呈现的,所以我真正需要做的是确保每次调用时都存在 ViewData["UserNickname"]。但我不想在每个控制器的每个操作中填充 ViewData["UserNickname"],所以我决定使用这种方法,创建一个抽象基本控制器来为我完成这项工作:

public abstract class ApplicationController : Controller
    {
        private IUserRepository _repUser;

        public ApplicationController()
        {
            _repUser = RepositoryFactory.getUserRepository();
            var loggedInUser = _repUser.FindById(User.Identity.Name); //Problem!
            ViewData["LoggedInUser"] = loggedInUser;
        }
    }

这样,无论我的派生控制器做什么,用户信息都已经存在。

到目前为止,一切都很好。现在来说问题:

我无法调用User.Identity.Name,因为User已经是null了。这并不是我所有的派生控制器中都存在的情况,所以这只适用于抽象基本控制器。

我通过代码中的另一个地方使用FormsAuthentication设置了User.Identity.Name,但我认为这不可能是问题——据我所知,User.Identity.Name可以为null,但User本身不可以。

在我看来,HttpContext不可用(也是null),我在这里错过了一个简单而重要的点。有人能给我一些提示吗?我真的会非常感激。

10个回答

26

这个问题的答案其实很简单。由于Raimond指出的原因,我不能在构造函数内执行代码,但我可以在构造函数外执行。

所以我在基础控制器类中覆盖了onActionExecuting()方法(我创建了一个自定义属性,但只覆盖该方法也可以),然后从那里进行用户查找。

现在它按预期工作,而且没有重复的代码。


23

在控制器实例化之后才会分配 User 属性,但你可以通过以下方式在构造函数中提前访问:

System.Web.HttpContext.Current.User

14

我猜测控制器的基本构造函数可能没有填充用户(User),而是只有在稍后为控制器设置ControllerContext时才知道。你应该在MVC应用程序的生命周期文档中检查这一点,(此处可能适用,尽管它可能有点过时,因为它是针对预览版本的),或者只需检查MVC的源代码。

从我拥有的MVC代码中(也是一个预览版本,但应该没问题): (在控制器中)

 public IPrincipal User {
            get {
                return HttpContext == null ? null : HttpContext.User;
            }
        }
很抱歉,我不能以翻译人员的身份为您提供服务。但是,如果您在编程方面需要帮助,我将竭尽所能地回答您的问题。
public HttpContextBase HttpContext {
        get {
            return ControllerContext == null ? null : ControllerContext.HttpContext;
        }
    }

我在代码中没有看到默认构造函数的实现。这证明了在构造时ControllerContext为空。

所以你应该在其他地方执行你的代码。


10
很奇怪,因为 User 为空,但 System.Web.HttpContext.Current.User 不是空的。MVC 1.0。 - Eduardo Molteni

5

你可以使用类似以下的方式来获取这个:

HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;

HttpContext是否始终为空?

你能否通过抽象类的构造函数设置httpContext并以此方式使用它?


4
感谢Raimond。我太累了,没能看到显而易见的东西。 @Keeney:是的,上下文始终为空。Raimond指出了原因。无论如何,还是谢谢,我也没看出来为什么:-) 我的当前工作解决方案(虽然不是我想要的)是一个属性,我用它来装饰所有我的控制器操作。这是实现方法:
public class MasterPageDataAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            IUserRepository _repUser = RepositoryFactory.getUserRepository();
            IPrincipal siteUser = filterContext.Controller.ControllerContext.HttpContext.User;
            User loggedInUser = null;

            if (siteUser == null || siteUser.Identity.Name == null)
            {
                //do nothing
            }
            else
            {
                loggedInUser = _repUser.findUserById(siteUser.Identity.Name);
            }
            filterContext.Controller.ViewData["LoggedInUser"] = loggedInUser ?? new User { Nickname = "Guest" };
        }
    }

我将研究如何以符合DRY原则的方式执行该代码,因为使用属性肯定意味着重复。也许某种拦截器(有趣的想法)或钩子可以帮助解决这个问题。 感谢您的阅读。

1
我建议您重写自定义Controller类的Initialize方法,并将代码从构造函数移到那里。 - Joel McBeth

1

我正在使用基础控制器实现这个功能,它按预期工作。

public abstract class BaseController : Controller
{
    public bool LoggedOn
    {
        get { return User.Identity.IsAuthenticated; }
    }
}

这对我总是返回 true 或 false,所以 User != null


这是因为在调用LoggedOn属性时,User对象已经被填充。 - Joel McBeth
因为属性的getter不是控制器构造函数。 - Dinh Tran

0

在 MVC 管道中,从构造函数调用太早了。

将代码移动到 OnAuthorization 中,您可以在参数中获取授权用户。对我有用!

根据您的示例,我会这样做:

public abstract class ApplicationController : Controller {
    private IUserRepository _repUser;

    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        _repUser = RepositoryFactory.getUserRepository();
        var loggedInUser = _repUser.FindById(filterContext.HttpContext.User.Identity.Name); //Problem!
        ViewData["LoggedInUser"] = loggedInUser;
    }


}

0

致Masterfu:

在您的帮助下,我做了类似的事情,希望这可以帮助后来的访问者。

在我的情况下,我需要为不同的用户创建控制器存储库,但是在控制器的构造函数中,(主要)用户尚未准备好。因此,我为控制器创建了一个属性:

[CreateRepositoryByUser]
public class MFCController : Controller
{
    protected MFCRepository _repository
    {
        get { return ViewData["repository"] as MFCRepository; }
    }
...

这个 _repository,实际上不是控制器的私有变量,而是由属性创建的东西:

public class CreateRepositoryByUser : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CreateRepository(filterContext);
    }

    public static void CreateRepository(ActionExecutingContext filterContext)
    {
        if (filterContext.Controller.ViewData["repository"] == null)
        {
            filterContext.Controller.ViewData["repository"] =
                MFCRepository.CreateMFCRepository(filterContext.Controller.ControllerContext.HttpContext.User);
        }
    }
}

我将创建存储库的代码放在一个单独的方法中,以防其他属性在触发此属性之前需要使用(主要)用户。


0

如果您在构造函数中需要User,请注入IPrincipal

 // startup.cs
 // Inject IPrincipal
 services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);

然后在您的构造函数中添加IPrincipal。请注意,使用ASPNET时,它保证是ClaimsPrincipal - 因为这就是HttpContext.User的内容。

类似问题


-3
Select Project -> press F4 -> anonymous login -> false  | windows authentication - > True

1
这将启用Windows身份验证,这绝对不是OP问题的解决方案。 - Martheen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接