Extreme ASP.NET Makeover: Disentangling Our Tangled Web-Overview - Refactoring

Download

Right click “Save as…”

  • High Quality WMV (PC)
  • MP3 (Audio only)
  • MP4 (iPhone, Android)
  • Mid Quality WMV (Lo-band, Mobile)
  • WMV (WMV Video)

A Topsy-Turvy World

The constructor signature for AuthorizationChecker now looks like this:

public AuthorizationChecker(
    ISettingsStorageProviderV30 settingsProvider, 
    IAuthTools authTools, 
    IAclEvaluator aclEvaluator) { ... }

We can clearly see that AuthorizationChecker depends on implementations of ISettingsStorageProviderV30, IAuthTools, and IAclEvalator, but we are not concerned with the actual implementations. This is the dependency inversion principle at work.

"High-level modules should not depend on low-level modules. Both should depend on abstractions." – Robert C. Martin (http://objectmentor.com/resources/articles/dip.pdf)

In this case, AuthorizationChecker and AuthTools don’t depend on each other, but depend on the abstraction provided by IAuthTools. The same is true for AuthorizationChecker’s other two dependencies, ISettingsStorageProviderV30 and IAclEvaluator.

We have decoupled AuthorizationChecker from its dependencies. The question remains that AuthorizationChecker still needs these dependencies supplied at run-time to actually be able to execute. The simplest way to supply these dependences as default implementations via AuthorizationChecker's parameterless constructor:

public AuthorizationChecker() : 
    this(Settings.Instance.Provider, 
        new AuthTools(), new AclEvaluator()) {
}

Of Containers and Castles

A more flexible way to supply dependencies is to use an Inversion of Control container, such as Castle Windsor. Castle Windsor is a set of assemblies that we reference from our code. We simply register all of our dependencies in the container and then ask the container for an implementation. The container walks the dependency chain, creates objects in the correct order, supplying dependencies as it creates objects further up the dependency chain until it can finally return us a fully constructed object. Consider the following code:

var authorizationServices = IoC.Resolve<IAuthorizationServices>();

The IoC is a static gateway class that provides an abstraction so that your code is not dependent on a particular IoC container for retrieving dependencies. It is similar in interface and approach to Microsoft’s Common Service Locator available on CodePlex. You can read more about the IoC static gateway in “Loosen Up: Tame Your Software Dependencies for More Flexible Apps”.

In order for Castle Windsor to satisfy this request for an implementation of IAuthorizationServices, we’ll need to register the dependencies:

var ssp = ProviderLoader.LoadSettingsStorageProvider(
    WebConfigurationManager.AppSettings["SettingsStorageProvider"]);
container.Register(
    AllTypes.FromAssembly(Assembly.GetExecutingAssembly())
        .Where(x => x.Namespace.StartsWith("ScrewTurn.Wiki.Services"))
        .WithService.FirstInterface(),
    Component.For<IAclEvaluator>().ImplementedBy<AclEvaluator>(),
    Component.For<ISettingsStorageProviderV30>().Instance(ssp)
);

This code is located in the ScrewTurn.Wiki.Core assembly. Rather than manually configuring each dependency individually, we use a convention-based approach whereby any types in the ScrewTurn.Wiki.Services namespace are registered via their first interface. This means that AuthorizationServices is registered as IAuthorizationServices, AuthTools is registered as IAuthTools, and similarly for other types in this namespace. This has the advantage that by placing services in the ScrewTurn.Wiki.Services namespace, they will automatically be registered in the container without requiring explicit configuration. Other classes can then take dependencies on these services simply by adding the appropriate interface to their own constructor.

AclEvaluator is located in the ScrewTurn.Wiki.AclEngine assembly. Since it is the only dependency in this assembly, we register it explicitly. There isn’t a great deal of value from having a separate assembly for ScrewTurn.Wiki.AclEngine and I would consider merging the AclEngine project into Core. AclEvaluator could then be placed in the ScrewTurn.Wiki.Services namespace and auto-registered like the other dependencies. For the moment, AclEvaluator serves as an example of how to explicitly configure dependencies in Castle Windsor.

ScrewTurn Wiki has code that dynamically loads a particular ISettingsStorageProviderV30 implementation specified by a fully qualified class name in a configuration file. Although we could use Castle Windsor’s configuration facilities to configure and load the appropriate assembly and implementation, that is not the current focus. We can leverage the existing loading mechanism by supplying Castle Windsor with a fully constructed instance via the Component.For<T>().Instance(obj) syntax.

Windsor provides many more configuration options, including overrides, parameters, XML configuration, and more. You can find a lot more information on configuration options in Castle Windsor in my article, Bricks and Mortar: Building a Castle.

Now that AuthorizationServices and all of its dependencies have been registered with the container, we can successfully resolve it from the container:

var authorizationServices = IoC.Resolve<IAuthorizationServices>();

To service this request, Windsor will look for an implementer of IAuthorizationServices, which is AuthorizationServices. Windsor will then examine AuthorizationServices constructor, specifically the constructor parameters:

public AuthorizationServices(IAuthorizationChecker authorizationChecker) {
    this.authorizationChecker = authorizationChecker;
}

Windsor will look to see if it has an implementation registered for IAuthorizationChecker, which it does in the form of AuthorizationChecker. Looking at AuthorizationChecker’s constructors:

public AuthorizationChecker() : this(Settings.Instance.Provider, 
    new AuthTools(), new AclEvaluator()) {
}

public AuthorizationChecker(ISettingsStorageProviderV30 settingsProvider, 
    IAuthTools authTools, IAclEvaluator aclEvaluator) {
    ...
}

Windsor has two constructors to choose from. It starts from the constructor with the most overloads. It will see if it has implementations for all of the constructor parameters. (If it can’t find implementations for all constructor parameters, Windsor will attempt the next most overloaded constructor.) In this case, it will see that it needs to find an implementation for ISettingsStorageProviderV30, IAuthTools, and IAclEvaluator. Windsor has an instance of ISettingsStorageProviderV30 that we supplied to it earlier. It can also create instances of AuthTools and AclEvaluator because they have no further dependencies. Windsor passes the newly created AuthTools and AclEvaluator, as well as the pre-built ISettingsStorageProviderV30 to AuthorizationChecker’s constructor. Windsor then passes the newly created AuthorizationChecker to AuthorizationServices’ constructor. Finally, Windsor is able to pass back AuthorizationServices to satisfy the call to IoC.Resolve<IAuthorizationServices>.

Note that Windsor is responsible for managing the lifetime of objects registered in the container. Windsor’s default lifetime strategy is singleton. That means that multiple classes depending on IAuthorizationServices will all receive the same instance of AuthorizationServices. This instancing policy is easily changed on a per-service basis and includes options such as per-thread, per-Web-request, transient, and pooled. You can even create your own instancing policies if none of the built-in ones are appropriate.

With the container responsible for wiring dependencies between objects, we can remove the parameterless constructors as they are no longer required:

public AuthorizationChecker(ISettingsStorageProviderV30 settingsProvider, 
    IAuthTools authTools, IAclEvaluator aclEvaluator) { 
    ...
}

If AuthorizationChecker required another dependency, we would simply declare that fact by adding another constructor parameter with a type of the required service interface. We would then create a concrete class in the ScrewTurn.Wiki.Services namespace that implements that service interface.

Managing the Container

I have seen many projects place all container configuration into a single XML or C# file. This is especially unwieldy if explicitly configuring dependencies individually, which is why I prefer the convention-over-configuration approach discussed above. By placing dependencies in certain assemblies and namespaces, they are automatically registered. (Note that the convention-over-configuration approach is not available with XML files, although XML files are useful for specifying deployment-time overrides if necessary. For more information about this technique, see Bricks and Mortar: Building a Castle.) Placing all of our conventions in a single C# file can quickly become cumbersome as we have unrelated conventions bundled together.

Let’s take a look at an approach that allows us to separate our conventions into smaller, simpler classes. It all starts with the ApplicationBootstrapper, which is called during application startup:

public void Configure() {
    container = new WindsorContainer();
    IoC.Initialize(new WindsorDependencyResolver(container));

    RegisterFacilityStartupTasks();
    ExecuteFacilityStartupTasks();
    RegisterContainerStartupTasks();
    ExecuteContainerStartupTasks();
    RegisterStartupTasks();
    ExecuteStartupTasks();
}

We start by creating a new WindsorContainer and using it to initialize the IoC static gateway. We then alternately register and execute FacilityStartup, ContainerStartup, and Startup tasks, as shown in Figure 2.

Figure 2 Execute FacilityStartup, ContainerStartup, and Startup Tasks

private void RegisterFacilityStartupTasks() {
    RegisterAllTypesBasedOn<IFacilityStartupTask>();
}

private void RegisterContainerStartupTasks() {
    RegisterAllTypesBasedOn<IContainerStartupTask>();
}

private void RegisterStartupTasks() {
   RegisterAllTypesBasedOn<IStartupTask>();
}

private void RegisterAllTypesBasedOn<T>() {
    assembliesToScan.ForEach(assembly => 
        container.Register(
            AllTypes.FromAssembly(assembly)
                    .BasedOn<T>()
                    .WithService.FirstInterface())
    );
}

The list of assemblies to scan for startup tasks is provided via AssemblyBootstrapper’s constructor and defaults to Assembly.GetExecutingAssembly if left unspecified. We look through each assembly for types implementing IFacilityStartupTask, IContainerStartupTask, and IStartupTask and register them against the appropriate interface. (Facilities are Windsor’s primary extension mechanism. You can use facilities to implement everything from automatic transaction management to logging to array parameter resolution and more.) Next, we need to execute each of the startup tasks:

private void ExecuteFacilityStartupTasks() {
    var tasks = container.ResolveAll<IFacilityStartupTask>();
    tasks.ForEach(task => task.Execute(container));
}

private void ExecuteContainerStartupTasks() {
    var tasks = container.ResolveAll<IContainerStartupTask>();
    tasks.ForEach(task => task.Execute(container));
}

private void ExecuteStartupTasks() {
    var tasks = container.ResolveAll<IStartupTask>();
    tasks.ForEach(task => task.Execute());
}

Note that IFacilityStartupTask|IContainerStartupTask.Execute both take an IWindsorContainer, as their purpose is to configure the container. IStartupTask.Execute is to perform other types of initialization once the container is ready to be used and therefore doesn’t accept an IWindsorContainer.

The individual startup tasks are small and self-contained. For example, take a look at the ServiceRegistration class, which is responsible for registering application-related services:

public class ServicesRegistration : IContainerStartupTask {
    public void Execute(IWindsorContainer container) {
        container.Register(
            AllTypes.FromAssembly(Assembly.GetExecutingAssembly())
                .Where(x => x.Namespace.StartsWith("ScrewTurn.Wiki.Services"))
                .WithService.FirstInterface(),
            Component.For<IAclEvaluator>().ImplementedBy<AclEvaluator>()
        );
    }
}

You can see the convention responsible for registering all services in the ScrewTurn.Wiki.Services namespace. There is also the extra configuration for the AclEvaluator. If we choose to combine the AclEngine project into Core, we could eliminate the extra configuration step for AclEvaluator.

We have a completely separate and independent IContainerStartupTask for Host and Provider registration:

public class HostAndProviderRegistration : IContainerStartupTask {
    public void Execute(IWindsorContainer container) {
        container.Register(
            Component.For<IHostV30>().ImplementedBy<Host>());

        var ssp = ProviderLoader.LoadSettingsStorageProvider(
            WebConfigurationManager.AppSettings["SettingsStorageProvider"]);
        container.Register(
            Component.For<ISettingsStorageProviderV30>().Instance(ssp));
    }
}

As we break apart the “God object” that is the Host, we can modify its container registration in one place without affecting the registration of the services. If we need to perform additional unrelated container configuration, we create another class that implements the IFacilityStartupTask or IContainerStartupTask without affecting other configuration that is already taking place.

To wrap up, let’s refactor StartupTools.Startup() into a IStartupTask where it belongs:

Conclusion

Although it is not initially obvious, the ScrewTurn Wiki codebase suffers from dependency problems. Tight coupling of implementation classes caused by singletons and static classes result in a morass of objects, which is difficult to refactor. Dependency cycles between objects result in sensitive constructor and method orderings, which can result in unexpected NullReferenceExceptions after apparently innocuous changes to the code.

In this article, we have made dependencies obvious by changing them into constructor parameters and using an IoC container to wire together the dependencies. Using convention-over-configuration approaches eases the introduction of new functionality, without requiring explicit configuration. We have introduced a reusable ApplicationBootstrapper, whereby new startup tasks can be added as the application grows without modifying existing code. The resulting codebase should be more flexible and maintainable over time.

Other videos from this article

Read the full article at http://msdn.microsoft.com/magazine/ee424155.aspx

Tags:

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.