Before DotNetNuke began using Member Role, it had its own data schema and services to handle membership. Because of this, when it came time to implement Member Role, there were some challenges ahead.
Among the challenges was that DotNetNuke supports running many portals from a single DotNetNuke installation. Each portal has its own users and roles that are not shared with any other portals. A portal is identified by a unique key, the PortalID. Because the default Membership/Roles Provider implementation is a generic solution, it does not natively support the concept of having multiple portals, each with their own users and roles. The default Membership/Roles Provider implementation was designed in a way that only supports a single portal site in a DotNetNuke installation. The Membership/Roles Provider refers to the DotNetNuke installation as an "application," and without customization, that application can only support a single set of users and roles (a single portal instance).
Microsoft abstracted the Membership/Roles Provider to enable a common source for validating users/roles across multiple applications. The design just didn't anticipate the kind of virtual segmentation within an application that DotNetNuke provides through portals, each with its own users and roles.
To overcome this limitation, a wrapper was needed for the Membership/Roles Providers' SQL data providers. This customization enables DotNetNuke to support application virtualization. The end result is that the Membership/Roles Providers, as implemented in DotNetNuke, can support multiple applications (multiple portal instances in a single DotNetNuke installation). PortalID in DotNetNuke was mapped to the ApplicationName in the Membership/Roles Provider. When a call was made to the Membership/Roles Provider, the ApplicationName was switched on-the-fly to match the PortalID of the portal instance using a concatenation of the object qualifier and the PortalID . Listing 9-1 shows the way this is set.
Listing 9-1: Setting ApplicationName
Public Function GetApplicationName(ByVal PortalID As Integer) As String Dim appName As String 'Get the Data Provider Configuration Dim _providerConfiguration As ProviderConfiguration = ProviderConfiguration.GetProviderConfiguration("data") ' Read the configuration specific information for the current Provider Dim objProvider As Provider = CType(_providerConfiguration.Providers (_providerConfiguration.DefaultProvider), Provider) 'Get the Object Qualifier frm the Provider Configuration Dim _objectQualifier As String = objProvider.Attributes("objectQualifier") If _objectQualifier <> "" And _objectQualifier.EndsWith("_") = False Then _objectQualifier += "_" End If appName = _objectQualifier + Convert.ToString(PortalID) Return appName End Function
To gain the full benefit from the Membership/Roles Provider, it's important to recognize that User and Role information can be externalized from the DotNetNuke and kept in a data store independent of the main data store. For instance, DotNetNuke may use Microsoft SQL Server as its database to store content and system settings, but the Membership/Roles Provider may use Windows authentication, LDAP, or another mechanism to handle authentication and authorization. Because security can be externalized using the Provider Model, it was essential to ensure that the implementation of the Membership/Roles Provider didn't customize any code or database tables used by the provider. The data tables used by the provider had to be independent of the other core DotNetNuke tables. Referential integrity could not be enforced between DotNetNuke data and the Membership/Roles Provider data, nor could cascade deletes or other data-level synchronization methods be used. In a nutshell, all of the magic had to happen in the business logic layer.
One of the challenges faced in implementing the Membership/Roles Provider was dealing with the fields supported by DotNetNuke but not by the provider. Ideally, a solution would have been achieved by completely replacing the DotNetNuke authentication/authorization-related tables with the tables used by the Membership/Roles Provider. This could not be accomplished because the authentication/authorization tables in DotNetNuke were already tied to so many existing and necessary features of the application. For instance, the DotNetNuke Users table has a column named UserID, which holds a unique identifier for each user. The UserID is used in nearly all core and third-party modules as well as the core itself. The most significant problem with UserID was that it didn't exist in the Membership/Roles Provider. Instead, the Membership/Roles Provider uses the username as the unique key for a user within an application. The challenge was that DotNetNuke needed a way to maintain the UserID to preserve the DotNetNuke functionality that depended on it. This is just one example of an attribute that cannot be handled by the default Membership/Roles Provider provided by Microsoft.
Ultimately, it was decided that DotNetNuke would need to maintain satellite tables to support the DotNetNuke attributes that could not be managed by the Membership/Roles Provider. The goal was to maintain enough information in the DotNetNuke tables so that functionality was not lost, and offload whatever data we can to the Membership/Roles Provider tables. The end result is a data model that mirrors the Membership/Roles Provider tables, as shown in Figure 9-1.
Note that none of the tables on top have database relationships to any of the tables on the bottom. The lines connecting them simply show their relationship in theory, not an actual relationship in the database.
Because the data for portals, profiles, users, and roles is stored in multiple unrelated tables, the business layer is responsible for aggregating the data. For instance, you cannot get a complete representation of a user without collecting data from both the aspnet_Users table (from the Membership/Roles Provider) and the Users table (native DotNetNuke table).
Understanding the limitations DotNetNuke had to conquer and how all three of the providers were affected is important to comprehending why the current solution works the way it does. After the original implementation of Member Role in the first official DotNetNuke 3.x release, the DotNetNuke Core Team found its implementation of Member Role to be less extensible than it could be. To overcome the restrictions that DotNetNuke imposed on itself, the Membership, Roles, and Profile providers have been abstracted out even further. In doing this, a new set of three concrete AspNet Providers was created. They are the revised versions of the original Member Role implementation and can be found in $AppRoot\Library\Providers\MembershipProviders\CoreProvider\DataProviders\AspNetMembershipProvider.
In all abstract/concrete provider implementations, the concrete public methods override the methods exposed by the abstract provider. In the concrete providers, a set of private methods and properties are used to extend and customize the concrete provider along with custom logic in the business layer. In DotNetNuke, additional logic is responsible for combining data from the data store in this layer.
Because Member Role is the current default provider set, the schema shown in Figure 9-1 is still relevant today. When creating this new set of concrete providers, extreme caution was taken to make sure the new implementation would not alter the previous one, therefore minimizing upgrade implications.
As with the previous Member Role implementation, the new AspNet set of providers, which are still technically a Member Role implementation, follows the Provider Model design pattern discussed in Chapter 7. Keep in mind that it is possible to create a custom concrete profile provider of your own and still use the AspNet membership and AspNet roles concrete providers. This is important because it can greatly reduce the amount of effort for your next DotNetNuke project if only one of these concrete providers does not meet your requirements.
A word of caution: Because users and roles are so heavily tied together, if you are creating a custom concrete provider of your own for either of these, you probably need to create a custom concrete provider for the other.