当使用MembershpProvider类创建新用户的时候,如果创建用户的同时还需要对数据库进行一些其他的操作,而又想把这些额外的操作同创建用户的动作融为一体,作为一个事务来处理的话,就要重写MembershipProvider了.还好M$提供了SqlMembershipProvider的源代码(http://download.microsoft.com/download/a/b/3/ab3c284b-dc9a-473d-b7e3-33bacfcc8e98/ProviderToolkitSamples.msi),在此基础上,我们就能更好的重写这个类了.
需求说明:Employee表中存放员工信息,需要根据员工信息创建用户,每个员工对应一个或0个用户.只有已经存在的员工,才能为其创建用户.在创建用户的同时需要更新Employee表,将些员工的UserName字段置为刚刚创建的用户的UserName.需要把上述两步放在同一个Transaction中,如果有一个不成功,则全部RollBack.同理,删除用户时也应进行相应的操作.
具体作法:
新建一个Class Library类型的工程,工程改名为myProvider.MembershipProvider.添加一个类mySqlMembershipProvider,最好给他指定一个命名空间.我指定的是myProvider.这个类要继承于M$的SqlMembershipProvider.再添加两个类,这两个类可以从你下到的源代码中找到,分别是SecUtil.cs和SR.cs(如果没下到,我会把这两个类的代码放到后面,重写MembershipProvider用于事务处理(二)中),因为要用到这两个类中某些方法.
////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////mySqlMembershipProvider.cs///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Web.Security;
using System.Web;
using System.Web.Configuration;
using System.Security.Principal;
using System.Security.Permissions;
using System.Globalization;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Configuration.Provider;
using System.Configuration;
using System.Web.DataAccess;
using System.Web.Management;
using System.Web.Util;
namespace myProvider
{
/// <summary>
/// Summary description for mySqlMembershipProvider
/// </summary>
public class mySqlMembershipProvider: SqlMembershipProvider
{
private string _sqlConnectionString;
private int _SchemaVersionCheck;
private int _CommandTimeout;
private MembershipPasswordFormat _PasswordFormat;
public CRM2006MembershipProvider()
{
//
// TODO: Add constructor logic here
//
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
_CommandTimeout = SecUtility.GetIntValue(config, "commandTimeout", 30, true, 0);
_SchemaVersionCheck = 0;
string temp = config["connectionStringName"];
if (temp == null || temp.Length < 1)
throw new ProviderException(SR.GetString(SR.Connection_name_not_specified));
_sqlConnectionString = null;
ConnectionStringSettings connObj = ConfigurationManager.ConnectionStrings[temp];
if (connObj != null)
_sqlConnectionString = connObj.ConnectionString;
string strTemp = config["passwordFormat"];
if (strTemp == null)
strTemp = "Hashed";
switch (strTemp)
{
case "Clear":
_PasswordFormat = MembershipPasswordFormat.Clear;
break;
case "Encrypted":
_PasswordFormat = MembershipPasswordFormat.Encrypted;
break;
case "Hashed":
_PasswordFormat = MembershipPasswordFormat.Hashed;
break;
default:
throw new ProviderException(SR.GetString(SR.Provider_bad_password_format));
}
base.Initialize(name, config);
}
/// <summary>
/// Adds a new user to the SQL Server membership database for a employee.
/// </summary>
/// <param name="username">The user name and employeId for the new user.Format="username|employeId"</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user.</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">A System.Guid that uniquely identifies the membership user in the SQL Server database.</param>
/// <param name="status">One of the System.Web.Security.MembershipCreateStatus values, indicating whether the user was created successfully.</param>
/// <returns>A System.Web.Security.MembershipUser object for the newly created user. If no user was created, this method returns null.</returns>
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
string[] strParms = username.Split('|');
username = strParms[0];
int employeId = 0;
try
{
employeId = int.Parse(strParms[1]);
}
catch
{
employeId = 0;
}
if (!SecUtility.ValidateParameter(ref password, true, true, false, 128))
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
string salt = GenerateSalt();
string pass = EncodePassword(password, (int)_PasswordFormat, salt);
if (pass.Length > 128)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
string encodedPasswordAnswer;
if (passwordAnswer != null)
{
passwordAnswer = passwordAnswer.Trim();
}
if (!string.IsNullOrEmpty(passwordAnswer))
{
if (passwordAnswer.Length > 128)
{
status = MembershipCreateStatus.InvalidAnswer;
return null;
}
encodedPasswordAnswer = EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), (int)_PasswordFormat, salt);
}
else
encodedPasswordAnswer = passwordAnswer;
if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, true, false, 128))
{
status = MembershipCreateStatus.InvalidAnswer;
return null;
}
if (!SecUtility.ValidateParameter(ref username, true, true, true, 256))
{
status = MembershipCreateStatus.InvalidUserName;
return null;
}
if (!SecUtility.ValidateParameter(ref email,
RequiresUniqueEmail,
RequiresUniqueEmail,
false,
256))
{
status = MembershipCreateStatus.InvalidEmail;
return null;
}
if (!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, 256))
{
status = MembershipCreateStatus.InvalidQuestion;
return null;
}
if (providerUserKey != null)
{
if (!(providerUserKey is Guid))
{
status = MembershipCreateStatus.InvalidProviderUserKey;
return null;
}
}
if (password.Length < MinRequiredPasswordLength)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
int count = 0;
for (int i = 0; i < password.Length; i++)
{
if (!char.IsLetterOrDigit(password, i))
{
count++;
}
}
if (count < MinRequiredNonAlphanumericCharacters)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (PasswordStrengthRegularExpression.Length > 0)
{
if (!Regex.IsMatch(password, PasswordStrengthRegularExpression))
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
}
ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true);
OnValidatingPassword(e);
if (e.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
SqlConnection Connection;
try
{
Connection = new SqlConnection(_sqlConnectionString);
Connection.Open();
}
catch (ArgumentException ex)
{
throw new ArgumentException(SR.GetString(SR.SqlError_Connection_String), "_sqlConnectionString", ex);
}
CheckSchemaVersion(Connection);
DateTime dt = RoundToSeconds(DateTime.UtcNow);
//create user
SqlCommand cmd = new SqlCommand("dbo.aspnet_Membership_CreateUser", Connection);
cmd.CommandTimeout = CommandTimeout;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(CreateInputParam("@ApplicationName", SqlDbType.NVarChar, ApplicationName));
cmd.Parameters.Add(CreateInputParam("@UserName", SqlDbType.NVarChar, username));
cmd.Parameters.Add(CreateInputParam("@Password", SqlDbType.NVarChar, pass));
cmd.Parameters.Add(CreateInputParam("@PasswordSalt", SqlDbType.NVarChar, salt));
cmd.Parameters.Add(CreateInputParam("@Email", SqlDbType.NVarChar, email));
cmd.Parameters.Add(CreateInputParam("@PasswordQuestion", SqlDbType.NVarChar, passwordQuestion));
cmd.Parameters.Add(CreateInputParam("@PasswordAnswer", SqlDbType.NVarChar, encodedPasswordAnswer));
cmd.Parameters.Add(CreateInputParam("@IsApproved", SqlDbType.Bit, isApproved));
cmd.Parameters.Add(CreateInputParam("@UniqueEmail", SqlDbType.Int, RequiresUniqueEmail ? 1 : 0));
cmd.Parameters.Add(CreateInputParam("@PasswordFormat", SqlDbType.Int, (int)PasswordFormat));
cmd.Parameters.Add(CreateInputParam("@CurrentTimeUtc", SqlDbType.DateTime, dt));
SqlParameter p = CreateInputParam("@UserId", SqlDbType.UniqueIdentifier, providerUserKey);
p.Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add(p);
p = new SqlParameter("@ReturnValue", SqlDbType.Int);
p.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add(p);
SqlTransaction trans = Connection.BeginTransaction();
cmd.Transaction = trans;
//create profile for the user
string sqlCreateProfile = "INSERT INTO Profile (EmployeeID,Username, ApplicationName, LastActivityDate, LastUpdatedDate, IsAnonymous) Values(@EmployeeID,@Username, @ApplicationName, @LastActivityDate, @LastUpdatedDate, @IsAnonymous)";
SqlCommand cmdCreateProfile = new SqlCommand(sqlCreateProfile, Connection);
cmdCreateProfile.Parameters.Add(CreateInputParam("@EmployeeID", SqlDbType.Int, employeId));
cmdCreateProfile.Parameters.Add(CreateInputParam("@Username", SqlDbType.NVarChar, username));
cmdCreateProfile.Parameters.Add(CreateInputParam("@ApplicationName", SqlDbType.NVarChar, ApplicationName));
cmdCreateProfile.Parameters.Add(CreateInputParam("@LastActivityDate", SqlDbType.DateTime, DateTime.Now));
cmdCreateProfile.Parameters.Add(CreateInputParam("@LastUpdatedDate", SqlDbType.DateTime, DateTime.Now));
cmdCreateProfile.Parameters.Add(CreateInputParam("@IsAnonymous", SqlDbType.Bit, false));
cmdCreateProfile.Transaction = trans;
//
int iStatus = -1;
try
{
cmd.ExecuteNonQuery();
iStatus = ((p.Value != null) ? ((int)p.Value) : -1);
if (iStatus < 0 || iStatus > (int)MembershipCreateStatus.ProviderError)
iStatus = (int)MembershipCreateStatus.ProviderError;
status = (MembershipCreateStatus)iStatus;
if (iStatus != 0) // !success
{
trans.Rollback();
Connection.Close();
return null;
}
cmdCreateProfile.ExecuteNonQuery();
trans.Commit();
Connection.Close();
}
catch
{
status = (MembershipCreateStatus)iStatus;
trans.Rollback();
Connection.Close();
}
providerUserKey = new Guid(cmd.Parameters["@UserId"].Value.ToString());
dt = dt.ToLocalTime();
return new MembershipUser(this.Name,
username,
providerUserKey,
email,
passwordQuestion,
null,
isApproved,
false,
dt,
dt,
dt,
dt,
new DateTime(1754, 1, 1));
}
private SqlParameter CreateInputParam(string paramName,
SqlDbType dbType,
object objValue)
{
SqlParameter param = new SqlParameter(paramName, dbType);
if (objValue == null)
{
param.IsNullable = true;
param.Value = DBNull.Value;
}
else
{
param.Value = objValue;
}
return param;
}
private void CheckSchemaVersion(SqlConnection connection)
{
string[] features = { "Common", "Membership" };
string version = "1";
SecUtility.CheckSchemaVersion(this,
connection,
features,
version,
ref _SchemaVersionCheck);
}
private DateTime RoundToSeconds(DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
}
private int CommandTimeout
{
get { return _CommandTimeout; }
}
///// <summary>
///// Removes a user's membership information from the SQL Server membership database.
///// </summary>
///// <param name="username">The name of the user to delete.</param>
///// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
///// <returns>true if the user was deleted; otherwise, false. A value of false is also returned if the user does not exist in the database.</returns>
//public override bool DeleteUser(string username, bool deleteAllRelatedData)
//{
// SecUtility.CheckParameter(ref username, true, true, true, 256, "username");
// try
// {
// SqlConnection Connection;
// try
// {
// Connection = new SqlConnection(_sqlConnectionString);
// Connection.Open();
// }
// catch (ArgumentException ex)
// {
// throw new ArgumentException(SR.GetString(SR.SqlError_Connection_String), "_sqlConnectionString", ex);
// }
// CheckSchemaVersion(Connection);
// //delete user
// SqlCommand cmd = new SqlCommand("dbo.aspnet_Users_DeleteUser", Connection);
// cmd.CommandTimeout = CommandTimeout;
// cmd.CommandType = CommandType.StoredProcedure;
// cmd.Parameters.Add(CreateInputParam("@ApplicationName", SqlDbType.NVarChar, ApplicationName));
// cmd.Parameters.Add(CreateInputParam("@UserName", SqlDbType.NVarChar, username));
// if (deleteAllRelatedData)
// {
// cmd.Parameters.Add(CreateInputParam("@TablesToDeleteFrom", SqlDbType.Int, 0xF));
// }
// else
// {
// cmd.Parameters.Add(CreateInputParam("@TablesToDeleteFrom", SqlDbType.Int, 1));
// }
// SqlParameter p = new SqlParameter("@NumTablesDeletedFrom", SqlDbType.Int);
// p.Direction = ParameterDirection.Output;
// cmd.Parameters.Add(p);
// SqlTransaction trans = Connection.BeginTransaction();
// cmd.Transaction = trans;
// //update employee
// string sqlUpdateEmployee = "UPDATE Employee SET UserName=null WHERE UserName=@username";
// SqlCommand cmdUpdateEmployee = new SqlCommand(sqlUpdateEmployee, Connection);
// cmdUpdateEmployee.Parameters.Add(CreateInputParam("@Username", SqlDbType.NVarChar, username));
// cmdUpdateEmployee.Transaction = trans;
// try
// {
// cmd.ExecuteNonQuery();
// int status = ((p.Value != null) ? ((int)p.Value) : -1);
// if (status <= 0)
// {
// trans.Rollback();
// Connection.Close();
// return false;
// }
// cmdUpdateEmployee.ExecuteNonQuery();
// trans.Commit();
// Connection.Close();
// }
// catch
// {
// trans.Rollback();
// Connection.Close();
// }
// return true;
// }
// catch
// {
// return false;
// }
//}
internal string GenerateSalt()
{
byte[] buf = new byte[16];
(new RNGCryptoServiceProvider()).GetBytes(buf);
return Convert.ToBase64String(buf);
}
internal string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bAll = new byte[bSalt.Length + bIn.Length];
byte[] bRet = null;
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm s = HashAlgorithm.Create(Membership.HashAlgorithmType);
bRet = s.ComputeHash(bAll);
}
else
{
bRet = EncryptPassword(bAll);
}
return Convert.ToBase64String(bRet);
}
}
}
代码分析:
1.重写Initialize()方法,主要是为了获得某些配置信息.比如连接字符串,密码加密方式等.
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
_CommandTimeout = SecUtility.GetIntValue(config, "commandTimeout", 30, true, 0);
_SchemaVersionCheck = 0;
string temp = config["connectionStringName"];
if (temp == null || temp.Length < 1)
throw new ProviderException(SR.GetString(SR.Connection_name_not_specified));
_sqlConnectionString = null;
ConnectionStringSettings connObj = ConfigurationManager.ConnectionStrings[temp];
if (connObj != null)
_sqlConnectionString = connObj.ConnectionString;
string strTemp = config["passwordFormat"];
if (strTemp == null)
strTemp = "Hashed";
switch (strTemp)
{
case "Clear":
_PasswordFormat = MembershipPasswordFormat.Clear;
break;
case "Encrypted":
_PasswordFormat = MembershipPasswordFormat.Encrypted;
break;
case "Hashed":
_PasswordFormat = MembershipPasswordFormat.Hashed;
break;
default:
throw new ProviderException(SR.GetString(SR.Provider_bad_password_format));
}
base.Initialize(name, config);
}
2.重写CreateUser()方法.
这个方法的前面半部分主要是验证输入信息的有效性,后半部分是用于事务处理.主要介绍一下事务处理这一块的吧.
int iStatus = -1;
try
{
cmd.ExecuteNonQuery();
iStatus = ((p.Value != null) ? ((int)p.Value) : -1);
if (iStatus < 0 || iStatus > (int)MembershipCreateStatus.ProviderError)
iStatus = (int)MembershipCreateStatus.ProviderError;
status = (MembershipCreateStatus)iStatus;
if (iStatus != 0) // !success
{
trans.Rollback();
Connection.Close();
return null;
}
cmdCreateProfile.ExecuteNonQuery();
trans.Commit();
Connection.Close();
}
catch
{
status = (MembershipCreateStatus)iStatus;
trans.Rollback();
Connection.Close();
}
providerUserKey = new Guid(cmd.Parameters["@UserId"].Value.ToString());
dt = dt.ToLocalTime();
return new MembershipUser(this.Name,
username,
providerUserKey,
email,
passwordQuestion,
null,
isApproved,
false,
dt,
dt,
dt,
dt,
new DateTime(1754, 1, 1));
cmd这个变量用于执行一个存储过程"aspnet_Membership_CreateUser",这个存储过程是M$提供的,它内部已经有了事务处理,也就是说执行这个存储过程时是不会引发异常的.所以我们只能根据这个存储过程的返回值(@ReturnValue)来判断创建新用户是否成功.
if (iStatus != 0) // !success
{
trans.Rollback();
Connection.Close();
return null;
}
如何使用:
写好了这个Class Library以后,编译成功后,怎么使用呢.在你的主项目中添加已存在的Project.把这个Class Library添加进去.然后在主项目中引用(Add Reference)这个Project. 然后在web.config中进行配置.如下:
<membership defaultProvider="mySqlMembershipProvider">
<providers>
<remove name="AspNetSqlProvider"/>
<clear/>
<add name="mySqlMembershipProvider" type="myProvider.mySqlMembershipProvider,myProvider.MembershipProvider" applicationName="appName" connectionStringName="LocalSqlServer" maxInvalidPasswordAttempts="5" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="6" passwordFormat="Hashed" requiresQuestionAndAnswer="true" requiresUniqueEmail="true" enablePasswordRetrieval="false"/>
</providers>
</membership>
需要说明的是type中的内容.type内容分两段,逗号前面是你的provider类的命名空间+类名,逗号后面是你的Class Library的工程名.也就是Assmbly的名字.