Migrating to ASP.NET 2 - Part 4 - Implementing Custom Providers for Membership, Roles and Profiles

Update!! 05/03/2006
A while ago, Microsoft released the full source of the Built-in ASP.NET 2.0 Providers. Downloading the source is highly recommended if you want to create your own (or customize existing) providers. You can find more detail here: http://weblogs.asp.net/scottgu/archive/2006/04/13/442772.aspx.

This is the fourth part of a (long running) article series about migrating a .NET 1.x Web site to ASP.NET 2. This installment focuses on custom providers for the Membership and Role management features found in the .NET Framework. While out of the box these features make it very easy to implement security in a new site you build, you can't use them directly in existing web sites that already implemented a custom security mechanism.

Read Part 1 - Introduction
Read Part 2 - Setup and Master Pages
Read Part 3 - Business Logic, Content Pages and User Controls
Read Part 4 - Implementing Custom Providers for Membership, Role Management and Profiles
Read Part 5 - It's the little things...

Upgrading my Custom Security Solution to ASP.NET 2.0

One of the most interesting design decisions I had to make in upgrading my web site to the new ASP.NET 2.0 platform was the way I wanted to handle security. I am sure you're aware of the many new features of ASP.NET 2.0, with one of my favorites being the whole Provider Model with providers like Membership and Role management built on top of it. For a new web site, all you need to do is enable these providers, optionally prepare a database so the providers can use it, and you're done. Instant security!!

My current web site (and many other ASP.NET 1.x web sites) already had its own security mechanism. Loosely modeled after early Whidbey builds, my solution had some resemblance with the way ASP.NET 2.0 handles security. That is, I had a SQL Server database with a Member table, a Role table, an Application table and a table to link members to roles. So far not much new. However, the biggest challenge I had to face is the way my members are stored in the database. I have a couple of hundred members in my database that can access protected parts of my site. For example, friends and family can log in to access my photo albums, while others can log in to manage content and members. These members have at least a user name and a password, and it's the password that made things difficult. As a good security practice, my passwords are not stored as clear text, nor are they encrypted. The database only stores a hash of the password, and since hashing is an irreversible process, there is no way to find out what the old password was.

I had locked myself in in my own solution. Since I couldn't convert the hashes of the passwords anymore, I also couldn't migrate my members from my table structure to the new structure that ASP.NET 2.0 expects. So, I had to come up with a solution. Probably the easiest solution was to write a short news article on my homepage, explaining my users that their account had been deleted, and that they would need to resubscribe to access the site. But obviously, that isn't a very good solution. I wanted to find a way to keep my own users, while still making use of the new and very powerful features of ASP.NET 2.0 security. Custom Providers to the rescue!!

Introduction to the Provider Model

On the Microsoft MSDN site you'll find a good definition of a Provider:

A provider is a software module that provides a uniform interface between a service and a data source. Providers abstract physical storage media, in much the same way that device drivers abstract physical hardware devices. Because virtually all ASP.NET 2.0 state-management services are provider-based, storing session state or membership state in an Oracle database rather than a Microsoft SQL Server database is as simple as plugging in Oracle session state and membership providers. Code outside the provider layer needn't be modified, and a simple configuration change, accomplished declaratively through Web.config, connects the relevant services to the Oracle providers.

 

What's important in this quote is the fact that a provider provides a "uniform interface" and that it abstracts physical storage. In simple terms, this means that you can program against a known interface, without knowing the actual implementation.

For example, in ASP.NET 2.0, a call to Membership.FindUsersByEmail is actually a call to the configured provider. This usually means a call to the SqlMembershipProvider provider, but it is also possible to use a different provider. For that to work, all you need to do is configure the relevant nodes in the web.config file and point it to a different (custom) provider. Once the settings in the web.config are applied, calls like the above are now forwarded to the provider you configured. For example, the following web.config code tells .NET to use a provider for the Membership feature called CustomSqlServerMembershipProvider that is present in an assembly called Spaanjaars.Imar.CustomSqlServerProvider:

<membership defaultProvider="CustomSqlServerMembershipProvider">
  <providers>
  <clear/>
  <add
    name="CustomSqlServerMembershipProvider"
    type="Spaanjaars.Imar.Providers.CustomSqlServerMembershipProvider, 
             Spaanjaars.Imar.CustomSqlServerProvider"
    connectionStringName="DefaultConnectionString"
    enablePasswordRetrieval="false"
    enablePasswordReset="true"
    requiresUniqueEmail="false"
    requiresQuestionAndAnswer="false"
    minRequiredPasswordLength="1"
    minRequiredNonalphanumericCharacters="0"
    applicationName="SampleSite"
    hashAlgorithmType="SHA1"
    passwordFormat="Hashed"
  />
  </providers>
