重写MembershipProvider用于事务处理(一)

    当使用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的名字.

因为爱上你,我才懂得珍惜,每一天日记,都写满了甜蜜
因为想念你,我每天都可以,对着镜子说我多爱你,有多想见到你。
原文地址:https://www.cnblogs.com/jackzhang/p/570246.html