-
Notifications
You must be signed in to change notification settings - Fork 4
Activators
Activators defines some contracts for creating new object instances 'not by new operator'.
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();
}
}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:
- Define service lifetime (more in section 'Lifetimes and scopes')
- Register services, which doesn't make sense in the root scope (for e.g.: user/request specific services).
Typical usage
- At composition root, root dependency container is created and global services are registered. From this point, only
IDependencyProvideris used (due toIDependencyContainerextendsIDependencyProvider), so no one can change behavior of root container. - At some time (for e.g. on start every HTTP request), we use the root dependency provider and calls it's
Scopemethod to create new instance of dependency container. This container will be based on the root provider. - We do the user(/request)-specific services registration into the newly created scope.
- We use the newly created scope as
IDependencyProviderfor 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 ofrequiredTypeby this factory. But lifetime is managed by the container, so factory is called only when we need to create instance. -
Type, type which extendsrequiredTypeand is passed to the underlaying unity container to create instance of. -
object, instance ofrequiredType. 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:
-
IHelloServicehas single instance in every scope and instance created from implementation typeHiService. -
IMessageWriterhas 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). -
Presenterhas instance for every resolution and is created from 'self' (mapping to self type).