Skip to content
Hank McCord edited this page Oct 5, 2019 · 24 revisions

Compile time configuration (assembly attributes)

For AutoDI 3.x configuration see legacy configuration

Run time configuration ([SetupMethodAttribute])

Compile time configuration

The following configurations are all specified with assembly attributes. These configurations are processed in order, so if there are rules that match the same type, the last one wins.

Generate Debuggable Code (v3.3.0+)

Default: None

[assembly:AutoDI.Settings(DebugCodeGeneration = AutoDI.CodeLanguage.CSharp)]

AutoDI can generate debuggable code to allow you to see the dependency parameters get resolved. This feature is off by default. It will also generate reference code for the initialization code. The code that it generates should be considered reference code. This code was not compiled to produce the final output, rather it is a reflection of the IL that was generated.

Generate Registrations

Default: True

[assembly:AutoDI.Settings(GenerateRegistrations = false)]

When this is true, AutoDI will construct a mapping of all types and build up all of the dependency registrations. These registrations are added at run time when AutoDI is initialized.

Included assemblies

Default: none

[assembly:AutoDI.IncludeAssembly("MyCompany.*")]

AutoDI will always scan the assembly being processed (ie. the assembly with the AutoDI.Build NuGet package installed) when building up the list of types to register. If there are additional dependent assemblies that AutoDI should also include you can specify them with an IncludeAssembly assembly attribute. The constructor parameter (assemblyPattern) follows the same matching rules at the Map (below) to match the assembly's full name.

Custom mapping

Default: none

[assembly:AutoDI.Map(".I*", ".*", Lifetime.Singleton)]
[assembly:AutoDI.Map(@"regex:Interfaces\.I(.*)", @"regex:Services\.$1", Force = false)]

If you need to configure a specific mapping between types you can specify it with an [AutoDI.MapAttribute] specifying the SourceTypePattern and TargetTypePattern. Both of these may contain a wildcard (*). In the first example [assembly:AutoDI.Map(".I*", ".*", Lifetime.Singleton)] maps all types that start with an 'I' to their corresponding type without the 'I' prefix. Note that the . in this example are being used to match a namespace delimiter and are not regular expressions. A wildcard in the TargetTypePattern will be replaced with the matched wildcard value in the SourceTypePattern attribute.

The SourceTypePattern and TargetTypePattern can also contain regular expressions to allow for more complex matching scenarios. For these, prefix the value with "regex:" (as seen above). The pattern in the TargetTypePattern attribute may contain regex groups that were matched in the SourceTypePattern attribute. This allows for some more complex mappings to be declared.

Maps may also specify a Lifetime to use for all of the mappings that are generated from it. This lifetime may be overridden by subsequent maps. Mapping that do not have an explicit lifetime will use LazySingleton.

AutoDI will also verify that the target type (specified in the TargetTypePattern attribute) can be cast to the service type (specified in the SourceTypePattern attribute). If the service type is not in the target type's hierarchy it will ignore the mapping. You can disable this check by setting the Force property to true (default is false).

When matching on nested types you may use either '/' or '+' to denote a nested class. [assembly:AutoDI.Map("Container+*", "Container/*")]

Object lifetime

Default: none

[assembly:AutoDI.Map(@"regex:Namespace\..+Service", Lifetime.Transient)]

Unless otherwise specified, the generated registrations by AutoDI have a lifetime of LazySingleton (see below). You can change the lifetime used for generated registration with the MapAttribute. The TargetTypePattern is processed using the same matching rules as above. All generated registrations whose target type's full name matches this pattern will use the specified lifetime.

