Phoenix.Functionality.Ioc.Autofac 2.3.0

dotnet add package Phoenix.Functionality.Ioc.Autofac --version 2.3.0
NuGet\Install-Package Phoenix.Functionality.Ioc.Autofac -Version 2.3.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Phoenix.Functionality.Ioc.Autofac" Version="2.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Phoenix.Functionality.Ioc.Autofac --version 2.3.0
#r "nuget: Phoenix.Functionality.Ioc.Autofac, 2.3.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Phoenix.Functionality.Ioc.Autofac as a Cake Addin
#addin nuget:?package=Phoenix.Functionality.Ioc.Autofac&version=2.3.0

// Install Phoenix.Functionality.Ioc.Autofac as a Cake Tool
#tool nuget:?package=Phoenix.Functionality.Ioc.Autofac&version=2.3.0

Phoenix.Functionality.Ioc

This is a collection of helper assemblies for the inversion of control principle.


Table of content

[toc]


Phoenix.Functionality.Ioc.Autofac

.NET Framework .NET Standard .NET
✔️ 2.0 ✔️ 5.0 ✔️ 6.0

This assembly is centered around the dependency injection framework Autofac.

Scope Handling

Scope Building (NestedScopeBuilder)

The NestedScopeBuilder allows to build nested Autofac.ILifetimeScopes. Each of those is build from a single named group consisting of multiple Autofac.Core.IModules. Its main purpose is to help setting up a complex chain of nested Autofac.ILifetimeScopes within an applications bootstrapper or loader class.

Usage

First an instance of a NestedScopeBuilder has to be created.

  • Without any dependencies

      var builder = new NestedScopeBuilder();
    
  • With a collection of IModules that are used to build the initial ILifetimeScope

      class MyModule : Autofac.Module {}
      var builder = new NestedScopeBuilder(new MyModule());
    
  • With an already existing ILifetimeScope

      var initialContainer = new ContainerBuilder().Build();
      var builder = new NestedScopeBuilder(initialContainer);
    

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #37ff00; background-color: #37ff0020' > <span style='margin-left:1em; text-align:left'> <b>Information</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> The instance could also be created by an <b>ILifetimeScope</b> that was setup during application startup whose only purpose is to provide the most basic services like settings, the main application window and of course this <i>NestedScopeBuilder</i>, that will be responsible for setting up all other scopes that the application needs. The constructor accepting an already existing scope should be used in this case. </div> </div> After the NestedScopeBuilder has been created, additional scopes can be setup. During setup, each scope is identified by a custom name, that allows to combine modules together that later should compose a single Autofac.ILifetimeScope. In the below example the DatabaseModule is added to a group name Services and the other two modules are added to a group name Application. During build two ILifetimeScopes would be created, where the one from the Application group would be a child scope to the Services one. This guarantees, that the Application scope can access all services from its parent scopes.

class DatabaseModule : Autofac.Module {}
class MyModule : Autofac.Module {}
class YourModule : Autofac.Module {}
builder.AddModule<DatabaseModule>().ToGroup("Services");
builder.AddModules(new MyModule(), new YourModule()).ToGroup("Application");

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #37ff00; background-color: #37ff0020' > <span style='margin-left:1em; text-align:left'> <b>Information</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> The order of how the groups are created is also the order of how the different <b>ILifetimeScope</b>s are nested during build. Adding a new <b>IModule</b> to an existing group after other groups have already been created does not change the order of the group. </div> </div>

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #37ff00; background-color: #37ff0020' > <span style='margin-left:1em; text-align:left'> <b>Information</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> When adding the same <b>IModule</b> multiple times, it will only be added, if its new group is a predecessor to its current one. This guarantees, that services of a module are always resolveable by any neseted scope. </div> </div>

The final step is to build all nested scopes. The return value will be the innermost ILifetimeScope that has access to all registered services.

var finalScope = await builder.BuildAsync();

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #37ff00; background-color: #37ff0020' > <span style='margin-left:1em; text-align:left'> <b>Information</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> Every time a new <b>ILifetimeScope</b> is created by a <i>NestedScopeBuilder</i> instance, all available modules are flushed and the created scope replaces the inital one. It is therefore possible to get intermediate scopes during setup and reuse the <i>NestedScopeBuilder</i>. </div> </div>

Scope Verification

The extension method ILifetimeScope.ExecuteVerificationMethodsAsync can be used together with functions that have been registered as IocScopeVerificationDelegate to verify any given scope with custom logic. Below example shows how an Autofac.Core.IModule could be written, to easily provide such an verification function.

First the module registers a service that provides database connections for other services within an application. The second registration is the verification function that checks the database connection.

class DatabaseModule : Autofac.Module
{
	protected override void Load(ContainerBuilder builder)
	{
		// Register the database service.
		builder.RegisterType<DatabaseContext>().As<IDatabaseContext>();

		// Register VerifyConnectivityAsync as verification method.
		builder.Register
			(
				context =>
				{
					var contextFactory = context.Resolve<IDatabaseContext.Factory>();
					IocScopeVerificationDelegate verificationCallback = token
						=> DatabaseModule.VerifyConnectivityAsync(contextFactory, token);
					return verificationCallback;
				}
			)
			.As<IocScopeVerificationDelegate>()
			.SingleInstance()
			;
	}

	internal static async IAsyncEnumerable<IocScopeVerificationResult> VerifyConnectivityAsync
	(
		IDatabaseContext.Factory contextFactory,
		[EnumeratorCancellation] CancellationToken cancellationToken = default
	)
	{
		yield return "Checking database availability";
		do
		{
			using var context = contextFactory.Invoke();
			var (isConnected, exception) = await context.CheckConnectionAsync(cancellationToken).ConfigureAwait(true);
			if (isConnected)
			{
				yield return "Connection to database has been established.";
				break;
			}
			else if (cancellationToken.IsCancellationRequested)
			{
				break;
			}
			else
			{
				yield return new IocScopeVerificationResult
				(
					"Connection to database couldn't be established. Trying again...",
					exception
				);
				try
				{
					await Task.Delay(2000, cancellationToken);
				}
				catch (OperationCanceledException) { /* ignore */ }
			}

		}
		while (!cancellationToken.IsCancellationRequested);
	}
}

At some point during application startup it would be a good idea to validate, if a connection to the database can be established. This is where the second registration of the IocScopeVerificationDelegate comes into play. The ILifetimeScope.ExecuteVerificationMethodsAsync extension method resolves all registered IocScopeVerificationDelegates and executes them. The implemented verification functions then asynchronously return IocScopeVerificationResults, that can be used for logging purposes or even be displayed as part of an application loading dialog.

var builder = new ContainerBuilder();
builder.RegisterModule<DatabaseModule>();
var container = builder.Build();
try
{
	var asyncEnumerable = container.ExecuteVerificationMethodsAsync();
	await foreach (var result in asyncEnumerable)
	{
		/* Log the messages. */
	}
}
catch (OperationCanceledException)
{
	/* Handle cancellation. */

}
catch (IocScopeVerificationException ex)
{
	/* Handle verification exception. */
}

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #37ff00; background-color: #37ff0020' > <span style='margin-left:1em; text-align:left'> <b>Information</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> Executing <i>ExecuteVerificationMethodsAsync</i> will only ever throw either an <i>OperationCanceledException</i> or an <i>IocScopeVerificationException</i>. If a verification method internally threw something else, this exception will be wrapped within the <i>IocScopeVerificationException</i> as inner exception. </div> </div>

Helpers

TypeList{T}

The TypeList{T} is a special collection that can be used to resolve the types of registered services (as opposed to their instances) from an IContainer. This can be useful in cases where only the types are required to get further information (like Attributes) about them.

To get this working the TypeListSource<T> (an IRegistrationSource) must be added to the ContainerBuilder where the services are registered.

interface ISomething { }
class Anything : ISomething { }
class Everything : ISomething { }

var builder = new ContainerBuilder();
builder.RegisterSource<TypeListSource<ISomething>>();
builder.RegisterType<Anything>().As<ISomething>();
builder.RegisterType<Everything>().As<ISomething>();
var container = builder.Build();

// Get a collection of the registered ISomething types.
var types = container.Resolve<TypeList<ISomething>>();

TypeAndFactoryList{T}

The TypeAndFactoryList{T} is a special collection that can be used to resolve the types of registered services (as opposed to their instances) alongside a factory for actually creating instances of the type from an IContainer. This can be useful in cases where only the types are required to get further information (like Attributes) about them and then based on those create actual instances.

To get this working the TypeAndFactoryListSource<T> (an IRegistrationSource) must be added to the ContainerBuilder where the services are registered.

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #ff0000; background-color: #ff000020' > <span style='margin-left:1em; text-align:left'> <b>Registration as self</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> It is mandatory to additionally register the services <i>AsSelf()</i>, so that individual instances can be resolved from <b>Autofac</b> when invoking the fatory methods. Not doing this will result in a <b>ComponentNotRegisteredException</b> being thrown when invoking the factory method. </div> </div>

interface ISomething { }
class Anything : ISomething { }
class Everything : ISomething { }

var builder = new ContainerBuilder();
builder.RegisterSource<TypeAndFactoryListSource<ISomething>>();
builder.RegisterType<Anything>().AsSelf().As<ISomething>(); // Don't forget to register as self.
builder.RegisterType<Everything>().AsSelf().As<ISomething>(); // Don't forget to register as self.
var container = builder.Build();

// Get a collection of the registered ISomething types.
var types = container.Resolve<TypeAndFactoryList<ISomething>>();

RegisterFactory

RegisterFactory is an extension method for a ContainerBuilder, that allows to register the return type of a delegate factory. This function is only a wrapper and could be replaced by simply using builder.RegisterType(returnType).AsSelf() instead. Its sole purpose is therefore providing a way to identify registrations that are later only used to resolve factories.

Below shows the typical approach of registering a specific type and letting Autofac automatically register the delegate factory in the background, thus concealing dependencies.

var builder = new ContainerBuilder();
// Only register the type containing a delegate factory:
builder.RegisterType<ClassWithDelegateFactory>().AsSelf();
var container = builder.Build();
// Why the delegate factory can be resolved is not immediately clear by the code alone and requires more detailed knowledge about Autofac.
var factory = container.Resolve<ClassWithDelegateFactory.Factory>();

The RegisterFactory extension method makes the intentions more clear and provides a better way to track registrations.

var builder = new ContainerBuilder();
// Register the delegate factory directly.
builder.RegisterFactory<ClassWithDelegateFactory.Factory>();
var container = builder.Build();
// Now resolving it should be more intuitive.
var factory = container.Resolve<ClassWithDelegateFactory.Factory>();

InternalConstructorFinder

The InternalConstructorFinder can be used when registering services that may provide only internal constructors. Normally this would throw a NoConstructorsFoundException by Autofac when the container is build. This is typical necessary when keeping the access level to a bare minimum.

To use the InternalConstructorFinder best apply the FindInternalConstructors extension method during registration.

class ClassWithInternalConstructor
{
    internal ClassWithInternalConstructor() { }
}

var builder = new ContainerBuilder();
builder.RegisterType<ClassWithInternalConstructor>().AsSelf().FindInternalConstructors();
var container = builder.Build();

// Get an instance.
var instance = container.Resolve<ClassWithInternalConstructor>();

Authors

  • Felix Leistner: v1.x - v2.x
Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.3.0 286 9/8/2023
2.2.0 236 8/15/2023
2.1.0 160 8/15/2023
2.0.0 147 8/15/2023
1.0.0 150 8/15/2023