niedziela, 15 maja 2011

Własna implementacja MembershipProvider (Custom MembershipProvider)

We wcześniejszym poście wspominałem w jaki sposób korzystać z MembershipProvidera. Provider ten oferuje możliwość korzystania z autentyfikacji przy użyciu kontrolek ASP.NET. O ile dobrze jest umieć wykorzystać ten mechanizm w formie takiej jakiej jest często przydaje się możliwość ingerowania w niego i stworzenia własnego Providera.

Jakie możliwości daje nam własny provider? Otóż daje przede wszystkim wszechstronność i elastyczność w użyciu:
- nadaje się świetnie tam gdzie, mamy schemat już gotowy i chcemy wykorzystać istniejącą strukturę tabel
- nie podoba się nam koncepcja Microsoftu w kwestii pól w tabeli użytkowników
- chcemy zmienić sposób działania mechanizmu 
- mamy zamiar stworzyć Providera dla innego systemu bazodanowego niż MS SQL (choć do wielu popularnych silników bazodanowych są już stworzone providery)

Na początek przyjrzyjmy się przykładowej strukturze tabeli:


Taki schemat może istnieć w np. już istniejącym projekcie do którego chcemy dołożyć możliwość korzystania z zalet MembershipProvidera.

Na początek stworzymy klasę MyUser dziedziczącą po klasie MembershipUser. Klasa ta będzie implementować dodatkowe pola, który znajdują się w tabeli naszej bazy danych. Implementacja jest bardzo prosta:


Code:
public class MyUser : MembershipUser
    {
        private string firstName;

        public string FirstName
        {
            get { return firstName; }
            set { firstName = value; }
        }

        private string lastName;

        public string LastName
        {
            get { return lastName; }
            set { lastName = value; }
        }

        private DateTime birthDate;

        public DateTime BirthDate
        {
            get { return birthDate; }
            set { birthDate = value; }
        }

        private int idUser;

        public int IdUser
        {
            get { return idUser; }
            set { idUser = value; }
        }


        public MyUser(string providerName, string name, object providerUserKey, 
            string email, string passwordQuestion, string comment, bool isApproved, 
            bool isLockedOut, DateTime creationDate, DateTime lastLoginDate, 
            DateTime lastActivityDate, DateTime lastPasswordChangedDate, 
            DateTime lastLockoutDate, int idUser, string firstName, string lastName,
            DateTime birthDate)
            : base(providerName, name, providerUserKey, email, passwordQuestion, 
            comment, isApproved, isLockedOut, creationDate, lastLoginDate, 
            lastActivityDate, lastPasswordChangedDate, lastLockoutDate)
        {
            FirstName = firstName;
            LastName = lastName;
            BirthDate = birthDate;
        }
    }

Mając nową implementację klasy MembershipUser możemy jej użyć podczas implementacji MembershipProvider-a.

Aby zaimplementować własnego MembershipProvidera który używa innej implementacji MembershipUsera niż standardowa należy w klasie dziedziczącej po MembershipProvider wykonać następujące trzy czynności:
1. W metodach (pierwsza przyjmuje jako argumenty string i bool, druga object i bool) GetUser zwracamy instancję naszej klasy MembershipUser (oczywiście metoda nadal musi zwracać jako typ MembershipUser - ale oczywiście każdy zna zasady dziedziczenia i raczej rozumie o co chodzi w tym)
2. UpdateUser - w tej metodzie przyjmujemy obiekt typu MembershipUser. Obiekt ten musimy rzutować na nową implementację naszego MembershipUser.
3. CreateUser - metoda ta to jedna z najważniejszych metod. Pozwala na utworzenie nowego użytkownika w naszej aplikacji. Nasza klasa dziedzicząca po MembershipProvider musi posiadać podstawową wersję metody CreateUser. Aby utworzyć użytkownika z nowymi parametrami musimy stworzyć przeładowanie tej metody zawierające dodatkowe pola. Oprócz tego jako wynik działania klasy zwracamy nasz nowy typ użytkownika.

Tak więc moja implementacja powyższych metod przedstawia się następująco:


Code:
public class MyProvider : MembershipProvider
    {
        private string name = "app";
        private string connectionString = ConfigurationManager.ConnectionStrings[1].ConnectionString;

        public override string ApplicationName
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }
            MembershipUser user = GetUser(username, false);
            if (user == null)
            {
                DateTime createDate = DateTime.Now;
                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        byte[] clearPassword = Encoding.ASCII.GetBytes(password);
                        byte[] encryptetPassword = EncryptPassword(clearPassword);
                        cmd.CommandText = @"INSERT INTO Users(UserName, UserNameLowerCase, Password, PasswordSalt, FirstName, LastName, BirthDate, Email)
                                            VALUES(@UserName, @UserNameLowerCase, @Password, @PasswordSalt, @FirstName, @LastName, @BirthDate, @Email)";
                        cmd.Parameters.AddWithValue("@UserName", username);
                        cmd.Parameters.AddWithValue("@UserNameLowerCase", username.ToLower());
                        cmd.Parameters.AddWithValue("@Password", Convert.ToBase64String(encryptetPassword));
                        cmd.Parameters.AddWithValue("@PasswordSalt", password);
                        cmd.Parameters.AddWithValue("@Email", email);
                        try
                        {
                            cmd.Connection = conn;
                            conn.Open();
                            int iRecAdded = cmd.ExecuteNonQuery();
                            if (iRecAdded > 0)
                            {
                                status = MembershipCreateStatus.Success;
                            }
                            else
                            {
                                status = MembershipCreateStatus.UserRejected;
                            }
                        }
                        catch (Exception ex)
                        {
                            status = MembershipCreateStatus.UserRejected;
                        }
                        finally
                        {
                            conn.Close();
                        }
                        return GetUser(username, false);
                    }
                }

            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }
            return null;
        }

        public MyUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status, string firstName, string lastName, DateTime birthDate)
        {
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }
            MembershipUser user = GetUser(username, false);
            if (user == null)
            {
                DateTime createDate = DateTime.Now;
                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        byte[] clearPassword = Encoding.ASCII.GetBytes(password);
                        byte[] encryptetPassword = EncryptPassword(clearPassword);
                        cmd.CommandText = @"INSERT INTO Users(UserName, UserNameLowerCase, Password, PasswordSalt, FirstName, LastName, BirthDate, Email)
                                            VALUES(@UserName, @UserNameLowerCase, @Password, @PasswordSalt, @FirstName, @LastName, @BirthDate, @Email)";
                        cmd.Parameters.AddWithValue("@UserName", username);
                        cmd.Parameters.AddWithValue("@UserNameLowerCase", username.ToLower());
                        cmd.Parameters.AddWithValue("@Password", Convert.ToBase64String(encryptetPassword));
                        cmd.Parameters.AddWithValue("@PasswordSalt", password);
                        cmd.Parameters.AddWithValue("@FirstName", firstName);
                        cmd.Parameters.AddWithValue("@BirthDate", birthDate);
                        cmd.Parameters.AddWithValue("@Email", email);
                        try
                        {
                            cmd.Connection = conn;
                            conn.Open();
                            int iRecAdded = cmd.ExecuteNonQuery();
                            if (iRecAdded > 0)
                            {
                                status = MembershipCreateStatus.Success;
                            }
                            else
                            {
                                status = MembershipCreateStatus.UserRejected;
                            }
                        }
                        catch (Exception ex)
                        {
                            status = MembershipCreateStatus.UserRejected;
                        }
                        finally
                        {
                            conn.Close();
                        }
                        return (MyUser)GetUser(username, false);
                    }
                }
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }
            return null;
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override bool EnablePasswordReset
        {
            get { throw new NotImplementedException(); }
        }

        public override bool EnablePasswordRetrieval
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            MyUser user = null;
            SqlDataReader reader = null;
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.CommandText = @"SELECT IdUser, UserName, UserNameLowerCase, Password, PasswordSalt, FirstName, LastName, BirthDate
                                        FROM Users 
                                        WHERE UserNameLowerCase == @UserNameLowerCase";
                    cmd.Connection = conn;
                    cmd.Parameters.AddWithValue("@UserNameLowerCase", username);
                    try
                    {
                        conn.Open();
                        reader = cmd.ExecuteReader();
                        if (reader.HasRows)
                        {
                            reader.Read();
                            user = GetUserFromReader(reader);
                        }

                    }
                    catch (Exception e)
                    {
                    }
                    finally
                    {
                        if (reader != null)
                        {
                            reader.Close();
                        }
                        conn.Close();
                    }
                    return user;
                }
            }
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                using (SqlCommand cmd = new SqlCommand())
                {
                    MyUser myUser = user as MyUser;
                    if (myUser != null)
                    {
                        cmd.Connection = conn;
                        cmd.CommandText = @"UPDATE Users SET FirstName = @FirstName, LastName = @LastName, BirthDate = @BirthDate
                                        WHERE UserNameLowerCase = @UserNameLowerCase";
                        cmd.Parameters.AddWithValue("@FirstName", myUser.FirstName);
                        cmd.Parameters.AddWithValue("@LastName", myUser.LastName);
                        cmd.Parameters.AddWithValue("@BirthDate", myUser.BirthDate);
                        cmd.Parameters.AddWithValue("@UserNameLowerCase", myUser.UserName.ToLower());
                        try
                        {
                            cmd.ExecuteNonQuery();
                        }
                        catch (Exception ex)
                        {
                        }
                        finally
                        {
                            conn.Close();
                        }
                    }
                }
            }
        }

        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }

        private MyUser GetUserFromReader(SqlDataReader reader)
        {
            string username = reader["UserName"].ToString();
            string email = reader["Email"].ToString();
            int idUser = int.Parse(reader["IdUser"].ToString());
            string firstName = reader["FirstName"].ToString();
            string lastName = reader["LastName"].ToString();
            DateTime birthDate = DateTime.MinValue;
            if (reader["BirthDate"] != DBNull.Value)
            {
                birthDate = (DateTime)reader["BirthDate"];
            }
            MyUser u = new MyUser("", username, null, email, "", "", false, false, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,
                DateTime.MinValue, DateTime.MinValue, idUser, firstName, lastName, birthDate);
            return u;
        }
    }


