Провайдер профилей и его переопределение

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Если провайдер членства помогает системе определить, что это за пользователь, его идентифицировать, провайдер ролей указывает системе на статус пользователя и наделяет его определенные правами доступа, то провайдер профилей определят набор дополнительных свойств пользователя. К подобным дополнительным свойствам могут относиться, например, рост, возраст, вес, должность и т.д.

Использовать профили очень просто:

// получение
ViewBag.Name = HttpContext.Profile["Name"];
// установка
string name = "Bill Gates";
HttpContext.Profile["Name"]=name;

Однако если вы так напишите в приложении, то скорее всего получите ошибку, так как перед использованием свойство Name должно быть определено. Теперь посмотрим, как все это должно быть определено и как все это использовать.

При использовании универсальных провайдеров (например, в проектах по шаблону Basic) у нас в файле web.config уже подключен стандартный провайдер профилей:

<profile defaultProvider="DefaultProfileProvider">
    <providers>
      <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, 
			Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
			connectionStringName="DefaultConnection" applicationName="/" />
    </providers>
</profile>

Добавим в узел profile определение профиля, изменив данный узел следующим образом:

<profile defaultProvider="DefaultProfileProvider">
      <providers>
        <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, 
		System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
		connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
      <properties>
        <add name="FirstName" type="System.String" />
        <add name="LastName" type="System.String" />
        <add name="Age" type="System.Int32"/>
      <group name="Contact">
        <add name="Website" type="System.String"/>
        <add name="Email" type="System.String"/>
      </group>
      </properties>
</profile>

Определение свойств профиля помещается в узел properties. Определение свойства заключается в использовании элемента add, в котором мы определяем название свойства через атрибут name и его тип через атрибут type. В качестве типа могут выступать примитивные типы .NET.

Здесь также мы можем определить подгруппу свойств с помощью элемента group. В дальнейшем мы можем использовать данные свойства профиля:

[Authorize]
public class HomeController : Controller
{
    public string Index()
    {
        HttpContext.Profile["FirstName"] = "Иван";
        HttpContext.Profile["LastName"] = "Петров";

        return "Добро пожаловать, " + HttpContext.Profile["FirstName"] + "  " + 
            HttpContext.Profile["LastName"];
    }
}

Определив в файле web.config свойства профиля, мы можем к ним обратиться, например присвоить: HttpContext.Profile["FirstName"] . Причем до присвоения эти свойства ничего не несут. А затем мы обратно можем получить.

Возникает вопрос, а куда это все сохраняется? При работе с универсальными провайдерами у нас создается в базе данных автоматически набор таблиц, одна из которых предназначена для хранения профилей - Profiles.

Перед использованием профилей она пуста и ничего в ней нет. Но после присвоения в ней появляются соответствующие данные. Например, в моем случае это будут следующие данные:

Названия установленных свойств у нас оказываются в поле PropertyNames, а их значения - в поле PropertyValueStrings. Кроме того, в таблице еще есть ряд столбцов, из которых хотелось бы выделить UserId. Это поле содержит ключ пользователя, которое имеет по этому полю связано с другими таблицами. А в качестве пользователя выступает текущий залогинившийся пользователь, который вошел в систему и обратился к методу Index контроллера Home.

Чтобы обратиться к свойствам, которые указаны в группе, мы обращаемся к ним через эту группу:

HttpContext.Profile.GetProfileGroup("Contact")["Email"] = "ivpetrov5@mail.ru";
ViewBag.Email = HttpContext.Profile.GetProfileGroup("Contact")["Email"];

Переопределение провайдера профилей

В данном случае мы продолжим работу с проектом, где мы переопределяли универсальные провайдеры членства и ролей. Для начала добавим модель, которая будет содержать все свойства профиля. Пусть эта модель будет называться Profile:

	public class Profile
    {
        public int Id { get; set; }

        // Внешний ключ для связи с пользователем
        public int UserId { get; set; }
        public virtual User User { get; set; }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public DateTime LastUpdateDate { get; set; }  
    }

И изменим класс контекста данных, чтобы он мог получать данные профилей:

	public class UserContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Role> Roles { get; set; }
        public DbSet<Profile> Profiles { get; set; }
    }

Теперь нам нужно место, где мы будем хранить данные профилей. Для этого в базе данных создадим таблицу Profiles со следующим определением:

CREATE TABLE [dbo].[Profiles]
(
	[Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [UserId] INT NOT NULL, 
    [FirstName] NVARCHAR(50) NULL, 
    [LastName] NVARCHAR(50) NULL, 
    [Age] INT NULL, 
    [LastUpdateDate] DATETIME NULL, 
    CONSTRAINT [FK_Profiles_ToUsers] FOREIGN KEY ([UserId]) REFERENCES [Users]([Id]) ON DELETE CASCADE
)

Таким образом, основной таблице у нас является Users, где находятся данные id, логина и пароля пользователей. Чтобы связать профиль с определенным пользователем в таблице профилей, мы определяем поле UserId. И это поле будет хранить значение столбца id из таблицы Users.

А теперь к самому интересному. Добавим в папку Providers класс провайдера профилей (назовем его CustomProfileProvider), который будет иметь следующее определение:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Profile;
using System.Configuration;
using CustomAuthorization.Models;
using System.Data;

namespace CustomAuthorization.Providers
{
    public class CustomProfileProvider : ProfileProvider
    {
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            // коллекция, которая возвращает значения свойств профиля
            SettingsPropertyValueCollection result = new SettingsPropertyValueCollection();

            if (collection == null || collection.Count < 1 || context == null)  
            {
              return result;  
            }
            // получаем из контекста имя пользователя - логин в системе
            string username = (string)context["UserName"];  
            if (String.IsNullOrEmpty(username))  
                return result;  
            
            UserContext db = new UserContext();
            // получаем id пользователя из таблицы Users по логину
            int userId = db.Users.Where(u => u.Email.Equals(username)).FirstOrDefault().Id;
            // по этому id извлекаем профиль из таблицы профилей
            Profile profile = db.Profiles.Where(u => u.UserId==userId).FirstOrDefault();
            if (profile != null)
            {
                foreach (SettingsProperty prop in collection)
                {
                    SettingsPropertyValue svp = new SettingsPropertyValue(prop);
                    svp.PropertyValue = profile.GetType().GetProperty(prop.Name).GetValue(profile, null);
                    result.Add(svp);
                }
            }
            else
            {
                foreach (SettingsProperty prop in collection)
                {
                    SettingsPropertyValue svp = new SettingsPropertyValue(prop);
                    svp.PropertyValue = null;
                    result.Add(svp);
                }
            }
            return result;
        }

        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            // получаем логин пользователя
            string username = (string)context["UserName"];

            if (username == null || username.Length < 1 || collection.Count < 1)
                return; 

            UserContext db = new UserContext();
            // получаем id пользователя из таблицы Users по логину
            int userId = db.Users.Where(u => u.Email.Equals(username)).FirstOrDefault().Id;
            // по этому id извлекаем профиль из таблицы профилей
            Profile profile = db.Profiles.Where(u => u.UserId == userId).FirstOrDefault();
            // если такой профиль уже есть изменяем его
            if (profile != null)
            {
                foreach (SettingsPropertyValue val in collection)
                {
                    profile.GetType().GetProperty(val.Property.Name).SetValue(profile, val.PropertyValue);
                }
                profile.LastUpdateDate = DateTime.Now;  
                db.Entry(profile).State = EntityState.Modified;
            }
            else
            { 
                // если нет, то создаем новый профиль и добавляем его
                profile = new Profile();
                foreach (SettingsPropertyValue val in collection)
                {
                    profile.GetType().GetProperty(val.Property.Name).SetValue(profile, val.PropertyValue);
                }
                profile.LastUpdateDate = DateTime.Now;
                profile.UserId = userId;
                db.Profiles.Add(profile);
            }
            db.SaveChanges();
        }

        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            throw new NotImplementedException();
        }

        public override int DeleteProfiles(string[] usernames)
        {
            throw new NotImplementedException();
        }

        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            throw new NotImplementedException();
        }

        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            throw new NotImplementedException();
        }

        public override string ApplicationName
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }
}

Класс провайдера наследуется от класса ProfileProvider и содержит определения ряда его методов. Так как у нас будет самый примитивный провайдер профилей, то мы реализуем всего два его метода: SetPropertyValues и GetPropertyValues.

Метод GetPropertyValues принимает два параметра - контекст (откуда мы можем получить разные данные типа логина пользователя) и коллекцию свойств профиля, которые будут использоваться. На выходе мы возвращаем объект SettingsPropertyValueCollection, который содержит названия свойств и их значения. Важно в любом случае передать названия свойств, даже если свойства не несут никакого значения, иначе среда не сможет определить свойство и присвоить ему значение (например, HttpContext.Profile["FirstName"] = "Vova";).

В самом методе мы получаем пользователя, его id, профиль и из профиля передаем значение в выходную коллекцию.

Метод SetPropertyValues берет установленные данные и уже через контекст передает их в базу данных.

Теперь подключим провайдер в файле web.config, добавив после определения провайдера ролей следующие строки:

	<profile enabled="true" defaultProvider="MyProfileProvider">
      <providers>
        <add  name="MyProfileProvider" type="CustomAuthorization.Providers.CustomProfileProvider"/>
      </providers>
      <properties>
        <add name="FirstName" type="System.String" />
        <add name="LastName" type="System.String" />
        <add name="Age" type="System.Int32"/>
      </properties>
    </profile>

И в завершении мы можем его протестировать, например, прописав в контроллере Home следующий метод:

public string ViewProfile()
{
    HttpContext.Profile["FirstName"] = "Vova";
    HttpContext.Profile["LastName"] = "Pupkin";
    HttpContext.Profile.SetPropertyValue("Age", 28);

    return "Имя Фамилия: " + HttpContext.Profile["FirstName"].ToString() +
        " " + HttpContext.Profile["LastName"].ToString() + " возраст: "+
        HttpContext.Profile["Age"].ToString();
}

Установка свойств может происходить как напрямую (HttpContext.Profile["FirstName"] = "Vova"), так и с помощью метода SetPropertyValue. После установки данные профиля появятся в таблице Profiles в БД, и затем мы сможем их оттуда получать и использовать в приложении.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850