To help explain some of the recent changes in the latest DotNetNuke Core release, I decided to blog about the Membership Services Provider abstraction. While this will not go into great detail for you to start creating your own set of providers, it should provide some guidance to help you understand what has changed and some history behind it to help understand why. To help make sure all levels of readers can follow along there are a few fundamental things to understand before moving on:
- An abstract provider is, simply put, an API. This is a base set of methods which any inheriting concrete provider must override.
- A concrete provider inherits from an abstract provider and overrides its methods.
- Concrete providers interact with the data store and therefore are normally written data store specific (if the provider has any type of functionality that requires interacting with the data store).
- All Providers are set in the Web.Config. Some specific providers have items, also set in the Web.Config, which determine the way the provider functions (Some more than others).
The original DotNetNuke 3 release used the ASP.NET backport MemberRole.dll, to prepare for the 2.0 ASP.NET framework. The way this was implemented into DotNetNuke adhered to the API of the MemberRole concrete providers and DotNetNuke based its membership API (the core's basis of its abstract provider) on it. MemberRole was actually all membership services including: membership, profile and roles. Since DotNetNuke has multi-portal capabilities, there were some things which MemberRole was not designed for and since DotNetNuke was working closely with Microsoft they added the ApplicationName columns to make the provider usable in situations like DotNetNuke.
When the time came to start building against 2.0 framework classes instead of MemberRole.dll there were some changes which had to occur and even Microsoft warned of this possibility prior to distributing MemberRole.dll. To handle this prior to releasing the first 4.x core, the changes were minimal but did require work that did not break the API already in place of the core. These changes were made in both 4 and 3 lines of the core to keep the code bases in synch as much as possible.
When decisions were being made as to what focus on for the 3.3/4.3 release, a great deal of discussion and time was spent on figuring out the majority of use cases for DotNetNuke when it comes to Membership Services. In addition to this, the core team also realized its self imposed limitations in its first set of concrete providers (which were the same ones built for the original 3 release). One of the biggest limitations was that the way this was originally implemented, it was almost impossible for someone to write their own concrete provider to use with DotNetNuke without requiring core changes. The simplest way to summarize this is that the original API written in the core was not flexible enough to be a solid API and function as an abstract provider allowing people to create their own providers without modifying the core. (Thus not adhering to the provider model)
Although there were some changes to the previously existing functions, the now legacy ones will function for the time being which avoids breaking changes for module developers. If you are working in a module project which interacts with Membership, roles, profile methods; you should notice the items are marked as legacy and prior to releasing a new version of your module you should focus using the newly provided methods when you are able. Of course, once you rely on these new methods your module will only work with DotNetNuke versions 3.3/4.3 or greater. One last major item to mention that all these providers have in common is that the new abstraction has lowered the number of hits to the database.
Membership - AspNet Membership Provider
Concrete Provider Located @ - Providers\MembershipProviders\AspNetMembershipProvider\AspNetMembershipProvider.vb
This is the original 3.x implementation rewritten to adhere to the new API provided by the core. Since DotNetNuke has been distributed for over a year based on the aspnet implementation (MemberRole & 2.0 Framework classes) the first task was to tackle the API and create a new set of providers using the aspnet data store items. Because of how critical these items are to running a DotNetNuke site in addition to the number of users already operating sites based on this current setup this new AspNetMembershipProvider is simply what was available before but now the ability for developers to create their own custom concrete Membership provider is possible without core changes. This concrete provider functions the same as the previous 3.x/4.x releases and does not support Single Sign On.
Previously and currently relies on these tables: aspnet_Membership, aspnet_Users as well as DNN's UserPortals, Users.
Roles - DNN Role Provider
Concrete Provider Located @ - Providers\MembershipProviders\DNNMembershipProvider\DNNRoleProvider.vb
As with Membership not too much has changed here either. The difference is that now the aspnet data store items are not used at all. Again, module developers should remove dependancy on legacy methods and adapt to using the latest API when developing newer module version or creating new modules. There is the basic addition of Role Groups in this instance as well (which the aspnet provider would not have supported out of the box), but currenly not much was developed to take advantage of this new addition (yet).
Previously used these tables: aspnet_Roles, aspnet_UsersInRoles. This also relied on Roles, UserRoles.
Currently uses these tables: Roles. UserRoles, RoleGroups.
Profile - DNN Profile Provider
Concrete Provider Located @ - Providers\MembershipProviders\DNNMembershipProvider\DNNProfileProvider.vb
Now this one is a huge change. If you have worked with the latest 3.3/4.3 you have probably seen the profile propery editor. This allows you to create registration items as well as profile items to customize your registration process. This provider tooks tons of effort and loads of testing. Not only was the provider itself built for this (in addition to the API changes), but everything around what was formerly Register.ascx and the two controls it used (Address.ascx & User.ascx I think was the other) had to change to work in the new registration which is dynamically loaded instead of being a single 'hard coded' ascx. One major benefit (besides dynamically built registration) is that the profile properties can be set per portal. This is a major change over the previous profile provider based on the aspnet implementation. (Remember, MemberRole was not originally built with the multi-portal environment in mind)
Those who worked with the previous AspNet implementation are probably aware of 'The Blob'!!! The blob is basically deliminted data stored between two columns: PropertyNames, PropertyValuesString. One column said what properties were collected, the other retained the values for the properties collected. (I believe both were seperated by the | character) Storing data in this manner allows it to be very extensible so you can easily add profile properties and values without changing the data schema but it presented a couple problems:
- Ability to easily query data - since it was stored as ntext there were certain SQL restrictions that nvarchar column types do not have. A sample query of 'SELECT * from table where Region = Georgia' was not possible unless you created UDF's or did this from code (a bit much to find out users who live in Georgia don't ya think?).
- Not as efficient as possible. - The way ntext works it points to a seperate spot (page) in MS SQL, while nvarchar actually stores the value within the table. Its best to use nvarchar when possible, but developers have to be concerned of the row size limit unless they use ntext. (8060 bytes I believe)
The current implementation stores a new row of data for each profile property. If the data length going into the sproc is below a certain level (7500 I think), it is stored in the PropertyValue column as nvarchar, otherwise it is stored in the PropertyText ntext column. This allows one row of data per property and also increases efficiency. One final addition here is that since the API is available to modules, module developers can start storing user specific data in this table using the API. This will allow certain things to be shared across modules. A good example would be if two modules wanted to know hair color but it wasn't collected at registration. If one module stores it in the UserProfile table and others first check there, the same data can be shared all from a central point across all modules. Of course, the first module should have checked to see if the property existed. The way this was designed, the same procedure first checks to see if it exists prior to creating it, if it does exist it will attempt to update it.
Now uses these tables: UserProfile (in combination with ProfilePropertyDefinition) ** Note: The Profile table has been available since DNN 2.1.2 I believe (at least 3.x). This is for storage of Personalization items of logged in users. Please do not confuse this table with the current or previous Profile provider. (It currently does not support Anonymous Personalization)
It should be noted that if you are a module developer you should try to make sure you are accessing items from any of these tables via the abstract provider API. This way, if a user has their own custom impmenentation of any of these, as long as their provider adheres to the API your module should work exactly the same if done correctly in both implementations.
Hope this offered some insight.