Dependency injection
Applications built with the DEM rely on dependency injection provided by a container. The library provides assemblies that work with the Autofac and it allows you to use other dependency injection containers.
Dependency injection containers reduce the dependency coupling between objects by providing a facility to instantiate instances of classes and manage their lifetime based on the configuration of the container. During the objects creation, the container injects any dependencies that the object requires into it. If those dependencies have not yet been created, the container creates and resolves their dependencies first. In some cases, the container itself is resolved as a dependency. For example, when using the Autofac as the container, modules have the container injected, so they can register their views and services with that container.
There are several advantages of using a container:
A container removes the need for a component to locate its dependencies or manage their lifetimes.
A container allows swapping of implemented dependencies without affecting the component.
A container facilitates testability by allowing dependencies to be mocked.
A container increases maintainability by allowing new components to be easily added to the system.
In the context of an application based on the DEM, there are specific advantages to a container:
A container injects module dependencies into the module when it is loaded.
A container is used for registering and resolving view models and application services.
A container can create the view models and injects the view.
A container injects the composition services, such as the api client and/or configutation.
A container is used for registering module-specific parts, which are services that have module-specific functionality, view models, widgets, translations, configuration etc.
Containers are used for two primary purposes, namely registering and resolving.
Registering
Before you can inject dependencies into an object, the types of the dependencies need to be registered with the container. Registering a type typically involves passing the container an interface and a concrete type that implements that interface. There are primarily two means for registering types and objects: through code or through configuration.
Typically, there are two ways of registering types and objects in the container through code:
You can register a type or a mapping with the container. At the appropriate time, the container will build an instance of the type you specify.
You can register a type as singleton. At the frist call, the container will build an instance in the container as a singleton. Next time container will return a reference to the existing object.
And you can register an existing object instance in the container as a singleton. The container will return a reference to the existing object.
Part of the application initializing process is to configure this container and register types with the container.
protected override void RegisterTypes(ITypeCatalog typeCatalog)
{
typeCatalog.Add(
new AppServiceInfo<NewService, INewService>() { IsSingleInstance = true });
typeCatalog.AddOrUpdate(
new AppServiceInfo<ApplicationStateOverride, IApplicationState>());
typeCatalog.AddOrUpdate(
new ViewModelInfo(typeof(Customized.AccountDetailsViewModelOverride))
{
PageTypes = new Type[] { typeof(Customized.AccountDetailsPage) }
}
);
typeCatalog.AddOrUpdate(
new ViewModelInfo(typeof(SampleViewModel))
{
DisplayData = new ViewModelDisplayData(20, "Sample", AllIcons.OutgoingCardTransaction),
ViewType = ViewType.Both
}
);
In this way, all the elements in the application can be accessed eg. we can inherit the existing implementation, change the behavior, and then register as a replacement for the given interface or create a completely new one that changes the behavior of the application.
Registration allows their dependencies to be provided through the container and allows them to be accessed from other types.
public class ModuleRegion : ModuleBase
{
public override IEnumerable<ITypeInfo> RegisterServices()
{
return base.RegisterServices();
}
public override IEnumerable<ITypeInfo> RegisterUsecases()
{
return base.RegisterUsecases();
}
public override IEnumerable<ITypeInfo> RegisterWidgets()
{
return base.RegisterWidgets();
}
}
Liftime
You should also consider whether a component's lifetime should be managed by the container. When you register a type the default behavior for the DI container is to create a new instance of the registered type each time the type is resolved or when the dependency mechanism injects instances into other classes. When you register an instance the default behavior for the DI container is to manage the lifetime of the object as a singleton. This means that the instance remains in scope as long as the container is in scope, and it is disposed when the container goes out of scope and is garbage-collected or when code explicitly disposes the container. If you want this singleton behavior for an object that DI creates when you register types, you must explicitly specify the IsSinglton property when registering the type.
Resolving
After a type is registered, it can be resolved or injected as a dependency. When a type is being resolved, and the container needs to create a new instance, it injects the dependencies into these instances.
In general, when a type is resolved, one of three things happens:
If the type has not been registered, the container throws an exception.
If the type has been registered as a singleton, the container returns the singleton instance. If this is the first time the type was called for, the container creates it and holds on to it for future calls.
If the type has not been registered as a singleton, the container returns a new instance.
The AccountDetailsViewModelOverride constructor contains the following dependencies (INewService), which are injected when it is resolved.
public class AccountDetailsViewModelOverride : AccountDetailsViewModel
{
public AccountDetailsViewModelOverride(INewService service)
{
service.Do();
Optional
public class AccountDetailsViewModelOverride : AccountDetailsViewModel
{
public AccountDetailsViewModelOverride(INewService service = null)
{
service?.Do();
Resolving with Application
Instead of requesting a concrete type, the code could request an instance of an interface.
var newService = App.ResolveService<INewService>();
Note
Use this only when service is optional and used in functions
Dependency injection containers, often referred to as just "containers," are used to satisfy dependencies between components; satisfying these dependencies typically involves registration and resolution.
The DEM provides support for the Autofac container, but it is not container-specific. Because the library accesses the container through the IContainerProvider, interface, the container can be replaced.
public interface IContainerProvider
{
T ResolveService<T>()
where T : IApplicationService;
T ResolveServiceAs<T>(Type type);
IEnumerable<T> ResolveListOfServices<T>()
where T : IApplicationService;
T ResolveViewModel<T>(Type viewModelType)
where T : ViewModel;
}
There are several advantages to using a dependency injection container. First, a container removes the need for a component to locate its dependencies and manage their lifetime. Second, a container allows mapping of implemented dependencies without affecting the component. Third, a container facilitates testability by allowing dependencies to be mocked. Forth, a container increases maintainability by allowing new components to be easily added to the system.
In DEM there are specific advantages to a dependency injection container. A container is used for registering and resolving view models and views. In addition, a container is used for registering application services, and injecting them into view models.
Application services
Application Services (AS) encapsulates common functionality to modules and view-models loaded into the client application. AS are used to expose infrastructure or domain logic to the presentation layer. These services provide a way to encapsulate horizontal concerns and package up specific implementations into re-usable and configurable assets.
In DEM, an application service should implement the IApplicationService interface.
namespace AssecoSEE.DEM.Core
{
public interface IApplicationService
{
}
}
Containers play a key role in an application created with the DEM. Both the DEM and the applications built on top of it depend on a container for injecting required dependencies and services.
During the container configuration phase, several core services are registered. In addition to these core services, you may have application-specific services that provide additional functionality as it relates to composition.
Core Services
The following table lists the core non-application specific services in the DEM.
Service interface | Description |
---|---|
IApiClient | Provides a base service for sending HTTP requests and receiving HTTP responses from a DE HUB API identified by a part of URI. |
IDisplaySettingsProvider | |
IAbstractValidator | |
IACrypto | |
IApplicationProvider | |
IApplicationService | |
IApplicationState | |
ICache | |
IConfigurationBase | |
IConnectivity | |
IConstants | |
IContainerProvider | |
IDebugService | |
IDeveloperOptions | |
IDeviceContactService | |
IDeviceData | |
IDeviceService | |
IFileLauncher | |
IFingerprintService | |
IInactivityService | |
IInitialConfigurationProvider | |
IInitTranslationProvider | |
IKeyChain | |
IListViewSettings | |
IMTokenWrapper | |
IPermissionsService | |
IPropertyValidator | |
IProtectedCache | |
IPushNotificationSubscriber | |
IStartupManager | |
IStayAwake | |
ISyncService | |
ITheme | |
ITokenManager | |
ITracker | |
ITranslation | |
ITranslationProvider | |
IUniqueDeviceIdentifierFactory | |
IUserSettingsProvider | |
IViewModelLocationProvider |
Application-Specific Services
The following list represent lists of the application-specific services used in the solution.
Service interface | Description |
---|---|
IAuthenticationService | |
IAccountNumberFormatter | |
IAccountService | |
IBeneficiaryService | |
IClientContactService | |
IConvertTemplatesToPaymentDataObject | |
IConvertTransferObjectToApiClientModelService | |
IExchangeRateService | |
IIBANValidator | |
IInstantPaymentWrapper | |
INavigationService | |
IPaymentService | |
ISessionState | |
ITransactionService |
Note
DEM uses AutoFac containers with explicit registration. In these cases, the registration typically occurs during module loading when DEM invokes the 'IModule.RegisterServices' method. See Modular Application Development for more information.
Lifetime of an Application Service
All application service instances are Transient. This means they are instantiated each time. But you can register application service as a singleton.
For more information see
For more information about platform specific services see Using the DependencyService with DEM
For more information about using services to share data see Communicating between loosely coupled components
For more information about registering and resolving in module see Creating a new customer module using DEM
Other resurces