C# course

Lecture 16

IoC in .NET

What is Inversion of Control

Inversion of Control - is a common principle for writting low coupled code. Imversion control could be implemented via:

  • factory pattern
  • service locator pattern
  • depenency injection
  • strategy pattern
  • using events\delegates
  • using interfaces

IoC vs DI vs DIP

  • IoC (Inversion of Control) - most general term indicating idea of invoking client code from a framework
  • DI (Dependency Injection) - set of patterns to pass dependencies to a class
  • DIP - (Dependency Inversion Principle) - tells that class should depend on abstractions from the same or higher level
More info about the terms (ru)

Reasons to use IoC in your project

  • reduce coupling
  • remove direct dependencies between classes
  • force use abstractions instead of implementations
  • manage dependencies in external configuration
  • minimize effort on injecting other implementation
  • increaze testability of code

How does IoC works

Sample code without DI
Sample code with DI

Constructor injection

Pass the object of the defined class into the constructor of the dependent class for its entire lifetime

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
class Watcher {
    INofificationAction action = null;

    public Watcher(INofificationAction concreteAction) {
        this.action = concreteAction;
    }

    public void Notify(string message) {   
        action.ActOnNotification(message);
    }
}
1: 
2: 
3: 
var writer = new EventLogWriter();
var watcher = new Watcher(writer);
watcher.Notify("Sample message to log");

Method injection

To work in a method with different concrete class we have to pass the dependency in the method only

1: 
2: 
3: 
4: 
5: 
6: 
7: 
class Watcher
{
    public void Notify(INofificationAction concreteAction, string message)
    {
        action.ActOnNotification(message);
    }
}
1: 
2: 
3: 
EventLogWriter writer = new EventLogWriter();
var watcher = new Watcher();
watcher.Notify(writer, "Sample message to log");

Property injection

If the responsibility of selection of concrete class and invocation of method are in separate places we need property injection

1: 
2: 
3: 
4: 
5: 
6: 
7: 
class Watcher {
    public INofificationAction Action { get; set ; }    

    public void Notify(string message) {   
        action.ActOnNotification(message);
    }
}
1: 
2: 
3: 
4: 
5: 
6: 
var writer = new EventLogWriter();
var watcher = new Watcher();
// This can be done in some class
watcher.Action = writer;
// This can be done in some other class
watcher.Notify("Sample message to log");

IoC containers

IoC containers are used to:

  • automatically inject dependencies
  • manage lifecycle of dependencies
  • manage dependencies relationship
  • split creation dependencies and configuration relationships

DI cons

  • high learning curve
  • constructors may look complicated
  • code may seem "magic" for those who don't know DI
  • overkill for small projects
  • makes classes hard to use outside IoC container

IoC containers in .NET world

Comparison table

Another comparison table

IoC Battle

Autofac - overview

  • open source project
  • automates constructor, method and property injection
  • ligh-weight and fast enought
  • has lower learning curve comparing to other containers
  • could be configured either via code or xml configuration
  • available for all .NET technologies (WPF, ASP MVC, Web API, WinPhone 8, Win RT etc)
  • supports modules and automated type loading from an assembly
  • supports interceptors

Autofac homepage

Sample container - manual resolution

1: 
2: 
3: 
4: 
5: 
6: 
7: 
static void Main(string[] args)
{
  var consoleOutput = new ConsoleOutput();
  var writer = new TodayWriter(consoleOutput);
  
  writer.WriteDate();
}

Sample container - resulution with Autofac

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
static void Main(string[] args)
{
  // register types for DI
  var builder = new ContainerBuilder();
  // dependency
  builder.RegisterType<ConsoleOutput>().As<IOutput>();  
  // class to inject dependency
  builder.RegisterType<TodayWriter>().As<IDateWriter>();
  var container = builder.Build();
  // resolve types and use instances with injected objects
  using (var scope = Container.BeginLifetimeScope())
  {
    var writer = scope.Resolve<IDateWriter>();
    writer.WriteDate();
  }
}

Glossary

  • Container - manager of application Components
  • Component - class that declares a Service and dependencies it uses
  • Service - is a contract (interface) between Dependencies
  • Dependency - Service required by a Component
  • Registration - adding Component to Container
  • Scope - is context where Instance of a component will be shared by other Components

Registering and resolving components

Register by Type

Components generated by reflection are registered by type:

1: 
2: 
builder.RegisterType<ConsoleLogger>();
builder.RegisterType(typeof(ConfigReader));