</membership>

As you can see, the class CustomSqlServerMembershipProvider is a custom class that I wrote that handles all the Membership tasks. Similar to the configuration for the MembershipProvider, you can configure a custom Role, Profile or Personalization provider.

But how can this class handle these tasks, and how is the CustomSqlServerMembershipProvider class able to service the requests made by the ASP.NET 2.0 Framework?. This is where the terms "uniform interface" come in to play. I'll take the Membership feature as an example in this article, but the same principle applies to the other providers. In ASP.NET 2.0 there is an abstract base class called MembershipProvider that defines all the methods of the Membership framework, like CreateUser, DeleteUser, ValidateUser and so on. Because the class is marked as abstract, it cannot be used directly, but it must be inherited from. Classes that inherit from MembershipProvider must implement all of its required methods. So, in the example above, the custom class CustomSqlServerMembershipProvider plays by the rules and has an implementation for all of the required methods.

Since a lot has been written about the Provider model, I won't dig into the inner workings of the provider model any deeper. Instead, this article focuses on the way I wrote my custom providers, and the issues I ran into. For a thorough introduction in the provider model and the different providers that ship with ASP,NET 2.0, I strongly suggest the article ASP.NET 2.0 Provider Model: Introduction to the Provider Model and all of the articles that are linked from that page. Additionally, you should check out the Provider Toolkit homepage, also at the MSDN web site. This section of the MSDN site provides invaluable information about the Provider model in general and ASP.NET 2.0 specific provider implementations.

Writing a Custom Provider for Imar.Spaanjaars.Com

As I mentioned before, the database design for my 1.x version of my web site is pretty similar to the model that ASP.NET 2.0 expects. So, it shouldn't be hard to make the two work together. However, before I even tried, I had a few decisions to make, the biggest being the way I was going to implement my provider. Basically, I had at least three options:

  1. Inherit from one of the available providers, like SqlMembershipProvider or SqlRoleProvider and only override methods whose behavior I wanted to change.
  2. Start from scratch, by inheriting from the base classes like MembershipProvider or RoleProvider.
  3. Be smart and reuse some of the work that Microsoft has already done by implementing a provider that works with an Access database.

Option 1 wasn't really an option. Although the differences between my database model and that of the Sql*Providers weren't that large, there was a fair chance I had to override almost all of the methods anyway. In such a scenario, it's better to choose a class a bit higher up and in the hierarchy and inherit from that.

Option 2 meant a lot of work, as you basically have to write everything from scratch. While this gives you a lot of flexibility, it also means an awful lot of work.

In the end - who would have guessed - option 3 was the best choice. On the Provider Toolkit homepage I linked to earlier, you can download an almost full implementation of the 4 important providers (Membership, Roles, Profiles and Personalization) as a Starter Kit in C#, targeting a Microsoft Access database. (You'll find the link to the C# project under Downloads on the Toolkit page). My idea was to use the full source for the Access implementation and then modify it to work with SQL Server.

When you download and install the start kit, you get a new project template under Visual C# | Starter Kits in the New Project dialog of Visual Web Developer (provided you're running the Standard, Professional or Team System edition). When you create a new project based on that template, you end up with a solution as depicted in the following figure:

The Visual Studio 2005 Solution window for the new provider project
Figure 1

At this stage, you have 4 fully working providers that talk with a Microsoft Access database. All you need to do is compile the application, and then configure your web site to use the new provider. The web.config file that is supplied with the download shows you the exact configuration settings you need.

Since I needed to change the app to have it work with SQL Server, simply compiling wasn't an option for me. So, I had to change the code. In fact, I had to change *a lot* of code. The seven *.cs files you see in the Solution Explorer contain over 5,700 lines of code! Fortunately, when you base your own provider on this one, you don't have to change all of it.

Here's what I did to change the Access project into a collection of Providers that works against my own custom database scheme.

1. Housekeeping

I started with some housekeeping. I am not going to use the PersonalizationProvider, so I dropped the entire class from the project. That also meant I could drop some of the methods that works with Paths from the ConnectionHelper class, as Paths are used exclusively by the PersonalizationProvider.

With the unneeded code removed, I changed the namespace of the project, renamed the assembly and moved some code around (for example, the using statements were placed inside the namespace, while I prefer to keep them outside the namespace definition).

2. Changing Code For the Data Provider

