There is something that has bothered me for a long time about my upcoming website. I use MySQL to store all of my data in, such as these blog entries which are on Blogger at the moment. However, I wanted to use the built in .NET User Management system, which takes care of registered user management automatically and securely. However, the problem has always been that while the login mechanism worked perfectly well from the Development Environment using the built in server, I could never get it to work once I transferred my work to IIS. I had tried several times in the past, without sucess to get this to work, but failed each time. So I decided Saturday that I was going to try again fora final time.
I Googled for the better part of a few hours, and had several people attempt to help. But nothing seemed to work, so I decided that I was fed up with the matter and thought of implementing my own solution. But given that I wanted to perserve the “clean” code that the automated system employed, I was not able to implement my own in the standard way. After searching through the various object involved with Membership and Roles in .NET I was unable to find a good pragmatic solution. I even considered putting it back to work on at some other time. However, then I remembered something I had looked at quite a while ago: Custom Providers.
Like any good Programming Framework, .NET allows you to use custom code to replace/extend existing features. In this case, by default, ASP .NET uses the SQL Membership and Role Provider class that tell it how it should interact with the SQL database that is housing the user information. Simply implementing derived classes from the base of SQLProvider allows you to implement providers for whatever database solution you choose. In addition, thanks to properly setting up these base classes, all features are available to all database types, through program implementation. So I decided to move my user login system into the MySQL database housing everything else; this makes more sense anyway, as its no real use to have two databases of differing types when one will suffice and save me a connection, and set of connecting information being stored in the web.config.
So the first thing to understand is the class relationship for these Custom Providers. Heres is the basic relationship:
RoleProvider -> MysqlRoleProvider
MembershipProvider -> MysqlMembershipProvider
So the two classes I am working with are MysqlRoleProvider (for dealing with ASP .NET Roles) and MysqlMembershipProvider (for dealing with ASP .NET site Membership). I am not going to list out the methods for each that need to be overridden, but rather provide links to the MSDN docs that I found quite helpful.
Example of Membership Provider: Implementing a Membership Provider
Example of Role Provider:
Implementing a Role Provider
The first thing to realize about this is that we have to tell our ASP .NET application which provider to use and which is the default. This involves simply adding a couple attributes and tags to our web.config file: Here is what I added to my web.config:
— For Membership —
<providers> <add name="MysqlMembershipProvider" type="MysqlMembershipProvider" requiresQuestionAndAnswer="true" connectionString="" /> </providers> </membership> --- For Roles --- <roleManager defaultProvider="MysqlRoleProvider"> <providers> <add name="MysqlRoleProvider" type="MysqlRoleProvider" connectionString="" applicationName="Website" writeExceptionsToEventLog="false" /> </providers> </roleManager>
Some things to note, you can leave the connection string empty here and access it as normal through the ConfigurationManager.ConnectionStrings hash – or, if you want to have a different database to store the information you can provide them here and access this connection string in the Initialize method implemented by the two providers through the config function parameter.
Finally, the afore mentioned config parameter also houses a key for each of the attributes you defined in the <add> tag in the web.config. This enables you to use this hash to store default values for your custom provider. This way you can set the settings for user management according to how you like. Now we go to speaking to the actual implementation of these custom providers.
The first thing I did was implement the Membership Provider and test its functionality my dragging a Create User Control onto a separate ASPX page. This is quite nice as you can easily break point the classes and see whats going on, its gets a little more complicated as you cant break point. But lets walk through the steps I took implementing the Membership and Role Providers, as well as my implementation validation methods.
First, after I created the Create User Control I added exception throws to each function my class implemented so I would know what functions I had to implement to get the basic functionality. The first was MembershipProvider::CreateUser which essentially inserted a user into our database. I used Microsoft’s example layout, which is horribly designed and not consistent with Database normal forms, which utilized three tables: Users, Roles, UsersInRoles.
So the create user function effectively checks if a user exists for a current application (the application name is stored in the web.config file) and insert them otherwise with the appropriate values. Perhaps the hardest part of this portion was figuring out how to properly Hash the passwords. I don’t much like storing passwords as plain-text, and while the Membership Provider supports plain-text, hashed, and encrypted passwords it seemed to be a fairly easy task. However, I was unable to get the automated routines to work, so I ended up finding code that would do the MD5 hash for me, and use that. I am still working on this portion and hope I will get it to work properly soon, as it is the proper solution.
Moving on, now that I could store users I moved onto allowing them to login. This was a simple matter of implementing MembershipProvider::ValidateUser. Again no problems here, and because of the way this is implemented you can customize the way users log in to the application to your hearts content. With this in place, I could now log into my website using MySQL, however, we were missing a key component: roles.
ASP .NET roles serve as a simple means of Access Control, that is they make it easy to determine who can do what on your site. In my website, the admin controls are only visible to users in the ‘admin’ role. So without this in place, despite the fact that we are able to login, were not able to do anything useful. So moving on to the implementation of the Role Provider. The development of this class was complicated due to the lack of debugging. Because modification of Roles can only take place in ASP .NET Web Site Configuration Manager, which runs in non-Debug mode, throwing exceptions was the only means of determine where control was when something went wrong.
Really the only problem to speak of was the AddUsersToRoles function, which has an odd way of doing things, though it is conducive to the way the Configuration tool presents the options to the admin. I had to copy and paste someoneelses code, but in the end it was a simple mistake that was killing mine. Once this was installed and I had done some more testing and further implemented needed functions (again I was using the same process of exception throwing from the Membership class), I had a working automated authentication system using MySQL on the backend to store users and roles and validate them. My hidden content was even working at this point with the roles that I stored.
This is definitely one of the more useful features I have found in .NET and is example of the built in flexibility a good framework like .NET has, and also the wealth of features and freedom provided to the programmer. There are very few restrictions placed on the programmer who wishes to implement a custom provider. While I did utilize the database design, which as I said is horrible, that Microsoft provided, it is by no means restricted to this as the model is very devoid of cohesive functions. That is, the end objects only care about what is returned, now how it was obtained.
I look forward to holding on to this code as it will be very useful in the future as will the knowledge of understanding this so that applications are not seen as restricted to only using what Microsoft provides.