Software Design Blog

Journey of Rinat Abdullin

Structuring .NET Applications With Autofac IoC

Below you will find my first article in series on building applications with Autofac Inversion of Control Container and Lokad Shared Libraries.

In this article we’ll concentrate on structuring multi-project .NET applications with Autofac.

  • We’ll begin with taking a closer look at Assemblies and Hosts.
  • Then, we’ll go to conventional Configuration Blocks and Autofac Modules.
  • This article will end up in an overview of approaches to organize Modules in complex and simple solutions.

Let’s get started.

Assemblies

Basically, any solution could be divided into assemblies and hosts (application entry points).

Assemblies are common library projects (class library) that contain reusable and testable functionality for your application. This functionality usually comes in form of components:

  • Views, Controllers and Models
  • Services
  • Persistence classes and repositories
  • Decorators
  • Reusable user controls
  • Rule libraries
  • Business logic

It is a good rule of thumb that these library projects generally should not reference any of:

  • IoC container assemblies;
  • Logging frameworks (BTW, for this purpose, there is ILog abstraction in Lokad.Shared.dll);
  • DAL Frameworks;
  • other 3rd party libraries.

In order to separate such logic, we can define and use some interfaces in our code. Then we will simply wire specific implementations to these interfaces in a configuration code.

Example of such an abstraction would be an ILog interface in Lokad.Shared.dll. It is defined in the main library and even has a couple of simple .NET implementations. In production scenarios, ILog can be wired to Apache log4net logging library in Lokad.Stack.dll (I’ll talk more about this in the articles-to-follow).

Since this interface is completely decoupled from the implementation, we can reconfigure the loggers or switch the logging framework without recompiling assemblies that use the interface.

Hosts

Hosts represent application entry points. Examples of host projects are:

  • desktop applications:
    • Windows.Forms;
    • WPF;
    • DX.
  • console applications;
  • windows services;
  • web applications
  • Microsoft Office Add-Ins;
  • Microsoft Azure Roles.

Hosts are responsible for establishing the environment for application (or context) and then passing the execution to some entry point.

In the Inversion of Control age, this is done by configuring assembly components into the container, resolving some Shell class (which happens to be a component as well) out of it and calling Shell.Run();

Normally, host projects should be as small as possible and just have two logical steps: configure container and call Shell.Run().

Here’s how the simplest application entry point might look in Autofac world:

var builder = new ContainerBuilder();
builder.Register(new ConfigurationSettingsReader());

using (var container = builder.Build())
{
  var shell = container.Resolve<Shell>();
  shell.Execute();
}

This code will work in all scenarios that have a single point of entry.

Web applications (including web services) could be considered as having multiple entry points - IIS creates web service or pages instances by itself (no constructor injection for us) and then passes execution to them. This situation is a reasonable excuse for static service locator class:

public static class GlobalSetup
{
  static readonly IContainer _container;

  static GlobalSetup()
  {      
    // this is executed only once,
    // as soon as code execution hits this class
    var builder = new ContainerBuilder();
    builder.Register(new ConfigurationSettingsReader());
    _container = builder.Build();
  }

  public static TService Resolve<TService>()
  {
    return _container.Resolve<TService>();
  }
}

Sample usage in web service endpoint:

public sealed class CustomerRepository : WebService
{
  ICustomerRepository _inner;
  ILog _log;
  public CustomerRepository()
  {
    // if this is a very first call, then the 
    // static constructor will fire
    _inner = GlobalSetup.Resolve<ICustomerRepository>();
    _log = GlobalSetup.Resolve<ILog>();
  }

  /// wrapper implementation
}

In Windows Azure applications, configuration does not end up in the config file. We also have the shared configuration for the role, that is distributed by the RoleManager. To support module reuse in the cloud environment, we must poll the RoleManager for the configuration overrides, before performing the actual wiring.

Configuration Blocks

All the configuration wiring magic happens in some place between the call to XML configuration and resolution of the first component.

It seems to be a rather common approach to do all the wiring in form of XML:

<components>
  <component id="LoginService"
     service="Company.Shared.ILoginService,Company.Shared.Interface"
     type="Company.Server.LoginService,Company.Server.Core"/>
  <component id="NavigationService"
     service="Company.Shared.INavigationService,Company.Shared.Interface"
     type="Company.Server.NavigationService,Company.Server.Core"/>
  <component id="HashService"
     type="Company.Shared.HashService,Company.Shared.Core"
     service="Company.Shared.IHashService,Company.Shared.Interface"/>
  <component id="WebViewFactory"
     service="Company.Web.IWebViewFactory,Company.Web.Interface"
     type="Company.Web.SimpleFactory,Company.Web.Core"/>
  <component id="DataStoreProvider"
     service="Company.Server.IDataStoreProvider,Company.Server.Interface"
     type="Company.Server.DataStoreProvider,Company.Server.Core">
    <parameters>
      <connectionString>My Connection String</connectionString>
    </parameters>
  </component>
  <!--Skipped for the sake of sanity-->
  <!-- That's a piece of my own config back in the naive ages 
  when I thought that 3-tier application with 12 projects is complex 
  and Castle.Windsor is the best IoC container for .NET. -->

  <!-- That script is dated 2007-12-19, actually))) -->
</components>

