How do I set up my membership provider correctly?
After all the answers to my last tweak question were more helpful than I expected, I thought I was asking another similar question about MembershipProviders as well.
So first, to clarify: I know what membership, role and profile are, how to implement my own, and how to set them up, and a lot of them. Implementing the role and profile provider is pretty straightforward because they require a simple CRUD most of the time. (One LINQ line is sufficient for about half of the RoleProvider methods.)
However, the membership provider is a different monster. Many of you may realize that this violates the SR (Single Responsibility) principle, because it should do EVERYTHING related to user management. While this leaves a lot of room for customization, it has its drawbacks. There is no information on the internet about what their expected EXACT behavior is, like when they should throw exceptions or just return zero and the like.
I am using this example implementation for reference, but it also contains a few contradictions.
- For example, it uses its own ValidateUser method to validate credentials in the ChangePassword method. But ValidateUser also updates the LastLoginDate user to the current date. So, can the framework expect me to install it in its own provider, or is it just a bug in the sample?
- Other: the ChangePassword method throws an exception every time a new password is checked, but CreateUser never throws an exception, it just returns false.
-
Last but not least, it counts the user's invalid password attempts and blocks them if it passes the threshold. While this is good, manual action is required to unlock it. Is this a problem if my ISP automatically unblocks the user after a certain time?
-
(EDIT) I almost forgot: The CreateUser method in the sample inserts the ID from the method parameter. I actually think this is bad practice because I am using auto-compressing inter as identifiers, so inserting them from some method parameter is not an option. Should I just ignore the parameter or require its value to be null and throw an exception if it is not?
Overall, does ASP.NET have any assumptions about the behavior of the MembershipProvider element?
Is there any documentation out there that outlines when I should throw an exception or just return null?
I also tried to find a set of typical unit tests that would provide some guidance on expected behavior, but with no luck, I found many articles on "unit testing" and "How to unit test a MembershipProvider" but not one where any actual tests would be ...
Thanks in advance everyone!
a source to share
You can consult MSDN. For example, it RoleProvider.RemoveUsersFromRoles
contains the following guidelines:
RemoveUsersFromRoles is called by RemoveUserFromRole, RemoveUsersFromRole, RemoveUserFromRoles and the RemoveUsersFromRoles Role class methods to remove the specified users from the specified roles to the data source. Only the roles for the configured application name changed.
If any of the specified role names are not found for the configured applicationName, we recommend your ProviderException.
If any of the specified usernames are not associated with any of the specified role names for the configured application name, we recommend that your provider has a ProviderException.
If any of the specified usernames are null or an empty string, we recommend that your provider throw an exception.
If any of the specified role names are null or an empty string, we recommend that your provider throw an exception.
If your data source supports transactions, we recommend that you include every delete operation in a transaction and that you roll back transactions and throw an exception if any delete operation does not work.
And he RoleProvider.GetRolesForUser
says:
GetRolesForUser is called by the GetRolesForUser method of the Roles class to retrieve the names of the roles with which the specified user is associated with the data source. Get only the roles for the customized application name.
If roles do not exist for the specified user for the configured application name, we recommend that your provider return an array of strings without elements.
If the specified username is null or is an empty string, we recommend that your provider throw an exception.
But in practice, each provider has only a few methods and behaviors that are required and expected. The rest is support for the features you want to implement.
After working with the default vendor stack for several years, I realized that you are pause on the pattern you are referring to, which is a very simple implementation that cuts corners, for example in the ChangePassword method.
If you use a reflector and examine the SqlMembershipProvider, you will notice some significant differences ...
For instance:
-
ValidateUser updates the login date as the user logs in, and he does so by calling .CheckPassword with true on UpdateLastLoginTime. Change password invokes the same method, but sends false.
-
The CreateUser method takes a ProviderUserKey because SPROC will also take a UserId parameter. This allows for recreation and synchronization between membership and user tables. As an API consumer, you usually don't use this functionality, but the provider stack does it internally.
-
About blocking .. It is up to you. This is what custom providers do: getting the desired behavior. As I said, there are very few systematic requirements.
So this brings us to the last part of your question (both paragraphs refer to the same issue): expected behavior and unit testing ...
Since the behavior of the provider is largely arbitrary and there are few expected behaviors, the established set of common tests will contain very few methods. You need to write tests that relate to the behavior of your implementation.
As a final note, I suggest you find and download samples of the Asp.Net vendor toolkit. It provides you with the source code for a complete package of Sql providers and will give you some insight into how "real" providers are implemented.
afterthoughts
At this very moment, I am implementing a complete package of SQLite providers that is 100% compatible with the default Sql stack. The test suite checks for behavioral parity between my stack and asp.net. If you click my blog through my profile and contact me, I'll let you know when the tests are complete and you can use them as a guideline as to what exactly is expected from the default stack.
a source to share