カスタムメンバシッププロバイダを作成します。今回作成するメンバシッププロバイダは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を実装してみます。