Autofac automatically uses the matching constructor

1: 
2: 
3: 
4: 
5: 
public class MyComponent : IService {
    public MyComponent() { /* ... */ }
    public MyComponent(ILogger logger) { /* ... */ }
    public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ }
}
1: 
2: 
3: 
4: 
5: 
builder.RegisterType<MyComponent>();
builder.RegisterType<ConsoleLogger>().As<ILogger>();
var container = builder.Build();
//...
var component = container.Resolve<MyComponent>();

Register by type specifying constructor

You can manually choose a particular constructor to use and override the automatic choice:

1: 
2: 
builder.RegisterType<MyComponent>()
       .UsingConstructor(typeof(ILogger), typeof(IConfigReader));

Instance components

You can add pre-generate an instance of an object and add it to the container:

1: 
2: 
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

To avoid Autofac dispose the instance use:

1: 
2: 
3: 
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>()
       .ExternallyOwned();

Lambda expression components

Autofac can create a component using lambda expression:

1: 
2: 
builder.Register(c => new A(c.Resolve<B>()));
// parameter c is an component context of type IComponentContext

It is important to use component context rather than a closure to access the container

Lambda expression components - cases

Pass constant value to constructor:

1: 
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

Property Injection:

1: 
2: 
builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });
// ResolveOptional will try to resolve dependency but won't throw exception

Conditional creation:

1: 
2: 
3: 
4: 
5: 
builder.Register<CreditCard>((c, p) => {
      var accountId = p.Named<string>("accountId");
      var result = accountId.StartsWith("9") ? new GoldCard(accountId) : new StandardCard(accountId);
      return result;
    });
1: 
var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));

Services vs. Components

When registering components, Autofac should be specified with services that component exposes
By default, registration exposes itself as the type registered:

1: 
2: 
// This exposes the service CallLogger
builder.RegisterType<CallLogger>();

Components can only be resolved by the services they expose

1: 
2: 
3: 
4: 
5: 
// This will work because the component
// exposes the type by default:
scope.Resolve<CallLogger>();
// This will NOT work
scope.Resolve<ILogger>();

Exposing multiple services

Component can expose multiple services:

1: 
2: 
3: 
builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>();

Component can expose even itself as a service along with others:

1: 
2: 
3: 
4: 
builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>()
       .AsSelf();

Default registration

Autofac uses the last registered component as the default provider of that service:

1: 
2: 
3: 
4: 
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();
//...
scope.Resolve<ILogger>(); // FileLogger will be returned

To override this behavior, use the PreserveExistingDefaults() modifier:

1: 
2: 
3: 
4: 
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();
//...
scope.Resolve<ILogger>(); // ConsoleLogger will be returned

Scopes

The scope of a service is the area where that service can be shared with other components that consume it

Scopes in Autofac:

  • are nestable and they control how components are share
  • track disposable objects and dispose of them when the lifetime scope is disposed

It is important to always resolve services from a lifetime scope and not the root container

Lifetime options

Once registered, components can be configured with their lifetime

  • Instance Per Dependency - create new instance on each service request
  • Single Instance - aka Singleton
  • Instance Per LifeTime Scope - same instance in single scope
  • Instance Per Matching LifeTime Scope - singleton within the named scope

More about lifetime scopes

Modules

A module is a class that can be used to bundle up a set of related components behind a 'facade' to simplify configuration and deployment.

Modules:

  • Decreased Configuration Complexity
  • Configuration Parameters are Explicit
  • Abstraction from the Internal Application Architecture
  • Better Type Safety
  • Dynamic Configuration

Module example

Create module

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
public class CarTransportModule : Module {
  public bool ObeySpeedLimit { get; set; } 
  protected override void Load(ContainerBuilder builder) {
    builder.Register(c => new Car(c.Resolve<IDriver>())).As<IVehicle>();

    if (ObeySpeedLimit)
      builder.Register(c => new SaneDriver()).As<IDriver>();
    else
      builder.Register(c => new CrazyDriver()).As<IDriver>();
  }
}

Register module

1: 
2: 
3: 
builder.RegisterModule(new CarTransportModule() {
    ObeySpeedLimit = true
});

Autofac Integration

Autofac is integrated with following technologies:

  • ASP.NET
    • OWIN
    • MVC
    • Web API
    • SignalR
    • Web Forms
  • WCF
  • Managed Extensibility Framework
  • NHibernate
  • Moq

Full list