Część metod jak widać nie jest zaimplementowana - nie wszystko jest wymagane. Jeżeli któraś jest nam potrzebna - implementujemy ją. W przeciwnym razie nie ma potrzeby implementowania czegoś, co nie będzie używane.

Kolejnym etapem jest dodanie Providera do Web.configa:


Code:
<?xml version="1.0"?>

<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->

<configuration>
  <connectionStrings>
    <clear/>
    <add name="Db" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=test2;Integrated Security=True"/>
  </connectionStrings>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <membership defaultProvider="MyProvider" userIsOnlineTimeWindow="15">
          <providers>
            <clear/>
            <add name="MyProvider" 
              type="WebApplication7.MyProvider"
              enablePasswordRetrieval="false"
              enablePasswordReset="false"
              requiresQuestionAndAnswer="false"
              applicationName="/"
              requiresUniqueEmail="false"
              passwordFormat="Clear"
              description="Stores and retrieves membership data from SQL Server"
              decryptionKey="68d288624f967bce6d93957b5341f931f73d25fef798ba75"
              validationKey="65a31e547b659a6e35fdc029de3acce43f8914cb1b2
                         4fff3e1aef13be438505b3f5becb5702d15bc7b98cd
                         6fd2b7702b46ff63fdc9ea8979f6508c82638b129a"
          />
          </providers>
        </membership>
      <machineKey
validationKey= 
"ADD36F43434EFE7750E6E56E3636D33A4D36356FA3A9190691A5BAA6FC5B6FD676C08D82A617ACEFBBCDCF6FC2B14955A66D83E2D1E13E92E617587D8B348355"
decryptionKey=
 "25AB4BB356AFF0E1690F87F824540B03D5EF7903B4134521DAAFB61B29889BF1"
validation="SHA1"
decryption="AES"/>
    </system.web>

</configuration>

Teraz już możemy wypróbować stworzony przez nas provider:


Code:
MyProvider user = (MyProvider)Membership.Provider;
            MembershipCreateStatus status;
            user.CreateUser(TextBox1.Text, TextBox2.Text, TextBox3.Text, "", "", false, null, out status,
                "Patryk", "Osowski", DateTime.Now);

Po wywołaniu kodu do bazy zostanie dodany nowy użytkownik.

W taki oto sposób utworzyliśmy kompletny MembershipProvider który możemy wykorzystać w naszym projekcie.

Brak komentarzy:

Prześlij komentarz