As you may have guessed, the Access implementation uses the OleDb data provider. To make it work with SQL Server, I replaced all occurrences of OleDb with Sql. So, I changed an OleDbConnection to a SqlConnection, an OleDbParameter with a SqlParameter (there are a few helper methods that return these kind of objects) and so on. I also had to change some of the DbTypes used, like OleDbType.BSTR.

3. Changing Code Implementation

The next thing I had to change was the Initialize method. Since my provider is not going to support all of the features (for example, I won't allow clear text or encrypted passwords), I changed the behavior of Initialize so it throws exceptions when an unsupported value is set.
I also removed the method GetFileNameFromConnectionName and replaced it with GetConnectionFromConnectionName. The Access implementation allows you to set a file path as a connection string. Some code in the AccessConnectionHelper.cs class then builds up a valid connection string based on the file path. In my case, I wanted to be able to set a connectionStringName attribute on the provider configuration, and have it point to a valid connection string in the <connectionStrings> section.

4. Changing the Way the Database is Accessed

At this stage, my application compiled successfully. However, it didn't run of course because the internal implementation of T-SQL statements differed from the way my database is designed. Also, the Access implementation uses embedded T-SQL statements, so you'll find a lot of SELECT, INSERT, UPDATE and DELETE statements all over the code. I prefer to use stored procedures, so I modified the code accordingly. To see what I did, take a look at the Access implementation of the GetUserId method:

internal static int GetUserID(OleDbConnection connection, 
     int applicationID, string userName, bool createIfNeeded, 
     bool newUserIsAnonymous, DateTime lastActivityDate)
{
  if (applicationID == 0 || userName == null || userName.Length < 1)
    return 0;

  if (connection == null)
    return 0; // Wrong params!
OleDbCommand lookupCommand = new OleDbCommand( @"SELECT UserId FROM aspnet_Users WHERE ApplicationId = @AppId AND UserName = @UserName", connection); lookupCommand.Parameters.Add(new OleDbParameter("@AppId", applicationID)); lookupCommand.Parameters.Add(new OleDbParameter("@UserName", userName)); object lookupResult = lookupCommand.ExecuteScalar(); if ((lookupResult != null) && (lookupResult is int)) { return (int)lookupResult; } if (createIfNeeded) { OleDbCommand createCommand = new OleDbCommand(@"INSERT INTO aspnet_Users " + @"(ApplicationId, UserName, IsAnonymous, LastActivityDate) " + @"VALUES (@AppID, @UserName, @IsAnonymous, @LastActivityDate)", connection); createCommand.Parameters.Add(new OleDbParameter("@AppID", applicationID)); createCommand.Parameters.Add(new OleDbParameter("@UserName", userName)); createCommand.Parameters.Add(new OleDbParameter("@IsAnonymous", newUserIsAnonymous)); createCommand.Parameters.Add(new OleDbParameter("@LastActivityDate", new DateTime(lastActivityDate.Year, lastActivityDate.Month, lastActivityDate.Day, lastActivityDate.Hour, lastActivityDate.Minute, lastActivityDate.Second))); if (createCommand.ExecuteNonQuery() != 0) { lookupResult = lookupCommand.ExecuteScalar(); if ((lookupResult != null) && (lookupResult is int)) { return (int)lookupResult; } } } return 0; }

And here's how my version ended up, using stored procedures instead of in-line T-SQL:

internal static Guid GetUserId(SqlConnection connection, 
       Guid applicationId, string userName, bool createIfNeeded, bool newUserIsAnonymous, 
       DateTime lastActivityDate, SqlTransaction myTransaction)
{
  if (applicationId == Guid.Empty || userName == null || userName.Length < 1) 
  {
    return Guid.Empty;
  }
  if (connection == null)
  {
    return Guid.Empty;
  }

  SqlCommand lookupCommand = 
      new SqlCommand(@"sprocMemberSelectIdByName", connection);
  lookupCommand.CommandType = CommandType.StoredProcedure;

  if (myTransaction != null)
  {
    lookupCommand.Transaction = myTransaction;
  }

  lookupCommand.Parameters.Add(new SqlParameter("@appId", applicationId));
  lookupCommand.Parameters.Add(new SqlParameter("@userName", userName));

  object lookupResult = lookupCommand.ExecuteScalar();
  if ((lookupResult != null) && (lookupResult is Guid))
  {
    return new Guid(lookupResult.ToString());
  }

  if (createIfNeeded)
  {
    SqlCommand createCommand = 
              new SqlCommand(@"sprocMemberInsertSingleItem", connection);
    createCommand.CommandType = CommandType.StoredProcedure;

    if (myTransaction != null)
    {
      createCommand.Transaction = myTransaction;
    }
            
    createCommand.Parameters.Add(new SqlParameter("@appID", applicationId));
    createCommand.Parameters.Add(new SqlParameter("@userName", userName));
    createCommand.Parameters.Add(new SqlParameter("@isAnonymous", newUserIsAnonymous));
    createCommand.Parameters.Add(new SqlParameter("@lastActivityDate", 
           new DateTime(lastActivityDate.Year, lastActivityDate.Month, 
           lastActivityDate.Day, lastActivityDate.Hour, 
           lastActivityDate.Minute, lastActivityDate.Second)));

    if (createCommand.ExecuteNonQuery() != 0)
    {
      lookupResult = lookupCommand.ExecuteScalar();
      if ((lookupResult != null) && (lookupResult is Guid))
      {
        return new Guid(lookupResult.ToString());
      }
    }
  }

  return Guid.Empty;
}	

There are a lot of similarities between the original code, but also a few interesting differences. First of all, you won't see in-line SQL in my code. This makes the code a bit easier to maintain, and also easier to reuse specific SQL statements. Secondly, I changed some of the behavior of the application. The Access implementation uses Ints to identify users, roles and applications. The SQL Server version that comes with ASP.NET 2.0 uses Guids as primary keys. My own database uses Guids as well, so it was a logical choice to develop the code in favor of the Guids. Since the ProviderUserKey of a MembershipUser is an object, it doesn't matter to calling code whether an Integer or a Guid is used (as long as the calling code knows *what* is used).

The final thing to notice is the Transaction object. Some of the methods in the Access implementation use transactions to ensure database integrity. Access has pretty limited support for transactions. So instead of creating ADO.NET Transaction objects, the following construct was used to start a transaction:

  OleDbCommand command = new OleDbCommand("BEGIN TRANSACTION", connection);
  command.ExecuteNonQuery();
  fBeginTransCalled = true;

Later in the code, either COMMIT TRANSACTION or ROLLBACK TRANSACTION was called, depending on the success or failure of the operation.

I had to update number of helper methods to support transactions as well. For example, the GetApplicationId and GetUserId now have an overload that accepts a Transaction object. I had to do this, because these methods are used in transaction aware methods. As soon as you have an open Connection with a Command object that has a Transaction object attached, all other command actions and objects need a Transaction as well (at least, according to my findings; there may be ways to avoid this problem). To fix this, I could have created a separate connection, for read-only access. However, extending some of the helper methods so they support transactions felt a lot more natural.

During the rewrite, I moved ALL the T-SQL code to stored procedures. Along the way, I recreated the database tables from Access in my own database, and modified them where appropriate (that is, I changed Ints to Guids and so on). I decided that at this stage it would be much easier to implement the table structure of the Access database. Once all the stored procedures were created, it would be much easier to modify the procedures again so they were using my own database scheme instead. That allowed me to focus on the rewrite, without getting distracted with database design too much.

5. Tweaking the Database Design

Once all the code had been upgraded to stored procedures, I could focus on a redesign of the database. One of the things I changed is the way users are stored. The .NET providers for Membership have a separate User and Membership table. My current database stores this data in a single table. Modifying the stored procedures wasn't very difficult; most columns were easily transferred, renamed or dropped completely. I also removed some of the procedures I had written in the previous phase, and combined their code with a few others to create multi purpose procedures.

At the end, I ended up with around 30 procedures for the Membership and Role providers, each of them accessing my own tables now and returning relevant information about users, roles and applications.

6. Fixing the Password Problem

It turned out that the main problem that brought me to writing a custom provider was a lot easier to fix than anticipated. As you may recall, my passwords were stored as hashes only. ASP.NET 2 is capable of storing clear text, encrypted and hashed passwords. It does this by storing the PasswordFormat in a separate column. I used this column to introduce another type of PasswordFormat: Legacy. I updated the entire table and set that column for all users to -1. I then used the following code in the CheckPassword methods to determine whether an "old" user is accessing the site, or a new one, and hash the password accordingly:

private bool CheckPassword(SqlConnection connection, Guid userId, 
     string password, out bool userIsApproved, SqlTransaction myTransaction)
{
  string salt;
  string pass2;
  int passwordFormat, status;
  string pass = GetPasswordWithFormat(connection, userId, 
          null, false, out passwordFormat, out status, 
          out salt, out userIsApproved, myTransaction);
  if (passwordFormat != -1)
  {
    // New user account. Using default hashing behavior from the Access provider code
    pass2 = EncodePassword(password, passwordFormat, salt);
  }
  else
  {
    // backwards compatibility hack. Get the password from the database and compare it
    // with my custom hashed version the way I did in my ASP.NET 1.x application 
    pass2 = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1");
  }
  return (pass == pass2);
}

This turned out to work remarkably well. When existing users log in, the call to GetPasswordWithFormat will give status a value of -1, which in turn ensures that my "legacy code" is run by calling HashPasswordForStoringInConfigFile on the FormsAuthentication class and passing it a proper hashing algorithm (SHA1 in this case). Users that sign up after the upgrade will automatically use the new code in the Provider implementation.

The good thing of this solution is that the problem will go away automatically over time. When users change their password, they'll make use of the new feature, get a proper PasswordFormat and then use the new code from that point onward.

With the change in the password module, I had fixed about every issue I had in the Membership provider. Changing the Roles provider was even less of a hassle; most code worked out of the box, as the ASP.NET 2 database schema was pretty compatible with my own. At the time of writing, I haven't completed the Profiles provider yet, but I am not anticipating much problems.

Debugging Tips

Once you have a basic framework of your providers ready, it's time to test them. You could write special methods for testing the layer (which is always a good idea; using Nunit for example). However, you may also want to do some quick testing of some methods. The easiest way to test your provider code is by using the Web Site Administration Tool that comes with Visual Web Developer (choose Website | ASP.NET Configuration). But if you do use that tool, you'll find that it doesn't break into your code, even if you set some break points. That's because the process isn't started with debugging enabled. But this is easy to fix. Right after you start the tool, choose Debug | Attach to Process in Visual Web Developer and then locate the Web server's process in the Available Processes list. The process is called WebDev.WebServer.Exe. If you have multiple processes with the same name, make sure you choose the one with the right port number as you can see from the following screen shot.

List of Running Processes in Visual Web Developer
Figure 2

Now, when you perform any actions in the WSA tool that cause a call on your provider (e.g. get a list of users, add users to roles etc), your code will break and you can walk through your provider's code line by line.

Notice that the WSA tool does not cover all of your methods; so even if your code runs fine while testing the tool, you still need to do additional testing.

What's Next

With my custom providers done, I am making good progress with my upgrading process to ASP.NET 2.0. There are, however, a few things that still need to be done. First of all, I need to update my security related pages that should now make use of the new Membership and Role providers. That will be easy now; it's mostly a matter of removing obsolete code and removing calls like MyCustomBusinessObject.GetUsers() by calls to Membership.GetAllUsers(). Building a security tool can hardly be any easier than this.

Note

Please note that I am not releasing the code for my providers. First of all, they aren't finished completely. But a more logical reason is that since they are custom providers, they are custom to my database and my database only. There is no point in trying to reuse code that has been written specifically against my database. To learn *how* the code works, the Access implementation that you can download from the Microsoft site should be more than enough. If you do have compelling reasons to see my code, drop me a line and explain why you want to see it....

References

I listed a few of the sources I used for my custom articles throughout this article, but for completeness sake I'll list them here again.


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Thursday, February 16, 2006 9:00:39 PM Wajeeh said:
Imar,

Great detailed article, just the information I was looking for. It took me a whole day to find such valueable information that you have here. I would love to see follow ups on this and other Provider stuff that you do.
On Thursday, February 16, 2006 9:13:13 PM Imar Spaanjaars said:
Hi Wajeeh,

Glad you like it. That was exactly the reason I wrote it.

I am still working on finalizing my providers. Are there any specific topics you want to see another article about??

I should work on the SEO for my site if it takes you a day to get here.... ;-)

