Skip to content
Marek Fišera edited this page Aug 7, 2015 · 8 revisions

Activators defines some contracts for creating new object instances 'not by new operator'.

IFactory

Interface IFactory<T> is a generic context-free contract for creating objects of type T. For e.g.:

public class ProductRepositoryFactory : IFactory<IProductRepository> 
{
    private readonly bool useDbRepository;

    public class ProductRepositoryFactory(bool useDbRepository)
    {
        this.useDbRepository = useDbRepository;
    }

    public IProductRepository Create() 
    {
        if (useDbRepository)
            return new SqlProductRepository();

        return new FileProductRepository();
    }
}

Beside this simple factory contract there is also 'with-context' contract defined as IFactory<T, TContext> whose Create method takes object type TContext as parameter. For e.g.:

public class ProductRepositoryHttpContextFactory : IFactory<IProductRepository, HttpContext> 
{
    public IProductRepository Create(HttpContext httpContext) 
    {
         //TODO: Provide instance of IProductRepository.
         throw new NotImplementedException();
    }
}

IDependencyProvider & IDependencyContainer

These defines contracts for service location. IDependencyProvider has method Resolve, which takes type to resolve instance of, so IDependencyProvider is simple service locator and is read-only - no one can change resolving policy. On the other hand, IDependencyContainer extends IDependencyProvider by adding methods for mapping types. Beside providing instances, IDependencyProvider has one more ability. This ability is to create new scope of IDependencyContainer. Scopes are designed for two purposes:

  1. Define service lifetime (more in section 'Lifetimes and scopes')
  2. Register services, which doesn't make sense in the root scope (for e.g.: user/request specific services).

Typical usage

  1. At composition root, root dependency container is created and global services are registered. From this point, only IDependencyProvider is used (due to IDependencyContainer extends IDependencyProvider), so no one can change behavior of root container.
  2. At some time (for e.g. on start every HTTP request), we use the root dependency provider and calls it's Scope method to create new instance of dependency container. This container will be based on the root provider.
  3. We do the user(/request)-specific services registration into the newly created scope.
  4. We use the newly created scope as IDependencyProvider for resolving services inside HTTP request.

Lifetimes and scopes

Lifetimes of services inside dependency container are based on scopes used in the application. Every scope takes it's name.

  • In the composition root, we typically create Root scope.
  • When HTTP request starts, scope named Request is typically created.
  • When view processing starts (from Request scope), scope named ViewProcessing can be created.

And services can be bound to these scopes, so we can for e.g. register service to have one instance for every scope Request. This service can't be resolved from Root scope and will have one instance in Request and this instance will be shared in all ViewProcessing scopes created from this Request scope.

Beside this binding to the concretely named scope, other supported lifetimes are

  • Transient, new instance for every resolve.
  • AnyScope, single instance in every scope (independently of the name).

Registering services

For creating type mappings, IDependencyContainer contains collection of type IDependencyDefinitionCollection which defines method for creating mapping by passing takes three parameters:

  • requiredType (Type), type we want to map
  • lifetime (DependencyLifetime), lifetime we want to bind instances in.
  • target (object), resolution target.

The third parameter is little bit complicated and possible values for it are based on implementation. The design goal for defining this parameter as object is that it can be uses full to provide ability to register target object in different ways. In Unity implementation of dependency container, there are these supported targets:

  • IFactory<object>, create instances of requiredType by this factory. But lifetime is managed by the container, so factory is called only when we need to create instance.
  • Type, type which extends requiredType and is passed to the underlaying unity container to create instance of.
  • object, instance of requiredType. Using this registration, you create singletons (from container, where the instance is registered).

All this is hidden by extension methods, so typical registrations looks this way:

IDependencyContainer dependencyContainer = CreateDependencyContainer();

dependencyContainer.Definitions
    .AddScoped<IHelloService, HiService>()
    .AddScopedFactory<IMessageWriter>("Request", new ConsoleWriterFactory())
    .AddTransient<Presenter>();

From the example:

  • IHelloService has single instance in every scope and instance created from implementation type HiService.
  • IMessageWriter has single instance in scope named Request and this instance is shared for all scopes created from this scope ('Single instance per HTTP request', if scope 'Request' is created when HTTP request processing starts).
  • Presenter has instance for every resolution and is created from 'self' (mapping to self type).

Clone this wiki locally