ASP.NET MVC Пользовательские поля пользователя на каждой странице

Предыстория: я создаю все больше и больше веб-приложений, в которых дизайнеры / создатели шаблонов решают, что добавление "картинки профиля" и некоторых других данных, связанных с пользователем, конечно, только когда кто-то вошел в систему.

Как и большинство разработчиков ASP.NET MVC, я использую viewmodels, чтобы предоставить макетам бритвы информацию, которую мне нужно показать, полученную из репозиториев и др.

Легко показать имя пользователя с помощью

HttpContext.Current.User.Identity.Name

Что если я хочу показать на этих страницах информацию, которая была сохранена в моем резервном хранилище данных? Настраиваемые поля в классе ApplicationUser, такие как имя подразделения или URL-адрес CDN изображения профиля.

(для простоты давайте предположим, что я использую Identity Framework с Entity Framework (базой данных SQL), содержащей мои ApplicationUsers)

Вопрос

Как вы решаете это:

  1. Без замещения дерева представления / модели контроллера (например, создание BaseViewModel или BaseController для заполнения / предоставления этой информации?
  2. Без необходимости обходить базу данных каждый запрос страницы для этих деталей?
  3. Без запроса базы данных, если пользователь не вошел в систему?
  4. Если вы не можете использовать данные 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 с кешированием и меняйте его по логину

Другие вопросы по тегам