Imar
On Thursday, February 16, 2006 10:20:33 PM Wajeeh said:
Hi Imar,

I was wondering if you would do an article on making the Membership Provider work with Enterprise Library 2.0. The items I would like to see addressed are; how should the Provider and the Web Application reference the Application Blocks; where should the connection string be stored, common web.config or web.config in each of the Providers?

Thanks
Wajeeh
On Saturday, February 18, 2006 1:27:22 AM Imar Spaanjaars said:
Hi Wajeeh,

I haven't done much with Ent Lib 2 yet, so I cannot recommend / write much on that subject. I had some semi-bad experiences with Ent Lib 1, so I am not too eager yet to dive into 2. However, I see a lot of potential in the new release, so I'll surely investigate it.

But I don't think you necessarily need the Ent Lib to write good providers. Although there is quite a lot of data access in the provider code, it's pretty straight forward and can easily be done with straight ADO.NET code (that's what I have right now).

In terms of connection string management: each provider has a connectionStringName property that refers to a connection string in the connectionStrings node of the web.config file. Ent Lib can use that section as well, so it's easy to share connection strings between providers and other parts of your application. IMO, you shouldn't store the connection string as part of the provider; the provider should have no knowledge of the actual data store, other than a known scheme of tables / stored procedures etc.

Not as good as an article, but I hope it helps.

Contact me through the Contact page if you want to discuss this in more detail.

Cheers,

Imar
On Tuesday, February 21, 2006 3:26:45 AM Trevor Bandura said:
The CreatUserWizard runs off this provider right?

Well what I want to do is to be able to add optional form fields to the creat user wizard such as First Name, Last Name etc.

How can I modify the membership provider to automaticly add those fields to the creat user wizard when it is loaded?

I have been trying to figure this out for days now.
On Tuesday, February 21, 2006 7:18:23 AM Imar Spaanjaars said:
Hi Trevor,

Yeah, the CreateUserWizard eventually creates the user by calling Membership.CreateUser.

You need to provide a little more information. Is all you want to do adding a few fields to the CreateUserWizard control whose text you retrieve separately, or do you want to extend MembershipUser and adapt the providers for this scenario?

Imar
On Tuesday, February 21, 2006 6:19:35 PM Trevor Bandura said:
I want to extend the create user wizard to allow these extra fields that I need to get from new members.
On Tuesday, February 21, 2006 6:25:45 PM Imar Spaanjaars said:
Hi Trevor,

In that case, check out this blog post by Scott Guthrie: http://weblogs.asp.net/scottgu/archive/2005/10/18/427754.aspx

Basically, he has customized the code for the CreateUserWizard steps and added some additional controls. In the code behind, the values are retrieved from these controls and stored in the user's profile.

I think this might just do the job for you....

Imar
On Tuesday, February 21, 2006 8:52:25 PM Trevor Bandura said:
So far, this seems to be what I am looking for. Thank you.
On Tuesday, February 21, 2006 9:03:09 PM Imar Spaanjaars said:
You're welcome.

Cheers,

Imar
On Tuesday, February 21, 2006 10:52:44 PM kevin said:
is there a vb.net version of the startkit on the web somewhere?
On Tuesday, February 21, 2006 10:57:54 PM Imar Spaanjaars said:
Hi kevin,

I haven't seen one on the MSDN site, so the answer may be No.

I wouldn't be surprised ifyou'll see provider implementation start to appear on sites like GotDotNet or SourceForge in the near future, but so far I haven't seen anything as complete as the C# Access provider....

HtH,

Imar
On Monday, March 13, 2006 11:16:20 PM palakoll@gmail.com said:
http://imar.spaanjaars.com/QuickDocId.aspx?QUICKDOC=380
Hi,
I implemented the membership,role and web parts providers.But Iam still not clear about,How the provider actually loads? Are the providers loaded
at run time (by looking only into the web.config tags) or only when I use any of the controls(login,role or webparts).
When I start tracing the flow, I see that only the role providers are initialized at run time.But the others are not.
Is there any way ,I can initialize the other providers before the application starts? I need a way to initialize the membership provider without having to use any of the corresponding controls on the page.
Please let me ,if this is possible.

Thank you
Prashanth
On Tuesday, March 14, 2006 6:02:16 PM Imar Spaanjaars said:
I am not 100% sure, but I think they use a lazy initialization scheme, where they only start up when needed.

Why do you need to initialize them before they are used??

Imar
On Tuesday, March 28, 2006 5:03:39 AM Georgina said:
Hi Imar,

great article. I'm looking at implementing a custom provider but haven't decided yet. Currently I use ints not guids for identifiers (I know! I'm hanging my head.. :-)) and the SqlMembershipProvider seems to allow non-unique usernames ... and we are not supposed to use the ProviderUserKey (for reasons of abstraction???) ..but I need to use the userid in my app and I want to move on and use all the goodies in ASP.NET 2.0!

Thanks for going to the effort of writing it!
On Tuesday, March 28, 2006 7:20:46 PM Imar Spaanjaars said:
Hi Georgina,

Do you need the int ID to connect the users table to other data in your application? If that's the case, you could extend either the Users table or the Membership table and add an Identity column. That way, for each user, a new Id is inserted.

However, to make this new ID available in .NET code you need to extend the MembershipUser class, or indeed implement your own provider. Fortunately, it's not as difficult as it looks. I spent most of my time figuring out how it worked as opposed to writing code. However, with the Access implementation that you can download, most of the "figuring out" has already been done.

Since the ProviderUserKey is of type object, you can have your custom provider return an int instead of a Guid, allowing you to integrate the feature with existing code.

Hope this helps,

Imar
On Thursday, April 06, 2006 5:43:48 AM georgina said:
Thanks Imar, for your very helpful (logical) suggestions. I like the idea of extending the Membership table with an Identity column. That way I can use current code and move over to guids later (maybe). I've come back to your article as I've decided to go the custom provider route [smile], especially when you've already made it so easy for me!
On Thursday, April 06, 2006 8:07:05 PM Imar Spaanjaars said:
Hi Georgina,

Sorry for the errors you got.... You're not allowed to submit illegal characters to this page, so that's why you got some errors.... ;-)

Anyway, good luck with your provider. Post an update here with your success story if you like....

Imar
On Thursday, April 27, 2006 12:39:14 PM Rob Smith said:
Many thanks for posting the link to Scott Guthrie blog article: it had what I was looking for. Great article here too, congratulations
On Tuesday, May 02, 2006 8:21:41 PM Shay Stowe said:
Imar,

I am in a solution where I will be looking at extending the MembershipUser Class and adapting Custom Providers to implement them. This comes about from the fact that we do not use Unique User Names but instead Unique UserID's which I will need to implement in Custom Role Management. Are there any examples of extending this class and implementing the solution in a Custom Provider ?

Any help appreciated - Thanks
On Wednesday, May 03, 2006 6:30:19 PM Imar Spaanjaars said:
Hi Shay,

Did you look at the source for the SqlServer membership providers? A while ago, Microsoft released the full source for all SQL based providers.

You can find out more about it here: http://weblogs.asp.net/scottgu/archive/2006/04/13/442772.aspx

Studying this code will give you a lot of insight in how these providers work, which should help in creating your own solution.

To understand how to inherit from the MembershipUser class, take a look at this: http://fredrik.nsquared2.com/viewpost.aspx?PostID=322 and this: http://weblogs.asp.net/dneimke/archive/2005/02/06/367864.aspx

Hope this helps,

Imar
On Tuesday, November 07, 2006 7:13:04 AM Prasanna said:
I went to the site Provider Toolkit homepage, http://msdn2.microsoft.com/en-us/asp.net/aa336558.aspx to get the sample code. But when i click on, Sample Access Providers (http://lab.msdn.microsoft.com/vs2005/eula.aspx?id=96713a8e-b8d4-4d6e-bb8f-027e6c8e15d8) the page is showing the following error message.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly.

So if somebody is having the sample source code, please pass on to me.

Thanks in advance.

On Friday, December 08, 2006 12:37:25 PM Imar Spaanjaars said:
For anyone interested, the download link "Sample Access Providers" on the Toolkit homepage is working again....

Imar
On Friday, October 26, 2007 5:32:55 PM rizzy said:
Hi Imar,

Very informative article; I really liked it.  I'm in a similar situation and need some of your advice.

We have an already existing sql server 2005 database with about 10,000 users.  There is an app_user table that has login, pw, and some other columns about users.  I think we can apply the membership schema to the existing database and also maybe write a script or use the method you described to transfer the users over to the membership table.  The following are the issues that I see after that:

1.  Do we have to rely on our custom providers or can asp.net just automatically look after our user admin stuff?

2.  In our already existing table, we run a nightly process which evaluates, based on certain parameters, whether the user is still active or not.  If not, then a certain field is set to null, which won't allow them to log in again.  Now the question is, can we still have that functionality in the new membership table?  I mean, can we add new columns in there which include FK's into other tables, etc.?  What I'd really like to do is then make these new membership tables almost like the old app_user ones and make them totally work with the existing tables in the database, while at the same time allowing asp.net to take care of the user admin.security issues.

What I need from you is some advice on this, and maybe pointers to any sites/books which go into detail about this.  I've gone through 2 books, and many sites, and all they seem to talk about is how to implement membership, login, etc. etc.  Some of them also talk about how to take the existing user base and move it over to the membership tables.  However, nobody has yet addressed the issues of then making the new and the old stuff work together.

Thanks much,

Rizzy
On Saturday, October 27, 2007 8:54:04 AM Imar Spaanjaars said:
Hi rizzy,

1. It depends. If you can somehow get the data in the aspnet_* tables, then you can rely on the existing providers. If you need to maintain your own table structure, you have to write your own provider as I did, because your querying logic (for example, "What constitutes a valid user") will be different.

2. Yes, you can do that; with an existing or your own provider. You can modify existing procedures to take your additional field into account, or you can simply change the existing fields so the user can no longer login.

You can see if the book "Professional ASP.NET 2.0 Security, Membership, and Role Management" is something for you.

Cheers,

Imar
On Wednesday, October 31, 2007 4:36:28 PM Rizzy said:
Hello Imar,

Thanks for your advice.  

I decided to not even apply the membership schema to my database.  One of the main reasons I wanted to use the asp.net membership stuff was to use the login control and for it to take care of authentication ticket, etc.

So based on some articles I read, I've gone and created a custom provider. Right now I've overridden the relevant sqlmembershipprovider methods for creating and validating a user.  

Validating a user works fine right now.  For both the overridden methods I'm using my own stored procedures to update my existing app_user table.  The problem I've run into now is the create_user procedure.   I realized that the create user procedure has certain parameters for example passwordAnswer, which I don't even use because I have no field for that.  Rather I have other columns, for example Group_Code, which I need to insert into my table when creating a user.  So even though I can override the CreateUser method, and call my own stored procedures, I'm really stuck with the parameters!

So I'm pretty much stuck when it comes to creating a user using the create user wizard.  For all the work I did doing the custom provider stuff, I can only use the login.  It seems I'll just have to do my own custom page for creating users.  Do you have any advice for me?

Thanks

Rizzy
On Wednesday, October 31, 2007 8:17:05 PM Imar Spaanjaars said:
Hi Rizzy,

The Membership providers (and other providers as well) are all based on contracts. If you like the contract, you need to stick to it. If it doesn't do what you need, you shouldn't use it.

You can of course ignore some of the parameters , or not really implement methods you don't like by having them throw an NotImplementedException.

Cheers,

Imar
On Friday, September 25, 2009 7:59:16 AM meenu said:
Hello Imar,

This is my first post, really like your article.

I have created Custom SQLMembership Provider, for login control. & overridden all the methods
I have extra fields in usr_User Table like dept_ID,CostCentre etc in my User wizard.
But can't pass the extra field values in the Create UserMethod in MemnberProviderClass.

I have tried everything but none of them worked, I have created Custom MemberUser class as well.
But really I am stuck on this.

And 2nd thing is :
In Login Built control, If I want to get the values of UserName & paswordin login.aspx.cs  page after Login_Authentication:

can't able to get the values  from login control
I am using the below Command:
string username = LinMain.UserName.Tostring()
But its not working...

Could you pls help me..your help will be greatly appreciated.
Many Thanks in advance.
I am using VS 2005 & c#
waiting for your reply...
Kind Regards
Meenu




On Friday, September 25, 2009 8:02:28 AM Imar Spaanjaars said:
Hi meenu,

Rather than writing your own provider to store extra data, you're much better off storing it separately, in Profile for example or in a custom data store:

http://weblogs.asp.net/scottgu/archive/2005/10/18/427754.aspx

Cheers,

Imar
On Friday, September 25, 2009 8:22:05 AM meenu said:
Thanks very much for your prompt reply.

Do I have to use Profile, is there any  other option to save extra data.
Please let me know.

Thanks once again.

& Could u pls tell me how to get the value of built-in-LoginControl USername & password value..
I have read somewhere FindControl will but its not working..Could u tell me the code how to get the username & password value,,
could u help me pls...
Many Thanks..
Kind Regards
Meenu
On Friday, September 25, 2009 8:26:04 AM Imar Spaanjaars said:
Hi meenu,

Sure, you can use whatever you want. XML files, databases, whatever you have.

I don't know why the Login control doesn't give you the user name. Might be a security feature. For me to look into that, I need more info. Can you post that here: http://p2p.wrox.com/index.php?referrerid=385

Make sure you choose a relevant forum, and provide enough details and code.

Cheers,

Imar
On Friday, November 26, 2010 6:47:05 PM gabriel mareño said:
Hi. I have been looking for the 4 provider that you say above in this post but I don't found. I want to continue with the example. Thank you!!!
On Friday, November 26, 2010 8:17:17 PM Imar Spaanjaars said:
Hi gabriel,

Are you talking about the code of my provider? If so, check out the last part of the article where I say:

Please note that I am not releasing the code for my providers. First of all, they aren't finished completely. But a more logical reason is that since they are custom providers, they are custom to my database and my database only. There is no point in trying to reuse code that has been written specifically against my database. To learn *how* the code works, the Access implementation that you can download from the Microsoft site should be more than enough.

Additionally, you can get the official source for the SQL Providers by following the first link in this article.

Cheers,

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.