How to register common ASP.NET abstractions?

Mar 11, 2015 at 6:43 PM
I'm thinking of the following:
container.Register<BundleCollection>(() => BundleTable.Bundles);
container.Register<RouteCollection>(() => RouteTable.Routes);
container.RegisterPerWebRequest<IIdentity>(() => HttpContext.Current.User.Identity);
container.RegisterPerWebRequest<HttpSessionStateBase>(() => new HttpSessionStateWrapper(HttpContext.Current.Session));
container.RegisterPerWebRequest<HttpContextBase>(() => new HttpContextWrapper(HttpContext.Current));
container.RegisterPerWebRequest<HttpServerUtilityBase>(() => new HttpServerUtilityWrapper(HttpContext.Current.Server));
But I'm not sure about the first two, what the lifetime should be for BundleCollection and RouteCollection. The others seem right being tied to the request but I could be wrong.

Thanks - wg
Coordinator
Mar 11, 2015 at 7:09 PM
Why do you want to register all that stuff in the container? Where do you need to inject these types?
Mar 11, 2015 at 10:31 PM
  1. Unit tests
  2. So as to get access to these services from anywhere in the application (not just the MVC app).
Coordinator
Mar 11, 2015 at 11:00 PM
My first impression is if your application code depends directly on HttpSessionStateBase, HttpContextBase or HttpServerUtilityBase, you are violating both the Dependency Inversion Principle and the Interface Segregation Principle.:
  • Even though all three classes are abstract, I would say you are still violating the DIP, because all classes still contain a lot of logic and you are still depending on the ASP.NET sub system. The DIP also dictates that it is the client (your application) that should define the abstraction.
  • Those abstractions are wide (contain a shitload of methods) and this makes you violate the ISP that tells that clients should only be forced to depend on methods that it actually uses.
So even though these abstractions make it possible to test your code, they still not make it easy to do so, because you are forced to work with these big classes. Instead you should define abstractions that are specific to your application. So for instance, you might have some class like this:
public class TenantOperator : ITenantOperator
    private readonly HttpContextBase context;
    public (HttpContextBase context) {
        this.context = context;
    }

    public void Operate() {
        Guid tenantId = Guid.Parse(this.context.Request.QueryString["TenantId"]);
         // Do something useful with the tenant
    }
}
The code above can be refactored to something that is much cleaner:
public interface ITenantContext {
    Guid CurrentTenantId { get; }
}

public class TenantOperator : ITenantOperator
    private readonly ITenantContext tenantContext;
    public (ITenantContext tenantContext) {
        this.tenantContext = tenantContext;
    }

    public void Operate() {
        Guid tenantId = this.tenantContext.CurrentTenantId;
         // Do something useful with the tenant
    }
}
The use of the very narrow (role) interface ITenantContext allows the TenantOperator to be much cleaner, simpler and easier to test. We have prevented leaking any implementation details of ASP.NET into our consumer. Of course we need to create an implementation of ITenantContext:
public class AspNetTenantContext : ITenantContext {
    public Guid CurrentTenantId {
        get { return Guid.Parse(HttpContext.Current.Request.QueryString["TenantId"]); }
    }
}
The AspNetTenantContext is specific to ASP.NET. This class functions as a proxy between our application and the underlying application infrastructure. Also notice that I didn't even care about using the HttpContextBase. I just use HttpContext.Current directly. Simply because there's nothing here to test anyway.

This type of design is called Screaming Architecture by Robert Martin. In Cockburn's Hexagonal architecture, the ITenantContext is called a port and the AspNetTenantContext the adapter. I just call this a SOLID design.

This does mean however that you can get some extra interfaces that are really narrow and specific. In practice I found however that the number of extra interfaces I got is fairly limited, while it made my design much cleaner, and often made it much easier to test and maintain my systems. For instance, there might come a moment that you want to run some code in a background thread. But in a background thread, there is no HttpContext available. Now with the altered design, we are able to run the TenantOperator on a background thread without making any modifications to it (Open/closed). We obviously need some second 'BackgroundTenantContext` implementation of some sort to pull this off, but other classes stay untouched.

Do note that it is not always easy to get to this design, and you sometimes need to take a few steps back and reconsider other parts of your design. But in the end my experience is that it is almost always totally worth it to take your time to improve your code to conform to SOLID.
Coordinator
Mar 11, 2015 at 11:05 PM
But back to your original question about bundles and routes. Both BundleTable.Bundles and RouteTable.Routes properties return a singleton anyway, so there's no harm in registering them as follows:
container.RegisterSingle<BundleCollection>(BundleTable.Bundles);
container.RegisterSingle<RouteCollection>(RouteTable.Routes);
Since both properties always return the same reference to the underlying collection, we can safely cache it in Simple Injector. If the collection is updated later on, this can be seen by classes that got the collection injected into.