I think the Membership dream is really just a fantasy

Way back in early 2004, when I first got my hands on ASP.NET v2 and the Membership API, I though, wow, this is a seriously cool. What a dream come true to be able to plug this into any site and write your own provider. After all, isn't a single familiar and predictable interface to user data a good idea?

After much thought about the subject, I have to say that, no, it's not as great as I thought. Don't get me wrong, it certainly has its benefits for simple projects, but all of that flexibility ultimately constrains you. Oh the irony.

I started this year thinking, yep, this is the year I'm going to rewrite POP Forums, and dammit, I'm going to release it by the end of the year. The truth is that I have made some fabulous progress, and I'm starting to feel really good about the shape it's in. That said, the majority of my issues with the rewrite have had to do with user management, and trying to shoehorn it in to Membership.

I already ditched Profile, because I just didn't see any value in it anymore. It was an exercise that reminded me a lot of my first experiences with a DataGrid. Fabulous and flexible, but nearly worthless if you want to do any kind of clever UI or make something perform at a level you could respect. In the end it was almost always easier, and significantly faster, to roll your own solution with a good old fashioned Repeater, or your own templated, data-bound control.

The dream of Membership starts to become a nightmare as soon as you want to associate that data with other things. The system forces you to work in its little universe so that it can work the same with any provider. That creates a boundary from other data, and the simple SQL join becomes impossible because it would break compatibility with other providers.

So I think the time has come that I'm going to ditch the Membership component of the forum app. I have enough code coverage in unit tests that I think I can get away with the changes pretty quickly. I'm done trying to be clever and use ASP.NET to its fullest just for the sake of doing so. Maybe I've been drinking the 37signals Kool-Aid, but more simple is better.

