Asp.Net Membership Provider’s Lifetime Considerations- Part 2

Previously I made a post about issues I encountered with the Asp.Net Membership Provider.

My fix was a bit short-sighted as it only fixed the issue in one location, when the account controller tries to perform operations. The problem with my solution is that it does not deal with all the areas where Asp.Net creates the membership provider itself, and keeps that instance running through the application lifetime.

This meant that all calls to the membership provider (e.g. calling Membership.GetUser()) were all using the same database session, even across multiple web requests. Not only did this cause hidden caching issues, this also mean that if the database connection was broken all calls to the membership provider would now exception out with the only way to fix it is to restart the Asp.Net instance.

In order to fix this I had to remove my database session creation out of my custom membership provider’s constructor, and instead retrieve a new database session from my IoC system inside each method.

This seems to not only fix my original issue, but several other “random” exceptions that I could not reproduce on a regular basis.

Beware of Asp.Net’s Membership Provider Lifetime, Entity Framework Caching, and Dependency Injection

I recently struggled while dealing with a bug I encountered in my Asp.Net MVC application, and I thought I would write about it to hopefully help someone else.

I have a method in my business layer that users use to change their password. After implementing unit tests to verify the method worked properly I implemented a call to it in my account controller. Everything seemed perfect at first, as I was able to change my password successfully and even validate that the user entered their correct current password, which is required to perform a password change.

However, when I logged out and tried to log back in using the new password the login attempt failed. Yet when I used my previous password I was able to log in! To make things even more confusing, since I require users to enter their current password to change their password I was able to confirm that the password change did actually take effect. Finally, when I made a debugging change and re-ran the app, the new password worked when logging in!

A few irritating hours later, I finally figured out what was wrong. It came down to the difference of lifetimes in my MVC application between different classes. My AccountMembershipService class, which was mostly based on the default code that came with MVC, had the following constructor:

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }

The problem is that my application loads its service classes using Castle Windsor, and entity framework is loaded from Windsor with a per-web request lifetime. Even though my custom membership provider was retrieving the database context from Windsor, Asp.Net creates the membership provider for the lifetime of the whole application.

So what happened was that I would log in using the database context for the Membership Provider, but when I changed my password the service class would use a different database context. When I changed my password, the password is correctly changed in the database (and the request’s specific database context), but the Membership Provider is still using the original database context. Since Entity Framework caches previously received entities, when I go back to log in entity framework receives the user entity out of the cache, not the database, and thus the old password is what will pass the Membership Provider’s validation check.

The fix was to replace the previous code with:

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? new MyMembershipProvider();
        }

This forces a new membership provider instance to be created for that web request, and thus guarantees that the database context will not be holding a stale user entity.

Hopefully this saves someone from the irritation and wasted time that I went through.


Update: I have written another post adding on to the issues I wrote about here.