カスタムメンバシッププロバイダを作成します。今回作成するメンバシッププロバイダはxmlファイルをユーザ情報のストアとします。今回は第一弾として、ユーザ情報を現すクラスを定義します。XmlSerializerを使用したサンプルについては、XmlSerializerを使ってインスタンスをXML形式でシリアライズする を参照して下さい。
1.XmlMembershipProviderの仕様
今回作成する仕様はMembershipプロバイダでサポートするメソッドをすべてサポートしていません。以下の機能はサポートしません。
- maxInvalidPasswordAttempts, passwordAttemptWindowを設定することによる自動ロックアウト
- applicationName指定によるアプリケーションごとのユーザの管理
- requireUniqueEmailを設定した場合の重複チェック
機能として実装してはいませんが、プログラムの修正、追加をすれば対応することができると思います。
2.ユーザ情報のドメインデータ作成
2.1 作成するスキーマ
今回作成するユーザストアのスキーマは次のようになります。usersがルート要素で、各ユーザごとにuser要素のタグが作成されます。user要素のname属性がユーザIDを現します。applicationnameはアプリケーション名を現します。子属性はMembershipUserを構成するのに必要なプロパティとパスワード情報から構成されます。
<?xml version="1.0"?> <users xmlns="urn:Handcraft.UserInfoList"> <user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" applicationName="/" name="test1" xmlns="urn:Handcraft.UserInfo"> <approved>true</approved> <lockouted>false</lockouted> <comment /> <email /> <providerUserKey>69dfc154-0f50-495c-85e3-ace0730a947b</providerUserKey> <creationDate>2008-10-05T16:38:36.852297+09:00</creationDate> <lastActivityDate>2008-10-05T16:38:36.852297+09:00</lastActivityDate> <lastLockoutDate>1754-01-01T00:00:00</lastLockoutDate> <lastLoginDate>2008-10-05T16:38:36.852297+09:00</lastLoginDate> <lastPasswordChangedDate>2008-10-05T16:38:36.852297+09:00</lastPasswordChangedDate> <passwordQuestion /> <passwordAnswer /> <password>HJ9Lwvnk6rC3hDfXvIe8huCgLvI=</password> <passwordSalt>sA==</passwordSalt> </user> <user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" applicationName="/" name="test2" xmlns="urn:Handcraft.UserInfo"> <approved>true</approved> <lockouted>false</lockouted> <comment /> <email /> <providerUserKey>dfe453bf-468d-4e62-89f6-cecb2f1e5650</providerUserKey> <creationDate>2008-10-05T16:38:36.877686+09:00</creationDate> <lastActivityDate>2008-10-05T16:38:36.877686+09:00</lastActivityDate> <lastLockoutDate>1754-01-01T00:00:00</lastLockoutDate> <lastLoginDate>2008-10-05T16:38:36.877686+09:00</lastLoginDate> <lastPasswordChangedDate>2008-10-05T16:38:36.877686+09:00</lastPasswordChangedDate> <passwordQuestion /> <passwordAnswer /> <password>/ZIv8g/wxhkH2LAymE+b17wg3fA=</password> <passwordSalt>QQ==</passwordSalt> </user> </users>
2.2 ソリューションの作成
2.1で記載したスキーマに一致するXmlSerializerでシリアライズ可能なクラスを作成します。各クラスにはXmlMembershipProviderから利用されるメソッドも定義しています。Visual Studioを起動し、空のソリューションを作成します。ソリューション名はCustomProviderとします。
作成したソリューションにクラスライブラリプロジェクトCustomProviderを新規します。既定で作成されるClass1.csを削除し、各ユーザ情報を現すクラスUserInfoクラスを定義するために、UserInfo.csファイルを新規作成し、次のように編集します。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Serialization; using System.Web.Security; using System.Text.RegularExpressions; namespace CustomProvider { [XmlRoot("user", Namespace = "urn:Handcraft.UserInfo")] public class UserInfo { public UserInfo() { ApplicationName = "/"; UserName = string.Empty; IsApproved = false; IsLockedOut = false; Comment = string.Empty; Email = string.Empty; ProviderUserKey = Guid.Empty; CreationDate = MinumDateTime(); LastActivityDate = MinumDateTime(); LastLockoutDate = MinumDateTime(); LastLoginDate = MinumDateTime(); LastPasswordChangedDate = MinumDateTime(); PasswordQuestion = string.Empty; EncodedPasswordAnswer = string.Empty; EncodedPassword = string.Empty; PasswordSalt = string.Empty; } public static UserInfo CreateUserInfo(string applicationName, string userName, bool isApproved, bool isLockedOut, string comment, string email, Guid providerUserKey, string passwordQuestion, string encodedPasswordAnswer, string encodedPassword, string passwordSalt) { UserInfo user = new UserInfo(); user.ApplicationName = applicationName; user.UserName = userName; user.IsApproved = isApproved; user.IsLockedOut = isLockedOut; user.Comment = comment; user.Email = email; user.ProviderUserKey = providerUserKey; user.PasswordQuestion = passwordQuestion; user.EncodedPasswordAnswer = encodedPasswordAnswer; user.EncodedPassword = encodedPassword; user.PasswordSalt = passwordSalt; DateTime now = DateTime.Now; user.CreationDate = now; if (isApproved) { user.LastActivityDate = now; user.LastLoginDate = now; } else { user.LastActivityDate = user.MinumDateTime(); user.LastLoginDate = user.MinumDateTime(); } if (isLockedOut) user.LastLockoutDate = now; else user.LastLockoutDate = user.MinumDateTime(); user.LastPasswordChangedDate = now; return user; } /// <summary>アプリケーション名</summary> [XmlAttribute("applicationName")] public string ApplicationName { get; set; } /// <summary>ユーザID</summary> [XmlAttribute("name")] public string UserName { get; set; } /// <summary>承認済みか</summary> [XmlElement("approved")] public bool IsApproved { get; set; } /// <summary>ロックアウト中か</summary> [XmlElement("lockouted")] public bool IsLockedOut { get; set; } /// <summary>コメント</summary> [XmlElement("comment")] public string Comment { get; set; } /// <summary>email</summary> [XmlElement("email")] public string Email { get; set; } /// <summary>プロバイダユーザキー</summary> [XmlElement("providerUserKey")] public Guid ProviderUserKey { get; set; } [XmlElement("creationDate")] public DateTime CreationDate { get; set; } /// <summary>最終活動日時</summary> [XmlElement("lastActivityDate")] public DateTime LastActivityDate { get; set; } /// <summary>最終ロックアウト日時</summary> [XmlElement("lastLockoutDate")] public DateTime LastLockoutDate { get; set; } /// <summary>最終ログイン日時</summary> [XmlElement("lastLoginDate")] public DateTime LastLoginDate { get; set; } /// <summary>最終パスワード変更日時</summary> [XmlElement("lastPasswordChangedDate")] public DateTime LastPasswordChangedDate { get; set; } /// <summary>パスワードの質問</summary> [XmlElement("passwordQuestion")] public string PasswordQuestion { get; set; } /// <summary>パスワードの答え</summary> [XmlElement("passwordAnswer")] public string EncodedPasswordAnswer { get; set; } /// <summary>パスワード</summary> [XmlElement("password")] public string EncodedPassword { get; set; } /// <summary>パスワードサルト</summary> [XmlElement("passwordSalt")] public string PasswordSalt { get; set; } public virtual MembershipUser CreateMembershipUser(string providerName) { return new MembershipUser(providerName, UserName, ProviderUserKey, Email, PasswordQuestion, Comment, IsApproved, IsLockedOut, CreationDate, LastLoginDate, LastActivityDate, LastPasswordChangedDate, LastLockoutDate); } protected virtual DateTime MinumDateTime() { return new DateTime(1754, 1, 1, 0, 0, 0, 0); } } }
次に、UserInfoList.csファイルを作成し、ユーザのコレクションを表すUserInfoListクラスを以下のように記述します。UserInfoListは作成するメンバシッププロバイダからさまざまな機能をデリゲートされるため、いくつかMembershipProviderと同じメソッドを定義しています。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Serialization; using System.Web.Security; using System.Text.RegularExpressions; namespace CustomProvider { [XmlRoot("users", Namespace = "urn:Handcraft.UserInfoList")] public class UserInfoList : IXmlSerializable { /// <summary> /// ユーザ情報を格納するコレクション /// キーにUserInfo.UserNameを使用する /// </summary> private SortedList<string, UserInfo> _users = new SortedList<string, UserInfo>(); /// <summary> /// UserInfoを追加する /// </summary> /// <param name="userInfo"></param> public void AddUserInfo(UserInfo userInfo) { _users.Add(userInfo.UserName, userInfo); } public UserInfo GetUserInfo(string name) { try { return _users[name]; } catch (KeyNotFoundException) { return null; } } public UserInfo GetUserInfo(Guid providerUserKey) { foreach (UserInfo user in _users.Values) { if (user.ProviderUserKey.Equals(providerUserKey)) return user; } return null; } public bool RemoveUserInfo(string name) { return _users.Remove(name); } public MembershipUserCollection FindUsersByEmail(string providerName, string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { IList<UserInfo> list = new List<UserInfo>(); foreach (UserInfo user in _users.Values) { if (Regex.IsMatch(user.Email, emailToMatch)) list.Add(user); } totalRecords = list.Count; return SelectMembershipUser(providerName, pageIndex, pageSize, list); } public MembershipUserCollection FindUsersByName(string providerName, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { IList<UserInfo> list = new List<UserInfo>(); foreach (UserInfo user in _users.Values) { if (Regex.IsMatch(user.UserName, usernameToMatch)) list.Add(user); } totalRecords = list.Count; return SelectMembershipUser(providerName, pageIndex, pageSize, list); } public string GetUserNameByEmail(string email) { foreach (UserInfo user in _users.Values) { if (user.Email.Equals(email)) return user.UserName; } return null; } public MembershipUserCollection GetAllUsers(string providerName, int pageIndex, int pageSize, out int totalRecords) { IList<UserInfo> list = _users.Values; totalRecords = list.Count; return SelectMembershipUser(providerName, pageIndex, pageSize, list); } public int GetNumberOfUsersOnline(TimeSpan userIsOnlineTimeWindow) { DateTime compareDateTime = DateTime.Now.Subtract(userIsOnlineTimeWindow); int cnt = 0; foreach (UserInfo user in _users.Values) { if (user.LastActivityDate > compareDateTime) ++cnt; } return cnt; } #region IXmlSerializable Membersの実装 public System.Xml.Schema.XmlSchema GetSchema() { return null; } /// <summary> /// 独自でデシリアライズを行う /// </summary> /// <param name="reader"></param> public void ReadXml(System.Xml.XmlReader reader) { XmlSerializer s = new XmlSerializer(typeof(UserInfo)); reader.Read(); while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { UserInfo o = s.Deserialize(reader) as UserInfo; AddUserInfo(o); } } /// <summary> /// 独自でシリアライズを行う /// </summary> /// <param name="writer"></param> public void WriteXml(System.Xml.XmlWriter writer) { XmlSerializer s = new XmlSerializer(typeof(UserInfo)); foreach (UserInfo val in _users.Values) { s.Serialize(writer, val); } } /// <summary> /// 指定されたpageIndex,pageSizeに含まれる /// MembershipUserCollectionのリストを作成する。 /// </summary> /// <param name="providerName"></param> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="users"></param> /// <returns></returns> private MembershipUserCollection SelectMembershipUser(string providerName, int pageIndex, int pageSize, IList<UserInfo> users) { MembershipUserCollection membershipUsers = new MembershipUserCollection(); if (pageIndex > users.Count) return membershipUsers; for (int i = pageIndex; i < (pageIndex + pageSize); ++i) { if (users.Count <= i) break; membershipUsers.Add(users[i].CreateMembershipUser(providerName)); } return membershipUsers; } #endregion } }
XmlSerializerでシリアライズするためにプロパティにXmlElementなどがアノテートされていますが、詳細についてはMSDNライブラリや本サイトのサンプルXmlSerializerを使ってインスタンスをXML形式でシリアライズする を参照して下さい。
今回の説明は以上です。次回はXmlMembershipProviderを実装してみます。
さんのコメント: さんのコメント: