Почему пользователь (как в User.Identity.Name) нулевой в моем абстрактном базовом контроллере? - PullRequest
24 голосов
/ 10 января 2009

Я задавал связанный вопрос, но испортил название, и никто не поймет его. Поскольку теперь я могу более точно задать вопрос, я решил переформулировать его в новом вопросе и закрыть старый. Извините за это.

Итак, я хочу передать данные (псевдоним моего пользователя, сохраненный в БД) в 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 уже равен нулю. Это не относится ко всем моим производным контроллерам, так что это характерно для абстрактного базового контроллера.

Я устанавливаю User.Identity.Name с помощью FormsAuthentication в другом месте кода, но я думаю, что это не может быть проблемой - afaik User.Identity.Name может быть нулевым, но не самого пользователя.

Мне кажется, что HttpContext недоступен (поскольку также имеет значение null ;-), и здесь я упускаю простой, но важный момент. Кто-нибудь может дать мне несколько советов? Я был бы очень признателен.

Ответы [ 9 ]

23 голосов
/ 19 января 2009

Ответ на этот вопрос на самом деле очень прост. Я не могу выполнить код из конструктора по причинам, указанным Раймондом, но я могу сделать это вне конструктора.

Итак, я переопределил onActionExecuting () в базовом классе контроллера (я создал для него собственный атрибут, но переопределение метода также должно сработать), а затем выполнил поиск пользователя.

Теперь все работает как положено, и у меня нет повторного кода.

17 голосов
/ 26 ноября 2016

Свойство User не назначается до тех пор, пока не будет создан экземпляр контроллера, но вы можете получить ранний доступ от вашего конструктора с помощью:

System.Web.HttpContext.Current.User
14 голосов
/ 10 января 2009

Я полагаю, что базовый конструктор контроллера не заполняет пользователя, но он становится известен только позже, когда для контроллера установлен 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 является нулевым во время построения.

Так что вы должны выполнить свой код где-то еще.

4 голосов
/ 10 января 2009

Спасибо, Раймонд. Я был слишком уставшим, чтобы видеть очевидное. @ Кини: Да, контекст всегда нулевой. Раймон указал на причину. В любом случае, спасибо, я тоже не понял, почему: -)

Мое текущее рабочее решение (хотя и не то, что я хотел) - это Атрибут, который я использую для украшения всех своих действий контроллера. Вот реализация:

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

Я буду изучать, как выполнить этот код так, чтобы он следовал принципу СУХОЙ, поскольку использование атрибутов для этого определенно означает повторение самого себя. Может быть, какой-то перехватчик ( интересная идея ) или хук могут помочь.

Приветствия за это.

4 голосов
/ 10 января 2009

Можете ли вы взять это, используя что-то вроде:

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

Или HttpContext всегда пуст ?? ?? 1004 *

Не могли бы вы установить httpContext через конструктор абстрактного класса? и использовать это таким образом?

0 голосов
/ 29 января 2019

Внедрить IPrincipal, если вам нужно User в конструкторе.

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

Затем добавьте как IPrincipal в свой конструктор. Обратите внимание, что он гарантированно будет ClaimsPrincipal с ASPNET - потому что это то, что HttpContext.User.

Аналогичный вопрос

0 голосов
/ 16 февраля 2016

Вызов из конструктора слишком рано в конвейере 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 голосов
/ 17 марта 2013

к Мастерфу: Я сделал что-то похожее с вашей помощью, хочу, чтобы это помогло последним посетителям. В моем случае мне нужно создать репозиторий контроллеров для разных пользователей, но в конструкторе контроллеров (основной) пользователь не готов. Поэтому я создал атрибут для контроллеров:

[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 голосов
/ 12 января 2009

Я делаю это в реализации базового контроллера, и она работает как положено.

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

Для меня это всегда возвращает true или false, поэтому User != null

...