using System; using System.Web; using System.Web.Security; using System.Data; using System.Data.SqlClient; using Csla.Data; using System.Collections.Specialized; using Csla; using System.Reflection; using System.Collections.Generic; namespace { /// /// CslaMembershipProvider serves as a Membership Provider in the Membership API, effectively wrapping an implemented provider. /// /// /// Note that properties don't utilize remoting. I don't believe it's necessary. /// We could do the same thing - executing properties remotely, but it'll incur additional overhead. /// Typically, these properties simply expose configuration values. /// /// Methods are very straightforward. /// 1) If we're running on the server already, execute the wrapped provider. /// 2) Otherwise, make a call to ExecuteRemoteMethod, which will facilitate the running of this remotely. /// public class CslaMembershipProvider : MembershipProvider { /// /// The wrapped provider for which to provide implementation. /// MembershipProvider _wrappedProvider; #region Initialize /// /// Initialize is overridden to take the name/value collection and strip off the one thing /// we care about - the functionalProvider. It's removed to not cause exceptions on the /// implemented providers. /// public override void Initialize(string name, NameValueCollection config) { if (config == null) { throw new ArgumentNullException("config"); } // Determine the functional provider, instantiate and initialize it. Type functionalProviderType = Type.GetType(config["functionalProvider"]); config.Remove("functionalProvider"); _wrappedProvider = (MembershipProvider)Activator.CreateInstance(functionalProviderType); _wrappedProvider.Initialize(name, config); } #endregion #region ExecuteRemote Command /// /// Execute the calling method remotely using the parameters specified. /// /// /// Unfortunately, it's not really possible to get the values of the parameters from the /// calling method, which would be nice. /// private ExecuteRemoteMethodResult ExecuteRemoteMethod(object[] parameters) { ExecuteRemoteMethodCommand command; MethodBase callingMethod; // Get the calling method from the stack trace. MethodBase is serializable. // It's especially useful to have the MethodBase because GetMethod is tricky // when overloads and output parameters exist. callingMethod = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod(); try { // Use ExecuteRemoteMethodCommand to execute the method on the server // (This would be the remote application server, if applicable, or the local machine). command = DataPortal.Execute( new ExecuteRemoteMethodCommand(ApplicationName, callingMethod, parameters)); } catch (DataPortalException ex) { // If an exception is thrown, need to throw the original exception, not the DataPortalException throw ex.BusinessException; } // Return the result of the command, which includes a return value and out parameters. return command.Result; } [Serializable()] private class ExecuteRemoteMethodResult { private object _returnValue; private Dictionary _outParameters; /// /// Return value from the remotely executed method. /// public object ReturnValue { get { return _returnValue; } } /// /// Out parameters from the remotely executed method. /// public Dictionary OutParameters { get { return _outParameters; } } /// /// Constructor to establish an ExecuteRemoteMethodResult with the return value and out parameters. /// /// Return value from the remotely executed method /// Out parameters from the remotely executed method public ExecuteRemoteMethodResult(object returnValue, Dictionary outParameters) { _returnValue = returnValue; _outParameters = outParameters; } } /// /// The ExecuteRemoteMethodCommand executes a method remotely if necessary through the dataportal, /// returning a result that can be processed. /// /// /// I would have liked to unite the commands for the MembershipProvider and RoleProvider into /// a common class, and may do so in the future, but the utilization of of the actual APIs seemed /// a little tricky to segment. Undoubtedly, this can be solved - just not today. It works. /// [Serializable()] private class ExecuteRemoteMethodCommand : CommandBase { private string _applicationName; private MethodBase _callingMethod; private object[] _parameters; private ExecuteRemoteMethodResult _result; /// /// The ApplicationName to run for /// public string ApplicationName { get { return _applicationName; } } /// /// MethodBase to execute remotely. /// public MethodBase CallingMethod { get { return _callingMethod; } } /// /// Parameters to execute the CallingMethod with. /// public object[] Parameters { get { return _parameters; } } /// /// The result of the CallingMethod's execution. /// public ExecuteRemoteMethodResult Result { get { return _result; } } /// /// Constructor for the command, including the application name, calling method, and parameters. /// /// ApplicationName to run for /// MethodBase to invoke method on /// Parameters to call the MethodBase with public ExecuteRemoteMethodCommand(string applicationName, MethodBase callingMethod, object[] parameters) { _callingMethod = callingMethod; _parameters = parameters; _applicationName = applicationName; } /// /// Defined interface for executing the command on the server for CommandBase /// protected override void DataPortal_Execute() { Dictionary outParameters; object returnValue; ParameterInfo[] paramInfo; // Establish a dictionary for out parameters. outParameters = new Dictionary(); // Need to execute the method against the Roles API - get the current provider. MembershipProvider currentProvider = Membership.Provider; // Establish the application for which to run in. // (We might have a remoting directory set up for many applications). currentProvider.ApplicationName = _applicationName; // Invoke the calling method with the supplied parameters on the current provider. returnValue = _callingMethod.Invoke(currentProvider, _parameters); // Get the parameter info from the calling method to determine which are out parameters. paramInfo = _callingMethod.GetParameters(); for (int i = 0; i < paramInfo.Length; i++) { // If out parameter, add to the dictionary of out parameters. if (paramInfo[i].IsOut) { outParameters.Add(paramInfo[i].Name, _parameters[i]); } } // Establish the result of the remotely executed method. _result = new ExecuteRemoteMethodResult(returnValue, outParameters); } } #endregion #region Properties public override string Name { get { return _wrappedProvider.Name; } } public override bool EnablePasswordReset { get { return _wrappedProvider.EnablePasswordReset; } } public override bool EnablePasswordRetrieval { get { return _wrappedProvider.EnablePasswordRetrieval; } } public override string ApplicationName { get { return _wrappedProvider.ApplicationName; } set { _wrappedProvider.ApplicationName = value; } } public override string PasswordStrengthRegularExpression { get { return _wrappedProvider.PasswordStrengthRegularExpression; } } public override bool RequiresQuestionAndAnswer { get { return _wrappedProvider.RequiresQuestionAndAnswer; } } public override bool RequiresUniqueEmail { get { return _wrappedProvider.RequiresUniqueEmail; } } public override int MaxInvalidPasswordAttempts { get { return _wrappedProvider.MaxInvalidPasswordAttempts; } } public override int MinRequiredNonAlphanumericCharacters { get { return _wrappedProvider.MinRequiredNonAlphanumericCharacters; } } public override int MinRequiredPasswordLength { get { return _wrappedProvider.MinRequiredPasswordLength; } } public override int PasswordAttemptWindow { get { return _wrappedProvider.PasswordAttemptWindow; } } public override MembershipPasswordFormat PasswordFormat { get { return _wrappedProvider.PasswordFormat; } } /// /// If we are running locally or the execution location is on the server. /// /// /// This is used to determine if we need to facilitate the running of the current method remotely /// or if we're already in the right place. /// /// It would probably be wise to put this on a more global level. /// private static bool ExecutingOnServer { get { return Csla.ApplicationContext.DataPortalProxy == "Local" || Csla.ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Server; } } #endregion #region Methods public override bool ChangePassword(string username, string oldPassword, string newPassword) { bool changed; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { changed = _wrappedProvider.ChangePassword(username, oldPassword, newPassword); } else { result = ExecuteRemoteMethod(new object[] { username, oldPassword, newPassword }); changed = (bool)result.ReturnValue; } return changed; } public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { bool changed; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { changed = _wrappedProvider.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); } else { result = ExecuteRemoteMethod(new object[] { username, password, newPasswordQuestion, newPasswordAnswer }); changed = (bool)result.ReturnValue; } return changed; } public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { MembershipUser user; MembershipCreateStatus tempStatus = MembershipCreateStatus.ProviderError; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { user = _wrappedProvider.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); } else { result = ExecuteRemoteMethod(new object[] { username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, tempStatus }); user = (MembershipUser)result.ReturnValue; status = (MembershipCreateStatus)result.OutParameters["status"]; } return user; } public override bool DeleteUser(string username, bool deleteAllRelatedData) { bool deleted; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { deleted = _wrappedProvider.DeleteUser(username, deleteAllRelatedData); } else { result = ExecuteRemoteMethod(new object[] { username, deleteAllRelatedData }); deleted = (bool)result.ReturnValue; } return deleted; } public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { MembershipUserCollection users; int tempTotalRecords = 0; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { users = _wrappedProvider.FindUsersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords); } else { result = ExecuteRemoteMethod(new object[] { emailToMatch, pageIndex, pageSize, tempTotalRecords }); users = (MembershipUserCollection)result.ReturnValue; totalRecords = (int)result.OutParameters["totalRecords"]; } return users; } public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { MembershipUserCollection users; int tempTotalRecords = 0; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { users = _wrappedProvider.FindUsersByName(usernameToMatch, pageIndex, pageSize, out totalRecords); } else { result = ExecuteRemoteMethod(new object[] { usernameToMatch, pageIndex, pageSize, tempTotalRecords }); users = (MembershipUserCollection)result.ReturnValue; totalRecords = (int)result.OutParameters["totalRecords"]; } return users; } public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { MembershipUserCollection users; int tempTotalRecords = 0; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { users = _wrappedProvider.GetAllUsers(pageIndex, pageSize, out totalRecords); } else { result = ExecuteRemoteMethod(new object[] { pageIndex, pageSize, tempTotalRecords }); users = (MembershipUserCollection)result.ReturnValue; totalRecords = (int)result.OutParameters["totalRecords"]; } return users; } public override int GetNumberOfUsersOnline() { int userCount; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { userCount = _wrappedProvider.GetNumberOfUsersOnline(); } else { result = ExecuteRemoteMethod(new object[] { }); userCount = (int)result.ReturnValue; } return userCount; } public override string GetPassword(string username, string answer) { string password; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { password = _wrappedProvider.GetPassword(username, answer); } else { result = ExecuteRemoteMethod(new object[] { username, answer }); password = (string)result.ReturnValue; } return password; } public override MembershipUser GetUser(string username, bool userIsOnline) { MembershipUser user; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { user = _wrappedProvider.GetUser(username, userIsOnline); } else { result = ExecuteRemoteMethod(new object[] { username, userIsOnline }); user = (MembershipUser)result.ReturnValue; } return user; } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { MembershipUser user; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { user = _wrappedProvider.GetUser(providerUserKey, userIsOnline); } else { result = ExecuteRemoteMethod(new object[] { providerUserKey, userIsOnline }); user = (MembershipUser)result.ReturnValue; } return user; } public override string GetUserNameByEmail(string email) { string userName; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { userName = _wrappedProvider.GetUserNameByEmail(email); } else { result = ExecuteRemoteMethod(new object[] { email }); userName = (string)result.ReturnValue; } return userName; } public override string ResetPassword(string username, string answer) { string password; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { password = _wrappedProvider.ResetPassword(username, answer); } else { result = ExecuteRemoteMethod(new object[] { username, answer }); password = (string)result.ReturnValue; } return password; } public override bool UnlockUser(string userName) { bool unlocked; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { unlocked = _wrappedProvider.UnlockUser(userName); } else { result = ExecuteRemoteMethod(new object[] { userName }); unlocked = (bool)result.ReturnValue; } return unlocked; } public override void UpdateUser(MembershipUser user) { if (ExecutingOnServer) { _wrappedProvider.UpdateUser(user); } else { ExecuteRemoteMethod(new object[] { user }); } return; } public override bool ValidateUser(string username, string password) { bool validated; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { validated = _wrappedProvider.ValidateUser(username, password); } else { result = ExecuteRemoteMethod(new object[] { username, password }); validated = (bool)result.ReturnValue; } return validated; } #endregion } /// /// CslaRoleProvider serves as a Role Provider in the Role API, effectively wrapping an implemented provider. /// /// /// Note that properties don't utilize remoting. I don't believe it's necessary. /// We could do the same thing - executing properties remotely, but it'll incur additional overhead. /// Typically, these properties simply expose configuration values. /// /// Methods are very straightforward. /// 1) If we're running on the server already, execute the wrapped provider. /// 2) Otherwise, make a call to ExecuteRemoteMethod, which will facilitate the running of this remotely. /// public class CslaRoleProvider : RoleProvider { /// /// The wrapped provider for which to provide implementation. /// RoleProvider _wrappedProvider; #region Initialize /// /// Initialize is overridden to take the name/value collection and strip off the one thing /// we care about - the functionalProvider. It's removed to not cause exceptions on the /// implemented providers. /// public override void Initialize(string name, NameValueCollection config) { if (config == null) { throw new ArgumentNullException("config"); } // Determine the functional provider, instantiate and initialize it. Type functionalProviderType = Type.GetType(config["functionalProvider"]); config.Remove("functionalProvider"); _wrappedProvider = (RoleProvider)Activator.CreateInstance(functionalProviderType); _wrappedProvider.Initialize(name, config); } #endregion #region ExecuteRemote Command /// /// Execute the calling method remotely using the parameters specified. /// /// /// Unfortunately, it's not really possible to get the values of the parameters from the /// calling method, which would be nice. /// private ExecuteRemoteMethodResult ExecuteRemoteMethod(object[] parameters) { ExecuteRemoteMethodCommand command; MethodBase callingMethod; // Get the calling method from the stack trace. MethodBase is serializable. // It's especially useful to have the MethodBase because GetMethod is tricky // when overloads and output parameters exist. callingMethod = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod(); try { // Use ExecuteRemoteMethodCommand to execute the method on the server // (This would be the remote application server, if applicable, or the local machine). command = DataPortal.Execute( new ExecuteRemoteMethodCommand(ApplicationName, callingMethod, parameters)); } catch (DataPortalException ex) { // If an exception is thrown, need to throw the original exception, not the DataPortalException throw ex.BusinessException; } // Return the result of the command, which includes a return value and out parameters. return command.Result; } [Serializable()] private class ExecuteRemoteMethodResult { private object _returnValue; private Dictionary _outParameters; /// /// Return value from the remotely executed method. /// public object ReturnValue { get { return _returnValue; } } /// /// Out parameters from the remotely executed method. /// public Dictionary OutParameters { get { return _outParameters; } } /// /// Constructor to establish an ExecuteRemoteMethodResult with the return value and out parameters. /// /// Return value from the remotely executed method /// Out parameters from the remotely executed method public ExecuteRemoteMethodResult(object returnValue, Dictionary outParameters) { _returnValue = returnValue; _outParameters = outParameters; } } /// /// The ExecuteRemoteMethodCommand executes a method remotely if necessary through the dataportal, /// returning a result that can be processed. /// /// /// I would have liked to unite the commands for the MembershipProvider and RoleProvider into /// a common class, and may do so in the future, but the utilization of of the actual APIs seemed /// a little tricky to segment. Undoubtedly, this can be solved - just not today. It works. /// [Serializable()] private class ExecuteRemoteMethodCommand : CommandBase { private string _applicationName; private MethodBase _callingMethod; private object[] _parameters; private ExecuteRemoteMethodResult _result; /// /// The ApplicationName to run for /// public string ApplicationName { get { return _applicationName; } } /// /// MethodBase to execute remotely. /// public MethodBase CallingMethod { get { return _callingMethod; } } /// /// Parameters to execute the CallingMethod with. /// public object[] Parameters { get { return _parameters; } } /// /// The result of the CallingMethod's execution. /// public ExecuteRemoteMethodResult Result { get { return _result; } } /// /// Constructor for the command, including the application name, calling method, and parameters. /// /// ApplicationName to run for /// MethodBase to invoke method on /// Parameters to call the MethodBase with public ExecuteRemoteMethodCommand(string applicationName, MethodBase callingMethod, object[] parameters) { _callingMethod = callingMethod; _parameters = parameters; _applicationName = applicationName; } /// /// Defined interface for executing the command on the server for CommandBase /// protected override void DataPortal_Execute() { Dictionary outParameters; object returnValue; ParameterInfo[] paramInfo; // Establish a dictionary for out parameters. outParameters = new Dictionary(); // Need to execute the method against the Roles API - get the current provider. RoleProvider currentProvider = Roles.Provider; // Establish the application for which to run in. // (We might have a remoting directory set up for many applications). currentProvider.ApplicationName = _applicationName; // Invoke the calling method with the supplied parameters on the current provider. returnValue = _callingMethod.Invoke(currentProvider, _parameters); // Get the parameter info from the calling method to determine which are out parameters. paramInfo = _callingMethod.GetParameters(); for (int i = 0; i < paramInfo.Length; i++) { // If out parameter, add to the dictionary of out parameters. if (paramInfo[i].IsOut) { outParameters.Add(paramInfo[i].Name, _parameters[i]); } } // Establish the result of the remotely executed method. _result = new ExecuteRemoteMethodResult(returnValue, outParameters); } } #endregion #region Properties public override string Name { get { return _wrappedProvider.Name; } } public override string ApplicationName { get { return _wrappedProvider.ApplicationName; } set { _wrappedProvider.ApplicationName = value; } } /// /// If we are running locally or the execution location is on the server. /// /// /// This is used to determine if we need to facilitate the running of the current method remotely /// or if we're already in the right place. /// /// It would probably be wise to put this on a more global level. /// private static bool ExecutingOnServer { get { return Csla.ApplicationContext.DataPortalProxy == "Local" || Csla.ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Server; } } #endregion #region Methods public override void AddUsersToRoles(string[] usernames, string[] roleNames) { if (ExecutingOnServer) { _wrappedProvider.AddUsersToRoles(usernames, roleNames); } else { ExecuteRemoteMethod(new object[] { usernames, roleNames }); } return; } public override void CreateRole(string roleName) { if (ExecutingOnServer) { _wrappedProvider.CreateRole(roleName); } else { ExecuteRemoteMethod(new object[] { roleName }); } return; } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { bool deleted; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { deleted = _wrappedProvider.DeleteRole(roleName, throwOnPopulatedRole); } else { result = ExecuteRemoteMethod(new object[] { roleName, throwOnPopulatedRole }); deleted = (bool)result.ReturnValue; } return deleted; } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { string[] users; ExecuteRemoteMethodResult result; // If the DataPortalProxy is local or we're already on the server, run the method -- we're in the right place! if (ExecutingOnServer) { users = _wrappedProvider.FindUsersInRole(roleName, usernameToMatch); } else { result = ExecuteRemoteMethod(new object[] { roleName, usernameToMatch }); users = (string[])result.ReturnValue; } return users; } public override string[] GetAllRoles() { string[] roles; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { roles = _wrappedProvider.GetAllRoles(); } else { result = ExecuteRemoteMethod(new object[] { }); roles = (string[])result.ReturnValue; } return roles; } public override string[] GetRolesForUser(string username) { string[] roles; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { roles = _wrappedProvider.GetRolesForUser(username); } else { result = ExecuteRemoteMethod(new object[] { username }); roles = (string[])result.ReturnValue; } return roles; } public override string[] GetUsersInRole(string roleName) { string[] users; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { users = _wrappedProvider.GetUsersInRole(roleName ); } else { result = ExecuteRemoteMethod(new object[] { roleName }); users = (string[])result.ReturnValue; } return users; } public override bool IsUserInRole(string username, string roleName) { bool isInRole; ExecuteRemoteMethodResult result; if (ExecutingOnServer) { isInRole = _wrappedProvider.IsUserInRole(username, roleName); } else { result = ExecuteRemoteMethod(new object[] { username, roleName }); isInRole = (bool)result.ReturnValue; } return isInRole; } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { if (ExecutingOnServer) { _wrappedProvider.RemoveUsersFromRoles(usernames, roleNames); } else { ExecuteRemoteMethod(new object[] { usernames, roleNames }); } return; } public override bool RoleExists(string roleName) { bool exists; ExecuteRemoteMethodResult result; // If the DataPortalProxy is local or we're already on the server, run the method -- we're in the right place! if (ExecutingOnServer) { exists = _wrappedProvider.RoleExists(roleName); } else { result = ExecuteRemoteMethod(new object[] { roleName }); exists = (bool)result.ReturnValue; } return exists; } #endregion } }