Почему User (как в User.Identity.Name) равен нулю в моем абстрактном базовом контроллере?
-
08-07-2019 - |
Вопрос
Я задавал связанный с этим вопрос, но перепутал название, и никто его не понял.Поскольку теперь я могу задать вопрос более точно, я решил переформулировать его в новом вопросе и закрыть старый.Прошу прощения за это.
Итак, что я хочу сделать, это передать данные (ник моего пользовательского пользователя, сохраненный в базе данных) в 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
уже равно нулю.Это не относится ко всем моим производящим контроллерам, так что это специфично для абстрактного базового контроллера.
Я устанавливаю User.Identity.Имя через FormsAuthentication в другом месте кода, но я думаю, что это не может быть проблемой - afaik User.Identity.Имя может быть нулевым, но не сам Пользователь.
Мне кажется, что HttpContext недоступен (поскольку также null ;-) и что я упускаю здесь простой, но важный момент.Кто-нибудь может дать мне несколько советов?Я был бы вам очень признателен.
Решение
Я предполагаю, что базовый конструктор контроллера не заполняет имя Пользователя, но это становится известно только позже, когда 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 равен null на момент построения.
Поэтому вам следует выполнить свой код где-нибудь в другом месте.
Другие советы
Ответ на эту проблему на самом деле очень прост. Я не могу выполнить код из конструктора по причинам, указанным Раймондом, но я могу сделать это вне конструктора.
Итак, я переопределил onActionExecuting () в базовом классе контроллера (я создал для него собственный атрибут, но переопределение метода также должно сработать), а затем выполнил поиск пользователей.
Теперь все работает как положено, и у меня нет повторного кода.
Свойство User не назначается до тех пор, пока не будет создан экземпляр Controller, но вы можете получить ранний доступ от вашего конструктора с помощью:
System.Web.HttpContext.Current.User
Можете ли вы взять это, используя что-то вроде:
HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;
Или HttpContext всегда пуст ??
Не могли бы вы установить httpContext через конструктор абстрактного класса? и использовать это таким образом?
Спасибо, Раймонд. Я был слишком уставшим, чтобы видеть очевидное. @ Кини: Да, контекст всегда нулевой. Раймон указал на причину. В любом случае, я тоже не понимаю, почему: -)
Мое текущее рабочее решение (хотя и не то, что я хотел) - это Атрибут, который я использую для украшения всех своих действий контроллера. Вот реализация:
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, поскольку использование атрибутов для этого определенно означает повторение себя. Может быть, какой-то перехватчик -interceptors.aspx "rel =" nofollow noreferrer "> интересная идея ) или хук может помочь.
Приветствия для этого.
Я делаю это в реализации базового контроллера, и она работает как положено.
public abstract class BaseController : Controller
{
public bool LoggedOn
{
get { return User.Identity.IsAuthenticated; }
}
}
Это всегда возвращает true или false для меня, поэтому Пользователь! = null
Мастерфу: Я сделал что-то похожее с вашей помощью, хочу, чтобы это помогло последним посетителям. В моем случае мне нужно создать репозиторий контроллеров для разных пользователей, но в конструкторе контроллеров (основной) пользователь не готов. Поэтому я создал атрибут для контроллеров:
[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);
}
}
}
Я поместил коды создания хранилища в отдельный метод, в случае, если другие атрибуты могут захотеть использовать (основного) пользователя до того, как этот атрибут будет запущен.
Вызов из конструктора слишком рано в конвейере 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;
}
}
Вставьте IPrincipal
, если вам нужен Пользователь
в конструкторе.
// startup.cs
// Inject IPrincipal
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
Затем добавьте как IPrincipal
в свой конструктор. Обратите внимание, что он гарантированно будет ClaimsPrincipal
в ASPNET, потому что это HttpContext.User
.