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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/430704/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 03:06:04  来源:igfitidea点击:

Why is User (as in User.Identity.Name) null in my abstract base controller?

c#asp.net-mvccontrolleruser-controlshttpcontext

提问by Masterfu

I was asking a related question but messed the title up and no-one would understand it. Since I am able now to ask the question more precisely, I decided to reformulate it in a new question and close the old one. Sorry for that.

我在问一个相关的问题,但把标题弄乱了,没有人会理解。由于我现在能够更准确地提出问题,因此我决定在一个新问题中重新表述它并结束旧问题。对不起。

So what I want to do is passing data (my custom user's nickname as stored in the db) to the LoginUserControl. This login gets rendered from the master page via Html.RenderPartial(), so what I really need to do is making sure that, say ViewData["UserNickname"] is present on every call. But I don't want to populate ViewData["UserNickname"] in each and every action of every controller, so I decided to use this approachand create an abstract base controller which will do the work for me, like so:

所以我想要做的是将数据(存储在数据库中的我的自定义用户的昵称)传递给 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;
        }
    }

This way, whatever my deriving Controller does, the user information will already be present.

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

So far, so good. Now for the problem:

到现在为止还挺好。现在的问题:

I can't call User.Identity.Name because Useris already null. This is not the case in all of my deriving controllers, so this is specific for the abstract base controller.

我不能调用 User.Identity.Name 因为User已经为空。在我的所有派生控制器中都不是这种情况,因此这是特定于抽象基础控制器的。

I am setting the User.Identity.Name via FormsAuthentication at another place in the code, but I think this can't be the problem - afaik User.Identity.Name can be null, but not User itself.

我在代码的另一个地方通过 FormsAuthentication 设置 User.Identity.Name,但我认为这不是问题 - afaik User.Identity.Name 可以为空,但不是用户本身。

It looks to me like the HttpContext is not available (since also null ;-) and that I am missing a simple yet important point here. Can anyone give me some hints? I would really appreciate it.

在我看来 HttpContext 不可用(因为也是 null ;-),而且我在这里错过了一个简单但重要的点。谁能给我一些提示?我真的很感激。

采纳答案by Raymond Roestenburg

My guess would be that the Controller's base constructor is not filling in the User, but that it is only known later when the ControllerContext is set for the Controller. You should check this in the documentation about the lifecycle of an MVC application, (the one herewill probably do, although it might be a bit out of date since it's for the preview version), or just check the source code of MVC.

我的猜测是控制器的基本构造函数没有填写用户,但只有在为控制器设置 ControllerContext 时才知道。您应该在有关 MVC 应用程序生命周期的文档中检查这一点(这里可能会这样做,虽然它可能有点过时,因为它是预览版),或者只是检查 MVC 的源代码。

from the code that I have of MVC (also a preview version, but that should be fine): (In Controller)

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

 public IPrincipal User {
            get {
                return HttpContext == null ? null : HttpContext.User;
            }
        }

...

...

public HttpContextBase HttpContext {
        get {
            return ControllerContext == null ? null : ControllerContext.HttpContext;
        }
    }

I don't see en an implementation of a default constructor in the code. That would prove that the ControllerContext is null at the time of construction.

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

So you should execute your code somewhere else.

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

回答by keeney

Can you grab this using something like:

你能不能用这样的东西抓住这个:

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

Or is the HttpContext always empty??

还是 HttpContext 总是空的??

Could you set the httpContext through the constructor of the abstract class? and use it this way?

能不能通过抽象类的构造函数来设置httpContext?并以这种方式使用它?

回答by Masterfu

Thanks Raimond. I was too tired to see the obvious. @Keeney: Yes the context is always null. Raimond pointed out why. Thanks anyway, I didn't see why too :-)

谢谢雷蒙德。我太累了,看不到明显的东西。@Keeney:是的,上下文始终为空。雷蒙德指出了原因。无论如何,谢谢,我也不明白为什么:-)

My current working solution (albeit not what I wanted) is a Attribute that I use to decorate all my controller actions. Here is the implementation:

我当前的工作解决方案(尽管不是我想要的)是我用来装饰所有控制器操作的属性。这是实现:

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" };
        }
    }

I will be looking into how to get that code executed in a way that follows the DRY principle, since using attributes for that definitely means repeating oneself. Maybe some sort of interceptor (interesting idea) or hook might help.

我将研究如何以遵循 DRY 原则的方式执行该代码,因为为此使用属性肯定意味着重复自己。也许某种拦截器(有趣的想法)或钩子可能会有所帮助。

Cheers for that.

为此干杯。

回答by Schotime

I am doing this in a basecontroller implementation and it works as expected.

我在 basecontroller 实现中这样做,它按预期工作。

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

This always returns true or false for me so User != null

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

回答by Masterfu

The answer to this problem is actually quite simple. I can't execute the code from within the constructor for reasons pointed out by Raimond, but I can do it outside the constructor.

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

So what I did was overriding onActionExecuting() in the base controller class (I created a custom Attribute for it, but just overriding the method should also work) and then do my user lookup from there.

所以我所做的是覆盖基本控制器类中的 onActionExecuting() (我为它创建了一个自定义属性,但覆盖该方法也应该有效),然后从那里进行我的用户查找。

Now it works as expected and I have no repeated code.

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

回答by cheny

to Masterfu: I did something similiar with your help, wish that can help latter visitors. In my case, i need to create reposiotry of controllers for different users, yet in the constructor of controllers, (principal)User is not ready. So i created a attribute for controllers:

to Masterfu:我在你的帮助下做了类似的事情,希望可以帮助到后来的访客。在我的情况下,我需要为不同的用户创建控制器的存储库,但在控制器的构造函数中,(主体)用户还没有准备好。所以我为控制器创建了一个属性:

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

the _repository, indeed, is not a private variable of controller, but somethign create by the attribute:

_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);
        }
    }
}

I put codes of creating the repository in a seperate method, in case of that other attributes may want to use (principal)User before this attribute being triggered.

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

回答by Simon Novak

Calling from a constructor is too soon in the MVC pipeline.

在 MVC 管道中从构造函数调用为时过早。

Moving code to OnAuthorization, you get authorized user in a parameter. Worked for me!

将代码移至 OnAuthorization,您将在参数中获得授权用户。为我工作!

From your example I would do something like this:

从你的例子中,我会做这样的事情:

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;
    }


}

回答by Austin Hummel

The User property is not assigned until after the Controller has been instantiated, but you can gain early access from your constructor with:

User 属性直到 Controller 被实例化后才会分配,但您可以通过以下方式从构造函数获得早期访问权限:

System.Web.HttpContext.Current.User

回答by Simon_Weaver

Inject IPrincipalif you need Userin the constructor.

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

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

Then add as IPrincipalin your constructor. Note that it is guaranteed to be ClaimsPrincipalwith ASPNET - because that's what HttpContext.Useris.

然后IPrincipal在构造函数中添加 as 。请注意,它保证ClaimsPrincipal与 ASPNET一起使用 - 因为HttpContext.User就是这样。

Similar question

类似问题