AutoDI supports the following lifetimes:

  • LazySingleton (Default) - A singleton instance. The object is not created until the first time it is requested.
  • Singleton - A singleton instance. The object is created when AutoDI is instantiated.
  • Scoped - A unique lazy singleton instances is created in each service scope.
  • WeakSingleton - A single instance is created and returned. The container holds a weak reference to the instance and will return the same instance as long as it is alive; otherwise a new instance is created. In version 3.0.0 this was named WeakTransient (#61)
  • Transient - A new instance is created for each request.
  • None - Used to un-register a type that may have been previously registered.

You can also remove types that were previously mapped by simply setting the Lifetime attribute to None. The following removes all types within the Exclude namespace.

[assembly:AutoDI.Map("Exclude.*", Lifetime.None)]

Behaviors

Default: Behaviors.Default

[assembly:AutoDI.Settings(Behavior = AutoDI.Behaviors.SingleInterfaceImplementation | AutoDI.Behaviors.IncludeDependentAutoDIAssemblies)]

Having to declare every registration would be incredibly time consuming. To support many of the common scenarios AutoDI has several built in behaviors. Often it is simplest to let the behaviors do most of the mapping, and then fine tune the results with the MapAttribute.

Behavior list

  • None - All behaviors are turned off.
  • Default - All behaviors are turned on.
  • SingleInterfaceImplementation - All classes that are the sole implementation of an interface are automatically registered with the interface as the service type. Mappings created by this behavior have a default lifetime of LazySingleton.
public interface IService { ... }
public class Service : IService { ... }

All dependencies of IService will be automatically resolved with a Service instance.

  • IncludeClasses - All classes are automatically registered with their own class type as the service type. This allows for dependencies to be requested by their concrete type. Abstract classes are ignored. Mappings created by this behavior have a default lifetime of Transient.
  • IncludeBaseClasses - All classes are automatically registered with their base classes as the service types as long as they are the sole derived classes, and the class is not abstract. Mappings created by this behavior have a default lifetime of Transient.
  • IncludeDependentAutoDIAssemblies - By default only the main assembly is scanned when selecting types to register. Enabling this behavior will also scan any referenced assembly that references AutoDI. This can be useful for automatically picking up all of the assemblies that are also using AutoDI.

Initialization Mode

Default: InitMode.EntryPoint

[assembly:AutoDI.Settings(InitMode = InitMode.EntryPoint)]

Be default AutoDI will attempt to locate the assemblies entry point method (the main method) and inject a call to its initialize method there. You can disable this behavior by setting InitMode=InitMode.None. Disabling this behavior will require you to manually invoke AutoDI.DI.Init(...) to initialize the AutoDI framework.

When producing libraries you expect to be consumed by others, it is recommended to use the module load initialization option (InitMode=InitMode.ModuleLoad) so the consumer doesn't need to worry about manually initializing the AutoDI framework. AutoDI will detect the assembly's main module and inject the initialize method call in the module's static constructor.

For Xamarin.Android projects you will need to manually inject the container since the assemblies do not contain a single entry point.

Debug Log Level

Default: DebugLogLevel.Default

[assembly:AutoDI.Settings(DebugLogLevel = DebugLogLevel.Verbose)]

In the Build section of the Output window you can see the registrations that AutoDI is generating along with some basic output. If you are running into issues or simply want to see more details you can adjust the logging. The valid values are None, Default, or Verbose.

This setting does not affect any warnings or errors raised by AutoDI.

Debug Exceptions

Default: False

[assembly:AutoDI.Settings(DebugExceptions = true)]

Allows for debugging thrown exceptions in the generated <AutoDI>.AddServices method. The exceptions are caught, and wrapped into an AggregateException.

Constructor selection

By default AutoDI will invoke the constructor with the most arguments. However, there may be cases where you want to explicitly specify which constructor to use. In these cases you can simply decorate the constructor with the AutoDI.DiConstructorAttribute.

public class MyClass
{
    public MyClass(string name, int id) { ... }

    [AutoDI.DiConstructor]
    public MyClass(IService service) { ...}
}

Property Injection

AutoDI can also inject dependencies into properties. For a property to be resolved it must meet the following criteria.

  • Decorated with [AutoDI.DependencyAttribute]
  • Must have a setter or be an auto-property

The property values are set inside of the constructor (just like constructor parameters). This allows the properties to have any protection level, from public to private. Properties that only specify a getter method with a body are ignored.

Modifying the container at run-time

For something things it may be necessary to configure the AutoDI framework at run-time. To do this simply declare a method and decorate it with the AutoDI.SetupMethodAttribute.

[AutoDI.SetupMethod]
public static void Initialize(AutoDI.IApplicationBuilder application)
{
    //Any needed run-time configuration here
}

The setup method must meet the following criteria:

  • Must be static
  • Must be public or internal
  • Must take in a single AutoDI.IApplicationBuilder parameter
  • Must be in the main module of the assembly being processed.

You can see examples of using the run-time configuration in some of the example projects.

Registering services

You can register additional services using the IApplicationBuilder.ConfigureServices method.

[AutoDI.SetupMethod]
public static void Initialize(AutoDI.IApplicationBuilder application)
{
    application.ConfigureServices(services => services.AddAutoDILazySingleton<IService, Service>());
    //you can also use the standard extension methods provided by the  Microsoft.Extensions.DependencyInjection.Abstractions assembly, but this will limit you to object lifetimes supported there.
}

Container configuration

AutoDI's container is exposed through the AutoDI.IContainer interface. Using the IApplicationBuilder.ConfigureContainer<AutoDI.IContainer> method. You can use this method to make container specific calls.

[AutoDI.SetupMethod]
public static void Initialize(AutoDI.IApplicationBuilder application)
{
    application.ConfigureContainer<AutoDI.IContainer>(container => {
        //Manipulate the container
    });
}

The IContainer also has an event TypeKeyNotFound. If a service cannot be resolved this event is raised. You can use this event as a run-time means of logging missing dependencies or dynamic resolution of missing types.

[AutoDI.SetupMethod]
public static void Initialize(AutoDI.IApplicationBuilder application)
{
    application.ConfigureContainer<AutoDI.IContainer>(container => {
        container.TypeKeyNotFound += (_, e) => {
            e.Instance = ResolveInstance(e.ServiceKey);
        };
    });
}

private static object ResolveInstance(Type serviceKey)
{
    //Return appropriate instance
}

If you are using a different dependency injection framework you can make a similar call to interact directly with that framework's container. The following is an example using StructureMap as the backing dependency injection framework.

[AutoDI.SetupMethod]
public static void Initialize(AutoDI.IApplicationBuilder application)
{
    //This method provided by the StructureMap.AutoDI project in the Examples.
    application.UseStructureMap(); 
    application.ConfigureContinaer<Registry>(registry =>
    {
        Do any SM specific registration on the registry
    });
}
Clone this wiki locally