Disadvantages of such an approach are:

  • There is absolutely no strong-typing, compiler support or IntelliSense. Typing fully-qualified type references by hand is simply inefficient.
  • Configuration files tend to get large and messy.
  • It becomes really hard to implement and maintain multiple configuration profiles (in 1-click release process).
  • Renaming of component class in the code, will make configuration inconsistent (more classes are referenced in XML - higher the probability of this problem).
  • XML files are not well-suited for complex configuration scenarios (i.e.: defining a registration that returns read-only repository cache if there is a network failure).

Different approach is to encapsulate configuration specifics in .NET code blocks, that accept a few human-readable (and writeable) parameters from XML and then use that information to perform configuration in the code.

Logically, any configuration block could be represented with some high-level function:

// this pseudo-function configures the entire DAL
// for our application.
void Module(
  string connectionOptions,
  bool useMockDataLayer,
  Mode mode);

public enum Mode
{
  Release, Diagnostics, Debug
}

XML becomes more simple with this approach (if a property is missing, then default value is used):

<module type="Company.Data.Module, Company.Data">
  <properties>
    <property name="ConnectionOptions" value="my connection string" />
  </properties>
</module>

Autofac Modules

In the world of Autofac .NET IoC, configuration blocks are represented in the form of Modules. Skeleton of an Autofac module, implementing pseudo-function definition above, might look like:

public sealed class Module : IModule
{
  public string ConnectionOptions { get; set; }
  public bool UseMockDataLayer { get; set; }
  public Mode Mode { get; set; }

  public Module()
  {
    Mode = Mode.Release;
  }      

  public void Configure(IContainer container)
  {
      // we configure application infrastructure in different ways
      // according to the properties of this class
      // (properties are loaded from XML by Autofac)
  }
}

For this module (we’ll talk about the implementation later), following configurations are valid:

<!-- Production configuration -->
<module type="Company.Data.Module, Company.Data">
  <properties>
    <property name="ConnectionOptions" value="production connection string" />
  </properties>
</module>

<!-- Development configuration -->
<module type="Company.Data.Module, Company.Data">
  <properties>
    <property name="Mode" value="Debug" />
    <property name="UseMockDataLayer" value="True" />
  </properties>
</module>  

<!-- Sandbox configuration -->
<module type="Company.Data.Module, Company.Data">
  <properties>
    <property name="Mode" value="Diagnostics" />
    <property name="ConnectionOptions" value="connection to test DB" />
  </properties>
</module>

Structuring with Modules

So modules are responsible for wiring together components from different assemblies into the container.

Configuration process for an application might involve following steps:

  • configure logging and register ILog (i.e.: log to console, Windows Event Log, flat file, trace etc);
  • configure reliability policies (i.e.: retry on Db deadlocks and connectivity exceptions in Release scenario or pass exception to Visual Studio in Debug scenario);
  • register data access classes (i.e.: if we are mocking our DAL, then register mock repository services, otherwise register real services using the DB connection string).
  • register injection points for cross-cutting concerns (i.e.: register service decorators that apply logging, exception handling and validation to all calls to these services)
  • configure behavior of validation rules (i.e.: use thorough validation of arguments in debug scenario or throw exception on first warning in Release scenario).
  • use different UI skins to distinguish between production and diagnostics configurations.

As you can see, configuration code can become really tense. It could bring together numerous classes from multiple assemblies and 3rd party frameworks.

Based on the complexity of your solution, two major scenarios are possible for the organization of IoC modules.

Simple Scenario is common to situations with 5-10 assemblies and 3-5 hosts. For example: multi-tier application with web services and a simple smart client.

Simple scenario of structuring applications with Autofac .NET IoC Modules

In this situation every configuration block is unique, so it feels logical to define module classes in the same projects with the hosts that use them.

Complex scenario. As the complexity of the solution increases, similar bits of configuration code would emerge in different hosts. For example, we might have automation engine and web services application use the same data access components configured in a similar manner. Or we might have multiple desktop clients using repository interfaces exposed by our web services.

Comples scenario of structuring applications with Autofac .NET IoC Modules

In such a situation it feels logical to encapsulate reusable configuration bits in separate modules, that would get leveraged in different hosts.

Refactored scenario of structuring applications with Autofac .NET IoC Modules

By the way, there are some guidelines on naming and solution organization that you could adopt, to make your large Visual Studio solution more logical (look for the Guidelines for the Source\ projects section).

Making configuration modules reusable brings another question: Where do we put these classes?

There are two approaches:

  • Create a separate .dll project per module.
  • Keep modules in the assemblies that contain related components. It means that these assemblies would have to reference Autofac.dll and potentially other 3rd party frameworks.

Choice depends on the development habits, but I prefer the second one for the simplicity sake.

It also may feel logical to move smaller (but still repetitive) configuration blocks for 3rd party libraries into a reusable stack library as well: logging, container etc. That’s how it is done with Lokad.Stack.dll.

By the way, you can visualize all dependencies in your container, to explore and understand them better:

Exploring Autofac dependencies

I’ll talk more about this in the next articles in the series. Additionally I’ll walk over the actual implementation of our sample module with classes from the Lokad Shared Libraries. Stay tuned to the RSS feed for updates.

That’s it for the first article.

What do you think about it? If you have questions, comments or any other type of feedback, please don’t hesitate to drop a line into comments or a contact form.

Update: Article 2 - Logging with Lokad Shared Libraries for .NET