Управление профилями пользователей
На этой страничке я расскажу как стандартные API аз ASP2 применяются для управления пользовательскими профилями.
При правильном конфигурировании стандартного API идентификации и персонализации для ASP2-приложения автоматически создается база с именами пользователей и их профилями. Все параметры этой базы автоматически интегрированы в обьекты, доступные через имя юзера в HttpContext и профиль, доступный через класс Profile.
Прежде всего надо определить какие именно параметры мы будем хранить в профиле, их тип и тип из сериализации непосредственно в поле базы. Для пользователя форума я определил такие параметры профиля:
На самом деле, наиболее распространенным методом сериализации является XML (особенно для System.Collections.Specialized.StringCollection), однако для System.Collections.Specialized.StringDictionary поддерживается только Binary-сериализация. Как вы могли увидеть выше - в таблице dbo.aspnet_Profile есть поля для текстовой PropertyValuesString и двоичной PropertyValuesBinary сериализации. Указанное выше определения профиля сохраняется в поле PropertyNames.
Для начала рассмотрим вот такую интересную страничку, на которой профили пользователя выводятся. При работе пользоваетель форума может добавить закладки на некоторые темы форума, а на этой страничке их простмотреть. Эта страничка интересна тем, что НЕ СОДЕРЖИТ КОДА программирования. Только разметку.
Эта удивительная страничка не содержит кода программирования, но отображает из профиля пользователя все сохраненные в нем ссылки. В этом как бы весь смысл всей чехарды со встроенным АПИ профилей. Все автоматически поднялось из поля PropertyValuesBinary таблицы aspnet_Profile, и вывелось в таблицу на страничке в виде гиперссылок. Теперь я расскажу как я добился этого.
Сама по себе указанная страничка состоит из одного DataList с шаблоном в виде гиперссылки и одного ObjectDataSource. На вопросах привязки шаблона к данным я останавливаться не буду - это разжевано на моем сайте до безобразия. Вопросы изготовления собственных бизнес-обьектов - разжеваны тоже, однако тут - случай особый. Фактически бизнес-обьект здесь полностью вырожденный, и это заслуживает внимания.
Как видите, самое главное здесь - понимать, что StringDictionary - это хеш-таблица строк, индексируемая строками и если бы мы все-таки добавили в эту страничку код, то увидели бы как ObjectDataSource перебирает элементы DictionaryEnrty хеш-таблицы StringDictionary. Ну а второе, что можно увидеть из кода выше - как из базы поднялась и десериализовалась из бинарного потока таблицы aspnet_Profile методом Web.Profile.ProfileBase коллекция ProfileBase и заполнилась в контексте текущего пользователя.
Добавление данных в профиль выполняется методом SetPropertyValue, после чего коллекция сохраняется в базу методом SAVE.
Теперь рассмотрим страничку, на которой я покажу, как я вывожу графику из профиля пользователя.
Здесь применена техника, когда один из столбцов GridView определен статически в HTML-коде, а остальные столбцы создаются на лету, параметром AutoGenerateColumns=True. В принципе, шаблон можно было бы тоже создать на лету, ссылки как это сделать - тут
Как вы видите, страничка содержит GridView с единственным статическим элементом в шаблоне - рисунком. Все остальное я добавил в таблицу динамически. Для этого я определил свою DataTable и привязал сетку на эту ДатаТабле динамически. Сетку, а не DataList?, я выбрал потому, что она поддерживает автоматический пейджинг, который мне в данном случае понадобилось сюда докрутить.
Прежде всего, обратите внимание на технику пейждинга, в случае динамической привязки таблы по G1.DataSource = DT. Далее обратите внимание, как я в строке 29 создал новую строку DataRow своей DataTable и добавил ее в строке 62 в свою таблицу. Но перед тем как создать строку со строгим типом данных каждого поля методом NewRow, я создал в таблице столбцы. Обратите внимание на типы столбцов - не все они являются элементарными. Некоторые из них являются ссылочными и не могуь быть использованы в определении типа данных столбца. Как вы видите - рисунок - это набор байтов - строка 26. И вписать его (а не ссылку на него) в ДатаСет можно так, как я это сделал в строках 51-55.
Принадлежность этой таблицы - страничка, которая выводит в поток браузера собственно рисунок с типом данных "Image/BMP". В этой страничке никаких хитростей нет.
И, наконец, последняя страничка, которую я покажу в разделе профилей, будет считывать предпочтения пользователя при регистрации и запоминать их в базе профилей пользователей. В отличие от асинхронной загрузки рисунков - тут я сделал синхронную загрузку рисунка (сразу с проверкой и масштабированием). Этот контрол имеет довольно хитрый цикл жизни и я сохраняю здесь свои комментарии, касающиеся его цикла жизни. Обратите внимание на запись рисунка и предпочтений пользователя методам SetPropertyValue.
00001: 'У этого контрола хитрая схема сохранения состояния. 00002: 'Он грузится: 00003: '1.Просто вхолостую (при сборке странички Page4) - (нет Постбека) - состояние любое 00004: '2.Загрузка контрола в его начальном состоянии после редиректа с Page4_NewUser (состояние ForumState.State.NewUser) 00005: '3.Постбек с кнопки вложенного контрола. 00006: 'Далее если код введен правильно, ВОЗМОЖНО возникновения события SEND (если код введен правильно) 00007: 'После выполнения SEND всегда делается REDIRECT на Page4 00008: '4.После этого контрол грузится еще раз (нет Постбека) - ForumState.State.NewUser 00009: '5.При повторах в случае неудачного создания юзера (есть Постбек) - то же что и пункт 2 00010: 'При удачном создании юзера меняет состояние на ForumState.State.EndRegister 00011: ' 00012: 'Если записать просто ошибку в L0.Text и сделать Постбек, то после Редиректа 00013: 'при новой сборке страницы она потеряется ДАЖЕ во ViewState. 00014: ' 00015: 'Err1 - не принята EULA форума 00016: 'Err2 - отключен Javascript и не прошли проверки корректности на клиент2 00017: 'Err3 - ошибка создания нового юзера Membership-провайдером 00018: 'Err4 - общая ошибка странички 00019: 'Err5 - ошибка скачки файла 00020: 00021: Partial Class Control_ForumNewUser 00022: Inherits System.Web.UI.UserControl 00023: 00024: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00025: If Me.Page.IsPostBack Then 00026: If Not Err3.Checked Then 00027: Session("ForumRegErr3") = "Условие не принято. Регистрация невозможна." 00028: Else 00029: Session("ForumRegErr3") = "" 00030: End If 00031: Else 00032: If Session("ForumState") = ForumState.State.NewUser Then 00033: Err1.Text = Session("ForumRegErr1") 00034: Err2.Text = Session("ForumRegErr2") 00035: Err3.Text = Session("ForumRegErr3") 00036: Err4.Text = Session("ForumRegErr4") 00037: Err5.Text = Session("ForumRegErr5") 00038: End If 00039: End If 00040: End Sub 00041: 00042: Protected Sub S1_Send() Handles S1.Send 00043: If Not Err3.Checked Then 00044: Response.Redirect(Session("OnNavigateReturnURL")) 00045: Else 00046: Me.Page.Validate() 00047: If Me.Page.IsValid Then 00048: Session("ForumRegErr2") = "" 00049: Try 00050: Dim Result As MembershipCreateStatus 00051: 'создаем нового юзера 00052: Dim X As System.Web.Security.MembershipUser = System.Web.Security.Membership.CreateUser(T1.Text, T2.Text, T4.Text, T5.Text, T6.Text, True, Result) 00053: If Result = MembershipCreateStatus.Success And (Not (X Is Nothing)) Then 00054: Session("ForumRegErr1") = "" 00055: 'создали профиль нового юзера 00056: Dim P As System.Web.Profile.ProfileBase = ProfileBase.Create(X.UserName.ToString, True) 00057: 'и заполняем его 00058: Dim Y As New System.Collections.Specialized.StringDictionary 00059: P.SetPropertyValue("HideMail", C1.Checked) 00060: P.SetPropertyValue("HideLink", C2.Checked) 00061: P.SetPropertyValue("Devis", T7.Text) 00062: P.SetPropertyValue("Links", Y) 00063: 'скачка указанного файла и определение его корректности как рисунка 00064: Dim Z As System.Drawing.Bitmap, M As System.IO.MemoryStream 00065: If U1.PostedFile.FileName = "" Then 00066: Z = New Drawing.Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format24bppRgb) 00067: Dim G As Drawing.Graphics = Drawing.Graphics.FromImage(Z) 00068: G.Clear(Drawing.Color.White) 00069: P.SetPropertyValue("Image", Z) 00070: Else 00071: Try 00072: 'СИНХРОННАЯ скачка указанного файла 00073: If U1.PostedFile.InputStream.Length > 1000000 Then 00074: Session("ForumRegErr5") = "Превышена длина файла" 00075: Throw New System.Data.SyntaxErrorException 00076: Else 00077: Dim Buf(U1.PostedFile.InputStream.Length) As Byte 00078: U1.PostedFile.InputStream.Read(Buf, 0, U1.PostedFile.InputStream.Length) 00079: M = New System.IO.MemoryStream(Buf) 00080: Session("ForumRegErr5") = "" 00081: End If 00082: Catch ex As Exception 00083: Session("ForumRegErr5") = ex.Message 00084: End Try 00085: Try 00086: 'загрузить скачанное в BITMAP 00087: Dim Z1 As New Drawing.Bitmap(M, False) 00088: Dim Rate As Double = IIf(Z1.Width / 150 > Z1.Height / 150, Z1.Width / 150, Z1.Height / 150) 00089: 'перемасштабировали рисунок к максимальному размеру 150 рiх 00090: Z = New System.Drawing.Bitmap(Z1, CInt(IIf(Z1.Width / 150 > Z1.Height / 150, 150, Z1.Width / Rate)), CInt(IIf(Z1.Width / 150 > Z1.Height / 150, Z1.Height / Rate, 150))) 00091: Catch ex As Exception 00092: Z = New Drawing.Bitmap(50, 10, System.Drawing.Imaging.PixelFormat.Format24bppRgb) 00093: Dim G As Drawing.Graphics = Drawing.Graphics.FromImage(Z) 00094: G.Clear(Drawing.Color.White) 00095: G.DrawString("Error", New Drawing.Font("", 10, Drawing.FontStyle.Regular, Drawing.GraphicsUnit.Pixel), Drawing.Brushes.Red, 0, -2) 00096: End Try 00097: End If 00098: 'в любом случае - то что получили вписываем в профиль 00099: P.SetPropertyValue("Image", Z) 00100: P.Save() 00101: Session("ForumState") = ForumState.State.EndRegister 00102: Else 00103: Select Case Result 00104: Case MembershipCreateStatus.DuplicateEmail 00105: Session("ForumRegErr1") = "Ошибка. Дублирование почты." 00106: Case MembershipCreateStatus.DuplicateProviderUserKey 00107: Session("ForumRegErr1") = "Ошибка. Дублирование DuplicateProviderUserKey." 00108: Case MembershipCreateStatus.DuplicateUserName 00109: Session("ForumRegErr1") = "Ошибка. Дублирование имени пользователя." 00110: Case MembershipCreateStatus.InvalidAnswer 00111: Session("ForumRegErr1") = "Ошибка. Дублирование имени пользователя." 00112: Case MembershipCreateStatus.InvalidEmail 00113: Session("ForumRegErr1") = "Ошибка. Недопустимая почта." 00114: Case MembershipCreateStatus.InvalidPassword 00115: Session("ForumRegErr1") = "Ошибка. Слишком простой пароль." 00116: Case MembershipCreateStatus.InvalidProviderUserKey 00117: Session("ForumRegErr1") = "Ошибка. Недопустимый код пользователя." 00118: Case MembershipCreateStatus.InvalidQuestion 00119: Session("ForumRegErr1") = "Ошибка. Недопустимый ответ." 00120: Case MembershipCreateStatus.InvalidUserName 00121: Session("ForumRegErr1") = "Ошибка. Недопустимое имя пользователя." 00122: Case MembershipCreateStatus.ProviderError 00123: Session("ForumRegErr1") = "Ошибка провайдера авторизации." 00124: Case MembershipCreateStatus.UserRejected 00125: Session("ForumRegErr1") = "Ошибка. Пользователь сброшен." 00126: End Select 00127: SH.Common.ErrorMessage(Me.Page, "Ошибка создания пользователя.", Session("ForumRegErr1")) 00128: Session("ForumRegErr4") = "" 00129: End If 00130: Catch ex As Exception 00131: Session("ForumRegErr4") = ex.Message 00132: SH.Common.ErrorMessage(Me.Page, "Упала регистрация", ex.Message) 00133: Finally 00134: Response.Redirect(Session("OnNavigateReturnURL")) 00135: End Try 00136: Else 00137: Session("ForumRegErr2") = "Отключен JavaScript. Регистрация невозможна." 00138: Response.Redirect(Session("OnNavigateReturnURL")) 00139: End If 00140: End If 00141: End Sub 00142: End Class
Но перед тем, как вписывать предпочтения пользователя, его надо было сначала создать по System.Web.Security.Membership.CreateUser. Конечно, АПИ из ASP2 не только создает сами профиля юзеров, но и само вызывает необходимые процедуры чтобы создать и самих пользователей. Но об этом подробнее я писал здесь.
|