ASP.NET MVC Пользовательские поля пользователя на каждой странице
Предыстория: я создаю все больше и больше веб-приложений, в которых дизайнеры / создатели шаблонов решают, что добавление "картинки профиля" и некоторых других данных, связанных с пользователем, конечно, только когда кто-то вошел в систему.
Как и большинство разработчиков ASP.NET MVC, я использую viewmodels, чтобы предоставить макетам бритвы информацию, которую мне нужно показать, полученную из репозиториев и др.
Легко показать имя пользователя с помощью
HttpContext.Current.User.Identity.Name
Что если я хочу показать на этих страницах информацию, которая была сохранена в моем резервном хранилище данных? Настраиваемые поля в классе ApplicationUser, такие как имя подразделения или URL-адрес CDN изображения профиля.
(для простоты давайте предположим, что я использую Identity Framework с Entity Framework (базой данных SQL), содержащей мои ApplicationUsers)
Вопрос
Как вы решаете это:
- Без замещения дерева представления / модели контроллера (например, создание BaseViewModel или BaseController для заполнения / предоставления этой информации?
- Без необходимости обходить базу данных каждый запрос страницы для этих деталей?
- Без запроса базы данных, если пользователь не вошел в систему?
- Если вы не можете использовать данные SESSION (поскольку мои приложения часто масштабируются на нескольких экземплярах Azure - прочитайте, почему это невозможно, - меня не интересует кэширование SQL или Redis.
Я думал об использовании партиалов, которые создают свою собственную модель представления - но это все равно будет обходить базу данных SQL при каждой загрузке страницы. На данный момент данные сессий были бы безопасны, но при масштабировании в лазурном режиме это тоже не так. Любая идея, что будет моей лучшей ставкой?
TLDR;
Я хочу отображать информацию о профиле пользователя (ApplicationUser) на каждой странице моего приложения, если пользователи вошли в систему (anon access = позволено). Как мне показать эту информацию, не запрашивая базу данных каждый запрос страницы? Как мне сделать это без класса Session? Как мне сделать это без создания базовых классов?
4 ответа
Лучший способ использования Identity - использование утверждений для хранения пользовательских данных о пользователе. Ответ Сэма очень близок к тому, что я здесь говорю. Я уточню немного больше.
На ApplicationUser
класс у вас есть GenerateUserIdentityAsync
метод, который используется для создания ClaimsIdentity
пользователя:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
Это добавляет пары ключ-значение к идентификатору пользователя, который в конечном итоге сериализуется и шифруется в файле cookie аутентификации - это важно помнить.
После того, как пользователь вошел в систему, эта личность доступна вам через HttpContext.Current.User.Identity
- этот объект на самом деле ClaimsIdentity
с претензиями, взятыми из куки. Так что все, что вы указали в заявке на время входа в систему, для вас, без необходимости углубляться в вашу базу данных.
Чтобы получить данные из претензий, я обычно делаю методы расширения IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
Таким образом, вы можете получить доступ к данным претензий из ваших представлений Razor: User.GetFirstName()
И эта операция действительно быстрая, потому что она не требует каких-либо разрешений объектов из вашего DI-контейнера и не запрашивает вашу базу данных.
Единственное препятствие - когда значения в хранилище фактически обновляются, значения в утверждениях в файле cookie аутентификации не обновляются до тех пор, пока пользователь не выйдет из системы и не выполнит вход. Но вы можете заставить это самостоятельно через IAuehtenticationManager.Signout()
и немедленно подпишите их обратно с обновленными значениями претензий.
Вы можете хранить вашу дополнительную информацию в качестве претензий. В вашем методе входа в систему введите свои данные для сгенерированной личности. Например, если вы используете конфигурацию Identity по умолчанию, вы можете добавить свои претензии в ApplicationUser.GenerateUserIdentityAsync()
метод:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
И во всем приложении вы можете получить доступ к этой информации, прочитав текущие утверждения пользователей. На самом деле HttpContext.Current.User.Identity.Name
использует тот же подход.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
Я думаю, что "как" немного субъективно, так как, вероятно, есть много возможных способов сделать это, но я решил эту проблему, используя тот же шаблон, что и HttpContext. Я создал класс ApplicationContext со статическим свойством экземпляра, который возвращает экземпляр, используя DI. (Вы можете изменить свойство, чтобы генерировать и сам синглтон, если по какой-то причине вы не используете DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Затем вы просто ссылаетесь на свойство "Текущий" в своем представлении напрямую.
@ApplicationContext.Current.GetUsername()
Это решит все ваши требования, кроме #2. Вызов базы данных может не добавить достаточно значительных накладных расходов, чтобы вообще избежать их использования, но если вам это потребуется, то единственным вариантом будет использование какой-либо формы кэширования пользовательских данных при первом запросе.
Просто внедрите ChildAction с кешированием и меняйте его по логину