14 Comments

  • "I'm done trying to be clever and use ASP.NET to its fullest just for the sake of doing so. Maybe I've been drinking the 37signals Kool-Aid, but more simple is better."

    We've found the same thing overall. If you're building something specific, make it specific, don't generalize every piece of code, etc.

    That said, I'm curious about the rest of your comments about membership. We had to change how profile worked to make it easier to query (i.e. our own profile table with all the fields as real fields instead of all clumped together), but we've found membership overall to be great. What specific problems are you having? I understand your problem with joins, but I'm curious why that can't be solved in SOA by retrieving user information seperately from your main query (that's what we do). Anyway, I'd love to hear what issues you're having.


  • Human: Imagine for a moment you're building a Facebook clone (or any other social type of site). Your "friends" exist as a number of User ID's in your database. If this was a product where you wanted to give others the ability to roll with their own Membership provider, how do you turn that list of ID's into a list of names in a provider that you don't know anything about? Membership has no way to take that data set and match it with its own data. Even if it did, it would be much slower than a simple join.

    Scott: The provider's implementation doesn't really matter. You're still stuck with those boundaries unless you want to make your provider dependent on the bigger picture schema of your application.

  • "it would be much slower than a simple join"

    Agreed, but SOA could still fix the problem (even if a little slower). Still though, implement caching and I don't really see a big problem.

    I'm curious about your takes on this, because we're about to do the same thing, where our membership is completely seperated from all the data (two different sites with a single membership) and we plan on using asp.net membership. I still think SOA is the answer for this type of thing though. Maybe we'll just have to agree to disagree. ;) I just don't see how not using membership and rolling your own will really be an different.

  • What I'd typically recommend for systems like a PopForums where you want to be able to easily integrate it within any application, would be to create your own Friends/FriendRelationships tables that you use to manage people relationships specific to PopForums, and have those relate to the application's authentication using a username string.

    This avoids you hard-coding in a specific user management implementation, and also means that you can easily use it for both Internet (where you'd do forms-auth) and Intranet (where you'd probably do Windows-auth) solutions. I'd give this advice regardless of whether you use the Membership provider API or not.

    If you wanted to integrate with the Membership API and perform richer database JOINs on the backend, one option to look at is to use the ProviderUserKey property that the Membership API exposes for each user. This typically exposes the PrimaryKey value that the underlying membership provider is using to store the user within a database.

    You could use this as the relational key if you wanted to setup PK/FK relationships between your tables and the user table, as well as to perform JOINs or searches on the backend. It also guarentees uniqueness (whereas in theory a user management system allowed someone to create, delete and then re-use a username - the UserKey would always be new).

    For example:

    // Create User
    MembershipUser member = Membership.CreateUser("scott", "password");

    // Retrieve PK from Membership Provider table
    object userKey = member.ProviderUserKey;

    // Lookup the key for another user via the membership API
    object userKey = Membership.GetUser("anotheruser").ProviderUserKey;

    // Lookup a Member by their ProviderKey
    MembershipUser member = Membership.GetUser(userKey);

    This enables you to maintain a provider based user abstraction, while still getting lower in the schema to perform more advanced scenarios.

    Hope this helps,

    Scott

  • I'm not using the membership API's for my upcoming forum product either.

    Besides the points already being mentioned, I'd want people to easily integrate the forum software with their existing authentication mechanism, even if that doesn't use the ASP.NET 2.0 membership functionality.

  • Imho you shouldn't use the built-in membership provider as a golden hammer. If the features you need can't be find in the built-in membership provider just consider implementing the Service Locater pattern yourself for your needs.

  • Amen to the original post.


    Most of the problems come when you want to do anything other than what is shipped in the box. For example, if you want to write your own membership provider for a different datasource, you cant just simply plugin a different IDbConnection, IDbCommand, and specifiy a different CommandText for each method. Instead, you have to override or rewrite the entire method.

    This is fine for some of the simpler methods like GetUser() et al. But becomes problematic around login, CreateUser, and ChangePassword because you are now 100% responsible for doing the right thing(s) for security such as password hashing, salting, and encryption. Also, you take on responsibility for cookie management and any caching. Ultimately, this raises the bar significantly for the average developer wanting to simply connect to a legacy data source, xml file, or a simple Oracle Db.

    And in the cases where the developer DOES decide to take-on this endeavor, 90% of the examples I have seen are complete failures around proper security.

    And, to make things worse, much of the original SqlMembershipProvider functionality around this area is only available via internal sealed classes that are only available if you want to risk reflection or Reflector.

    This is just the tip of the iceberg...dont get me started on the RoleProvider and its inherent complexity to extend - or better yet, lets talk about what happens when you DO extend any of this stuff, and you can nolonger use all the nifty Login, CreateUser, SiteMaps, and other controls that depend on Role & Membership.

    When ASP.NET 2.0 was shiny and new, I thought it was awesome, but after several projects where I have had to apply numerous hacks for even minor extensions, now I too share the delight in knowing that its all just a dream implemented by MS interns...

  • I have to say the times that I've felt like this, I've figured out it really wasn't .NET's fault- it was mine. I've been writing a custom membership and role provider against a client's horribly designed and overly complex security database. By constraining me to overrideing a narrow set of methods, I've been able to focus on the important things. (And yes, I can still user Login, Sitemaps, authorization, etc.) If this system can be used, then pretty much anything can be. The best part is this can be used to wean them off of their crazy system at some point in the future.

    This is the converse of what CodeSniper's saying. He sees this as bad- the developer could fail at properly securing the custom provider. Well, if that's the case then the developer will certainly fail when trying to roll their own.

    IMO it's the best of both worlds: out-of-the-box system for simple projects, full customization for others. Not trying to be smug, but I'd really exhaust all the resources about ASPNET Providers before chunking them.

  • Human: SOA doesn't work well in anything that requires significant result sets that are derived from other sets of data, like the example of "match these hundred ID's with a table of users and return those complete records." In a non-real-time situation that's not bad, but in a Web app, the performance just isn't great no matter how you implement it. (It also happens to be above the heads of some of my audience, which is another consideration.)

    Scott: I did think about what you suggest, but I'm not comfortable with data that is totally unaware of its parent data (i.e., user names).

    Gabriel: Again, which provider you use, whether your own or the built-in, doesn't matter.

    Lance: Right on brother.

    I'm not saying that Membership is inherently bad, it's just not for me in the sense that it's too much work to implement when you start doing "clever" things with your app.

  • Scott,

    Looks like maybe a whitepaper or a blog post is needed about how one would go about finding the best solution of using the built-in membership database along with custom membership tables using the ProviderUserKey as the linker, like you suggested. Maybe this would clear up some of the issues.

  • You pointed that out very nicely - there is a big problem with the holw SOA / Provider approach.

    It still makes sense form the OTHER side. We use a CMS (we actually maintain and market one), and for us the provider model means we can expose our user database to ASP in a standard fashion. This Scenario is 100% the other way of what you did - but it allows people to just put up a page that is not CMS managed, and have access controlled from the CMS' user database. It means all the standard controls can work.

    But the moment you try to integrate multiple services from an SOA approach - you basically are dead, performance wise. It could work if SOA wout run around an object protocol (with a standardaized interface regardless how many objects you really use), and would have a query implementation. But as SOA works around function specific methods...
    ...any retrieval that needs to merge data from multiple services, even on the same machine, is vastly inferior from an integrated O/R mapper object model, which can go down to the database into what can be a 64kb large complex sql statement. The SOA alternative has to pull the data from different services (hoping there are at least mass-retrieval methods available, not just "get me ONE name for a user) and perform a client side join.

    What happend to the notion of "layer interfaces have to be non-chatty"? It is forgotten.

  • In most cases, the database already *is* exposed in a standard fashion too... System.Data.SqlClient! :) Last year I came into a "fixer upper" project where everything was a Web service. The app and the SQL Server were on the same box! SOA makes sense when you have different systems that normally speak different languages, but I have no idea what that was about.

  • Our upcoming free forum system which was started back in 2002 (and now in the process of being updated for asp.net 2.0) has its own membership/role system as well and I looked at the ASP.NET 2.0 version and I didn't see any reason to rip out our own role / membership system for the ASP.NET 2.0 one, especially because what's in place works fine and is easily extensible.

    However it depends on the design I think if the membership / role system is really not usable with a data-access layer. For example, in a search routine, you can push down from the application core the list of forums a user can see based on the role rights the user has, which where loaded when the user signed in. You can then use that list of forums to filter out the threads in forums the user doesn't have access and therefore the membership data can be used without needing to join.

    IMHO joining with role data to perform any action is too slow and inefficient as you know way before that if the user is able to do things or not and you can bake in limitations on forums and threads for example through filters based on the role rights read for the current user.

  • Wow.

    The "constraints and boundaries" as you keep referring, lets call it the Provider model. The Provider model isn't meant to fit every scenario. However, what it does provide is a base functionality that is common to most authentication schemes. Authentication and Role Management are abstracted into the provider.. you shouldn't need to delve too deep into what it does. Consider linking users providerkeys (to another database on the same server) to a users table.. use your custom user table as all the extended data.. in your object model, you have a way to create your base user type from Membership.GetUser().

    If you're looking to truly make interoperative controls, please continue to question your belief that using a provider system constrains you. http://en.wikipedia.org/wiki/Provider_model

Comments have been disabled for this content.