A Perfect Storm: LINQ to SQL, Dependency Injection, and ASP.NET Providers

Recently I ran into a scenario where LINQ to SQL, dependency injection, and ASP.NET providers were put together in a way that created a perfect storm that was difficult to troubleshoot. I’ll share some of the circumstances around the creation of that perfect storm, plus my solution, with the hope that it will spare someone else the difficulty of dealing with the same problem.

LINQ to SQL’s DataContext and Stale Results

As you know from a previous article, I’m a big fan of PLINQO because it removes the rough-edges from LINQ to SQL (L2S) and makes it a very viable solution for high-performance sites that need easy-to-manage object mapping. However, I’m not a fan of how LINQ to SQL handles the caching of query results internally. Specifically, I’m not a fan of how a LINQ to SQL DataContext will actually connect to the database, execute a query, and then discard the results of that query if it has already pulled back results for the same query previously.

Of course, it’s important to point out that I’m not opposed to LINQ to SQL caching results. In fact, I think it’s a great idea because LINQ to SQL DataContexts are DESIGNED to be very short lived (i.e., they should, ideally, be scoped to a single HTTP Request when used in a web-app for instance). My beef is just with the fact that caching is in play, yet L2S will still query the database only to DISCARD the results.

That said, as long as you understand that LINQ to SQL DataContexts are designed to be short lived (see Myth #10), then everything is fine. Unless, of course, you were to accidently scope one of the DataContexts into something long-term like, say, an ASP.NET Membership Provider.

ASP.NET’s Provider Model

Even though ASP.NET’s provider model for custom Membership, Roles, and Personalization has been around forever (it was released with ASP.NET 1.1), custom providers are still a key component of many ASP.NET sites. In my estimation that’s because custom providers are so incredibly easy to implement.

For example, if you want to create a custom ASP.NET MembershipProvider that will let website users authenticate against your own database, all you need to do is:

  • Inherit from MembershipProvider
  • Have a Parameterless constructor (more on this in a second)
  • Add logic to the ValidateUser method (which takes in a username and password and returns true or false).
  • Wire up the proper directives in your web.config

From there, ASP.NET uses your MembershipProvider implementation as the implementation for your application – which means that when users need to log into your site, they can be authenticated against your database or against whatever logic you’ve defined.

Dependency Injection

However, the problem is that most custom provider implementations are going to need access to a database in order to do lookups, pull back role assignments, verify usernames and passwords, and so on. LINQ to SQL can be used very effectively in these cases because it makes it very easy to pull back custom ‘User’ objects which can then be evaluated for things like authentication and role assignment.

That said, it’s a worst-practice to just use a LINQ to SQL DataContext directly within any consuming class because it makes your code brittle, hard to test, and subject to poor coupling. Consequently, a better approach is to use a Repository—where a Repository object wraps the LINQ to SQL DataContext (and any association with the underlying data access) from consumers. In this way, whenever you need access to a certain kind of repository within a class or object (in smaller applications you might have only a single repository, whereas in a larger application you might have a UserRepository, a ReportsRepository, an AdminRepository, and so on), you just pass a concrete instance of the needed repository’s interface into your classes or controllers via dependency injection.

Typically, constructor injection is the preferred method for injecting dependencies because requiring instances of things like FileReaders, PermissionsManagers, and DataRepositories within a constructor makes it painfully obvious that the class in question must have these objects (and encapsulated logic) available in order to function properly. (As opposed to just having the class magically and mysteriously create instances of these classes internally, which leads to poor coupling because this approach means that you don’t have a clear understanding of what kinds of object dependencies you have in place.)

The Gotcha

Here’s the problem: ASP.NET’s providers were designed and deployed before dependency injection really caught on, which means that ASP.NET’s providers won’t allow constructor injection. If you search for dependency injection and ASP.NET providers on the web, you’ll see plenty of people complaining about this problem, and Microsoft REALLY does need to update the providers (to bring them up to date with the latest coding practices and trends).

And, sadly, because there’s so much overhead involved in trying to intercept the ASP.NET runtime when it’s initializing and loading in provider definitions from the web.config, there’s really no easy or good way to do dependency injection with custom ASP.NET Providers. Which, in turn, leads to an anti-pattern where developers commonly just instantiate instances of requisite objects within the parameterless constructors for these objects – as seen in the following code listing:

public class CustomProvider : MembershipProvider, IMembershipProvider

 

\\{

    private IUserRepository _userRepo;

 

    // proper constuctor injection:

    public CustomProvider(IUserRepository repo)

    \\{

        this._userRepo = repo;

    \\}

 

    // requisite parameter-less constructor:

    public CustomProvider()

    \\{

        // anti-pattern: DO NOT DO THIS!!!

        this._userRepo = ObjectFactory.GetInstance<IUserRepository>();

    \\}

\\}

In the preceding code snippet, the first constructor represents the correct way to do dependency injection because the CustomProvider has absolutely no knowledge of how it gets a repo instance— it just knows that it needs one.

The second constructor, however, implements an anti-pattern as it couples the logic for retrieving an IUserRepository instances right into the class itself, instead of just passing that information into the CustomProvider as data or a contract (i.e., an interface implementation).

Sadly though, without the ability to use constructor overloads with providers, this anti-pattern makes its way into lots of code. (In fact, it made it into my code after I turned up empty-handed with solutions for doing dependency injection correctly.)

However, the bigger issue here isn’t that this approach uses an anti-pattern. The problem is that ASP.NET’s providers are global in scope—meaning that they get created when the application (or website) starts, and they stay in memory until the application terminates. That, in turn, means that any member variables that they have are going to be scoped indefinitely.

And that’s exactly what you don’t want to do with a LINQ to SQL DataContext. (Likewise, since the Entity Framework leverages core L2S capabilities, you’ll want to watch out for this when using the Entity Framework as well.) In fact, if you step into this trap (as I did), then you’ll notice that in cases where users might change their password (from a different page/controller – where a different DataContext is instantiated for the scope of that single POST/Request), they’re unable to log back into the system because the globally scoped DataContext in your Membership Provider will used the cached results for authentication when this user tries to log back into the system.

And while I’ll still balk at my dislike for how L2S will still query the database for this user who is attempting to log in, and throw away the query results, caching is not at fault here. Instead, the behavior is by design because L2S DataContexts should not be kept in scope over multiple requests.

It’s just that many developers, this one included, don’t always think about that when wiring up repositories within their providers – especially if they’re settling for a hack or anti-pattern to start with.

Correcting the Problem

In the end, correcting this problem requires that you, effectively, get a new DataContext per each individual request against your Custom Provider. That’s the best approach from the standpoint of managing the scope of your underlying DataContexts.

To handle that, I’ve taken the following approach:

public class CustomProvider : MembershipProvider, IMembershipProvider

\\{

    private IUserRepository _userRepo;

 

    // this .ctor is used via unit tests (as a seam)

    public CustomProvider(IUserRepository repo)

    \\{

        this._userRepo = repo;

    \\}

 

    // requisite parameter-less constructor:

    public CustomProvider()

    \\{

        // do NOTHING here

    \\}

 

    public override bool ValidateUser(string username, string password)

    \\{

        // HACK:

        IUserRepository repo = this._userRepo ??

            ObjectFactory.GetInstance<IUserRepository>();

        SiteUser user = repo.GetUserByEmailAddress(username.ToLower());

        if (user == null)

            return false;

 

        if (!user.Active || !user.Verified)

            return false;

 

        if (user.PassPhrase.IsNullOrEmpty())

            return false;

 

        // do other verification... etc

    \\}

\\}

 

I use the approach listed above because it still relies upon my object factory (I’m using StructureMap) to resolve the concrete implementation of my UserRepository, which is still a win (though not as much as proper dependency injection would be). Likewise, this approach still lets me test my ValidateUser method via unit tests because I can pass in mocks, stubs, or fakes into the constructor that takes in an IUserRepository instance.

A Hope for the Future

The solution listed above is far from perfect (it’s actually a vicious hack), but that’s kind of what you’re left with when you can’t properly do dependency injection with ASP.NET’s older providers. Hopefully Microsoft will address this issue (and others) with the providers to make them easier to use in the future. But, even if it does, developers will still have to make sure that they’re not accidently scoping their DataContexts for extended periods of time.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish