Tools.ContainerRegistration.Microsoft 0.2.1

dotnet add package Tools.ContainerRegistration.Microsoft --version 0.2.1
                    
NuGet\Install-Package Tools.ContainerRegistration.Microsoft -Version 0.2.1
                    
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="Tools.ContainerRegistration.Microsoft" Version="0.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tools.ContainerRegistration.Microsoft" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Tools.ContainerRegistration.Microsoft" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tools.ContainerRegistration.Microsoft --version 0.2.1
                    
#r "nuget: Tools.ContainerRegistration.Microsoft, 0.2.1"
                    
#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.
#:package Tools.ContainerRegistration.Microsoft@0.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tools.ContainerRegistration.Microsoft&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Tools.ContainerRegistration.Microsoft&version=0.2.1
                    
Install as a Cake Tool

Tools.ContainerRegistration

This project demonstrates how to register various services using Autofac or Microsoft DI, based on conventions and attributes. The registration configuration is governed by rules specified in ioc_config.json and custom attributes such as [Singleton], [Scoped], [ManualRegistration], and [ServiceRegistration].

Table of Contents

  1. Overview
  2. Attributes
  3. Service Factory
  4. Registration Rules
  5. Examples
  6. Generation output

Overview

The goal of this project is to manage service registration based on type conventions, interfaces, and custom attributes in BUILD TIME in opposite to registration provided as assembly sweep using reflection which is time consuming. The ioc_config.json file defines what types should be automatically registered, while the attributes fine-tune how specific types and interfaces should be treated.

Some services will be automatically registered based on naming conventions (such as ending with "Page" or "ViewModel"), while others require explicit attribute markings.

Not all namespaces are always resolved automatically, so the best approach to work with this tooling is to create GlobalUsings.cs and list all needed namespaces

global using Project.Mobile.Commands.Base.Interfaces;
global using Project.Mobile.Pages;
global using Project.Mobile.Pages.Base;
global using Project.Mobile.ViewModels;
global using Project.Mobile.ViewModels.Interfaces;

Attributes

Singleton

The [Singleton] attribute marks a class for registration as a singleton service. If the class is automatically registered based on naming conventions, this attribute ensures it is treated as a singleton regardless of other potential scope attributes.

Example:

[Singleton]
public class TestPage : ITestPage
{
    // This class will be registered as a singleton.
}

Scoped

The [Scoped] attribute indicates that a class should be registered with a scoped lifetime. However, if the [Singleton] attribute is also present, the singleton takes precedence.

Example:

[Scoped]
[Singleton(true)]
public class TestService : ITestService
{
    // Although it has both attributes, Singleton(true) ensures it is a singleton.
}

ManualRegistration

Classes with the [ManualRegistration] attribute are excluded from automatic registration. These services must be manually registered in the DI container.

Example:

[ManualRegistration]
public class TestUserPage : ITestUserPage
{
    // This class will not be registered automatically due to ManualRegistration.
}

ServiceRegistration

The [ServiceRegistration] attribute allows for manual control over which interfaces a class should be registered as. This is useful when a class implements multiple interfaces, but only some of them need to be registered.

Example:

[Singleton]
[ServiceRegistration([typeof(IBaseUserViewModel), typeof(IFriendsViewModel)])]
public class FriendlyUserViewModel : IBaseUserViewModel, IFriendsViewModel
{
    // Will be registered as IBaseUserViewModel and IFriendsViewModel, but not as FriendlyUserViewModel.
}

FactoryRegistration

The [FactoryRegistration] attribute is used to register a service via a factory method. This allows for custom service creation, where the factory method accepts a Type and a service provider (either IServiceProvider or IComponentContext for Autofac).

Can be applied to both interfaces and classes.

Example on interface:

[FactoryRegistration("Tools.ContainerRegistration.Sample.HelloWorldServiceFactory.CreateHelloWorldService")]
[Singleton(true)]
public interface IHelloWorldService
{
    void SayHello();
}

Example on class with interface forwarding:

[Singleton]
[FactoryRegistration("MyApp.Services.MobileLanguageBinderFactory.Create")]
[ServiceRegistration(typeof(ILanguageBinder))]
public class MobileLanguageBinder : LanguageBinder
{
    public MobileLanguageBinder(ITextProvider textProvider) : base(textProvider) { }
}

public static class MobileLanguageBinderFactory
{
    public static object Create(Type interfaceType, IServiceProvider provider)
    {
        var resx = new ResxTextProvider(AppResources.ResourceManager);
        resx.CurrentLanguage = Settings.CurrentCulture;
        return new MobileLanguageBinder(resx);
    }
}

When [FactoryRegistration] is used with [ServiceRegistration], the generator creates forwarding registrations for interfaces:

// Factory registration for concrete type
builder.AddSingleton(typeof(MobileLanguageBinder), provider => MobileLanguageBinderFactory.Create(...));
// Forwarding registration for interface
builder.AddSingleton<ILanguageBinder>(provider => provider.GetRequiredService<MobileLanguageBinder>());

OnActivated

The [OnActivated] attribute specifies a callback method to be invoked after the service is resolved from the container. This is useful for setting up locators, initializing services, or performing post-resolution configuration.

Can be applied multiple times to a single class.

Static method callback (Locator pattern):

[Singleton]
[OnActivated(typeof(LanguageBinderLocator), nameof(LanguageBinderLocator.SetImplementation))]
public class MobileLanguageBinder : LanguageBinder
{
    // After resolution, LanguageBinderLocator.SetImplementation(instance) will be called
}

Instance method callback:

[Singleton]
[OnActivated(nameof(Initialize))]
public class MyService : IMyService
{
    public void Initialize()
    {
        // This method is called after the service is resolved
    }
}

Combined with FactoryRegistration:

[Singleton]
[FactoryRegistration("MyApp.Services.MobileLanguageBinderFactory.Create")]
[OnActivated(typeof(LanguageBinderLocator), nameof(LanguageBinderLocator.SetImplementation))]
[ServiceRegistration(typeof(ILanguageBinder))]
public class MobileLanguageBinder : LanguageBinder
{
    public MobileLanguageBinder(ITextProvider textProvider) : base(textProvider) { }
}

This generates:

// In RegisterServices:
builder.AddSingleton(typeof(MobileLanguageBinder), provider => MobileLanguageBinderFactory.Create(...));
builder.AddSingleton<ILanguageBinder>(provider => provider.GetRequiredService<MobileLanguageBinder>());

// In AfterContainerBuilt:
var instance_MobileLanguageBinder = provider.GetRequiredService<MobileLanguageBinder>();
LanguageBinderLocator.SetImplementation(instance_MobileLanguageBinder);

InjectDependencies

The [InjectDependencies] attribute marks a class that requires dependency injection via the IInjector<T> pattern. This is useful when a class needs dependencies that cannot be resolved through constructor injection (e.g., circular dependencies, late-bound dependencies, or property injection patterns).

IMPORTANT: This attribute should typically be placed on base classes that implement IInjector<T>. The source generator will then detect the attribute and generate proper injection code for all derived classes.

Interface definition:

public interface IInjector<T>
{
    void Inject(T dependency);
}

Example on a base class (recommended pattern):

[InjectDependencies]
public abstract class GuardedAction : IGuardedAction, IInjector<IExceptionGuard>
{
    public IExceptionGuard ExceptionGuard { get; protected set; }

    void IInjector<IExceptionGuard>.Inject(IExceptionGuard service) => ExceptionGuard = service;

    public async Task ExecuteGuarded()
    {
        await ExceptionGuard.Guard(this, async () => await Execute());
    }

    protected abstract Task Execute();
}

// Derived class - automatically gets IExceptionGuard injected
public class MyAction : GuardedAction, IMyAction
{
    protected override Task Execute()
    {
        // ExceptionGuard is already available here
        return Task.CompletedTask;
    }
}

Example with multiple injected dependencies:

[InjectDependencies]
public abstract class AsyncGuardedCommandBuilder :
    IAsyncGuardedCommandBuilder,
    IInjector<IExceptionGuard>,
    IInjector<IMessenger>
{
    public IExceptionGuard ExceptionGuard { get; protected set; }
    public IMessenger Messenger { get; protected set; }

    void IInjector<IExceptionGuard>.Inject(IExceptionGuard service) => ExceptionGuard = service;
    void IInjector<IMessenger>.Inject(IMessenger service) => Messenger = service;
}

Generated code (Microsoft DI):

When a class (or its base class) has [InjectDependencies] and implements IInjector<T>, the generator creates a factory registration:

builder.AddTransient<IMyAction>(provider => {
    var instance = ActivatorUtilities.CreateInstance<MyAction>(provider);
    ((IInjector<IExceptionGuard>)instance).Inject(provider.GetRequiredService<IExceptionGuard>());
    return instance;
});
builder.AddTransient<MyAction>(provider => {
    var instance = ActivatorUtilities.CreateInstance<MyAction>(provider);
    ((IInjector<IExceptionGuard>)instance).Inject(provider.GetRequiredService<IExceptionGuard>());
    return instance;
});

Without [InjectDependencies]:

If the attribute is missing, the generator creates simple registrations without injection:

// This will cause NullReferenceException when ExceptionGuard is used!
builder.AddTransient<IMyAction, MyAction>();
builder.AddTransient<MyAction>();

Key points:

  • Place [InjectDependencies] on base classes that implement IInjector<T>
  • The generator scans the inheritance chain to detect the attribute
  • All IInjector<T> interfaces are detected and their Inject methods are called
  • Dependencies must be registered in the container before use

Registration Rules

Based on ioc_config.json

ioc_config.json file should have build action set to AdditionalFiles and exist in project root directory.

  • RegisterTypesEndingWith: Automatically registers types whose names end with specified suffixes, such as "Page" or "ViewModel".
  • ExcludedFromRegisteringTypesEndingWith: Excludes types from registration even if they match the suffix rules.
  • ExcludedFromRegisteringAsConventionInterface: Prevents automatic registration of a class as an interface based on convention.
  • RegisterAsSelf: If true service will be always registered as self (for example: TestPage will be registered as TestPage)
  • SplitGeneratedFiles: When true, splits generated code into multiple smaller files grouped by type suffix. This improves IDE performance (IntelliSense) for large projects. Default: false.

Split Generated Files

When SplitGeneratedFiles is enabled, the generator creates multiple smaller files instead of one large file:

{
  "RegisterTypesMatching": ["*Page", "*ViewModel", "*Action", "*CommandBuilder"],
  "SplitGeneratedFiles": true
}

This generates:

  • ServiceCollection_GeneratedServiceRegistration_Pages.g.cs - with RegisterPages(IServiceCollection builder)
  • ServiceCollection_GeneratedServiceRegistration_ViewModels.g.cs - with RegisterViewModels(IServiceCollection builder)
  • ServiceCollection_GeneratedServiceRegistration_Actions.g.cs - with RegisterActions(IServiceCollection builder)
  • ServiceCollection_GeneratedServiceRegistration_CommandBuilders.g.cs - with RegisterCommandBuilders(IServiceCollection builder)
  • ServiceCollection_GeneratedServiceRegistration.g.cs - main aggregator:
public static partial class ServiceCollection_GeneratedServiceRegistration
{
    public static void RegisterServices(IServiceCollection builder)
    {
        RegisterActions(builder);
        RegisterCommandBuilders(builder);
        RegisterPages(builder);
        RegisterViewModels(builder);
    }

    public static void AfterContainerBuilt(IServiceProvider provider)
    {
        // OnActivated callbacks here
    }
}

Usage remains the same - just call RegisterServices(builder) and all group methods are invoked automatically.


Examples

Automatically Registered Services

  • TestPage is registered as a singleton because it ends with "Page" and the ioc_config.json includes "Page" in RegisterTypesEndingWith. However, it will not be registered as ITestPage due to the ExcludedFromRegisteringAsConventionInterface rule.
[Singleton]
public class TestPage : ITestPage
{
}

Excluded from Registration

  • EnemyUserViewModel is excluded from automatic registration as it is listed in ExcludedFromRegisteringTypesEndingWith in ioc_config.json.
public class EnemyUserViewModel
{
}

Custom Service Registration

  • FriendlyUserViewModel is registered as both IBaseUserViewModel and IFriendsViewModel, bypassing the exclusion rule due to the presence of the [ServiceRegistration] attribute.
[Singleton]
[ServiceRegistration([typeof(IBaseUserViewModel), typeof(IFriendsViewModel)])]
public class FriendlyUserViewModel : IBaseUserViewModel, IFriendsViewModel
{
}

Factory Registration

Example:


[FactoryRegistration("Tools.ContainerRegistration.Sample.HelloWorldServiceFactory.CreateHelloWorldService")]
[Singleton(true)]
public interface IHelloWorldService
{
    void SayHello();
}

where factory and implementation is

namespace Tools.ContainerRegistration.Sample;

public static class HelloWorldServiceFactory
{
    public static object CreateHelloWorldService(Type typeToCreate, IServiceProvider provider) => new HelloWorldService(CultureInfo.CurrentUICulture);
    public static object CreateHelloWorldService(Type typeToCreate, IComponentContext provider) => new HelloWorldService(CultureInfo.CurrentUICulture);
}

[ManualRegistration]
public class HelloWorldService : IHelloWorldService
{
    private readonly CultureInfo _currentCulture;

    public HelloWorldService(
        CultureInfo currentCulture)
    {
        _currentCulture = currentCulture;
        SayHello();
    }

    public void SayHello()
    {
        Console.WriteLine($"I would like to say Hello World in {_currentCulture.EnglishName}, but I don't know it!");
    }
}

Manual Registration

  • TestUserPage is not registered automatically despite having the [Singleton] attribute, due to the [ManualRegistration] attribute. This service must be registered manually in the DI container.
[ManualRegistration]
[Singleton]
public class TestUserPage : ITestUserPage
{
}

Generation Output

Autofac

When using Autofac, library will create for example such class:

namespace Tools.ContainerRegistration.Sample;

public static class Autofac_GeneratedServiceRegistration
{
    public static void RegisterServices(ContainerBuilder builder)
    {
         builder.RegisterType<global::Tools.ContainerRegistration.Sample.TestPage>().AsSelf().SingleInstance();
         builder.RegisterType<global::Tools.ContainerRegistration.Sample.FriendlyUserViewModel>().As<Tools.ContainerRegistration.Sample.IBaseUserViewModel>().As<Tools.ContainerRegistration.Sample.IFriendsViewModel>().AsSelf().SingleInstance();
         builder.RegisterType<global::Tools.ContainerRegistration.Sample.ProfileViewModel>().AsSelf();
         builder.RegisterType<global::Tools.ContainerRegistration.Sample.TestService>().As<Tools.ContainerRegistration.Sample.ITestService>().As<Tools.ContainerRegistration.Sample.ITestService2>().AsSelf().SingleInstance().AutoActivate();
         builder.Register(context => Tools.ContainerRegistration.Sample.HelloWorldServiceFactory.CreateHelloWorldService(typeof(global::Tools.ContainerRegistration.Sample.IHelloWorldService), context)).As<IHelloWorldService>().AsSelf().SingleInstance().AutoActivate();
    }
}

then you can use it when initializing your container that way:

var containerBuilder = new ContainerBuilder();
Autofac_GeneratedServiceRegistration.RegisterServices(containerBuilder);
containerBuilder.Build();

Microsoft Dependency Injection

When using Microsoft Dependency Injection, library will create for example such class:

namespace Tools.ContainerRegistration.Sample;

public static class ServiceCollection_GeneratedServiceRegistration
{
    public static void RegisterServices(IServiceCollection builder)
    {
         builder.AddSingleton<global::Tools.ContainerRegistration.Sample.TestPage>();
         Tools.ContainerRegistration.Microsoft.Extensions.ServiceCollectionExtension.ReUseSingleton<global::Tools.ContainerRegistration.Sample.FriendlyUserViewModel, Tools.ContainerRegistration.Sample.IBaseUserViewModel>(builder);
         Tools.ContainerRegistration.Microsoft.Extensions.ServiceCollectionExtension.ReUseSingleton<global::Tools.ContainerRegistration.Sample.FriendlyUserViewModel, Tools.ContainerRegistration.Sample.IFriendsViewModel>(builder);
         builder.AddSingleton<global::Tools.ContainerRegistration.Sample.FriendlyUserViewModel>();
         builder.AddTransient<global::Tools.ContainerRegistration.Sample.ProfileViewModel>();
         Tools.ContainerRegistration.Microsoft.Extensions.ServiceCollectionExtension.ReUseSingleton<global::Tools.ContainerRegistration.Sample.TestService, Tools.ContainerRegistration.Sample.ITestService>(builder);
         Tools.ContainerRegistration.Microsoft.Extensions.ServiceCollectionExtension.ReUseSingleton<global::Tools.ContainerRegistration.Sample.TestService, Tools.ContainerRegistration.Sample.ITestService2>(builder);
         builder.AddSingleton<global::Tools.ContainerRegistration.Sample.TestService>();
         builder.AddSingleton(typeof(global::Tools.ContainerRegistration.Sample.IHelloWorldService), provider => Tools.ContainerRegistration.Sample.HelloWorldServiceFactory.CreateHelloWorldService(typeof(global::Tools.ContainerRegistration.Sample.IHelloWorldService), provider));
         // Factory + interface forwarding example:
         builder.AddSingleton(typeof(global::MyApp.MobileLanguageBinder), provider => MyApp.MobileLanguageBinderFactory.Create(typeof(global::MyApp.MobileLanguageBinder), provider));
         builder.AddSingleton<MyApp.ILanguageBinder>(provider => provider.GetRequiredService<global::MyApp.MobileLanguageBinder>());
    }

    public static void AfterContainerBuilt(IServiceProvider provider)
    {
         // AutoActivate services (marked with [Singleton(true)])
         provider.GetRequiredService<TestService>();
         provider.GetRequiredService<IHelloWorldService>();

         // OnActivated callbacks (marked with [OnActivated])
         var instance_MobileLanguageBinder = provider.GetRequiredService<global::MyApp.MobileLanguageBinder>();
         global::MyApp.LanguageBinderLocator.SetImplementation(instance_MobileLanguageBinder);

         // Instance method callbacks
         var instance_MyService = provider.GetRequiredService<global::MyApp.MyService>();
         instance_MyService.Initialize();
    }
}

then you can use it that way:

var collection = new ServiceCollection();
ServiceCollection_GeneratedServiceRegistration.RegisterServices(collection);
var provider = collection.BuildServiceProvider();
ServiceCollection_GeneratedServiceRegistration.AfterContainerBuilt(provider);

take a look that it also generates void AfterContainerBuilt(IServiceProvider provider) method. This method is used for:

  • AutoActivate - services marked with [Singleton(true)] are resolved to trigger construction
  • OnActivated callbacks - services marked with [OnActivated] have their callback methods invoked after resolution

GeneratedServiceRegistration namespace

Library should be added to each project that you want GeneratedServiceRegistration.g.cs file to be generated. When added to for example Project.Mobile.Common it will be accessible through full name: Project.Mobile.Common.Autofac_GeneratedServiceRegistration, when added to Project.Endpoints another class will be generated and be accessible through Project.Endpoints.Autofac_GeneratedServiceRegistration


For further details, refer to the ioc_config.json file and attribute-based configurations in the source code.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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
0.2.1 191 4/22/2026