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
<PackageReference Include="Tools.ContainerRegistration.Microsoft" Version="0.2.1" />
<PackageVersion Include="Tools.ContainerRegistration.Microsoft" Version="0.2.1" />
<PackageReference Include="Tools.ContainerRegistration.Microsoft" />
paket add Tools.ContainerRegistration.Microsoft --version 0.2.1
#r "nuget: Tools.ContainerRegistration.Microsoft, 0.2.1"
#:package Tools.ContainerRegistration.Microsoft@0.2.1
#addin nuget:?package=Tools.ContainerRegistration.Microsoft&version=0.2.1
#tool nuget:?package=Tools.ContainerRegistration.Microsoft&version=0.2.1
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
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 implementIInjector<T> - The generator scans the inheritance chain to detect the attribute
- All
IInjector<T>interfaces are detected and theirInjectmethods 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- withRegisterPages(IServiceCollection builder)ServiceCollection_GeneratedServiceRegistration_ViewModels.g.cs- withRegisterViewModels(IServiceCollection builder)ServiceCollection_GeneratedServiceRegistration_Actions.g.cs- withRegisterActions(IServiceCollection builder)ServiceCollection_GeneratedServiceRegistration_CommandBuilders.g.cs- withRegisterCommandBuilders(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.jsonincludes "Page" inRegisterTypesEndingWith. However, it will not be registered asITestPagedue to theExcludedFromRegisteringAsConventionInterfacerule.
[Singleton]
public class TestPage : ITestPage
{
}
Excluded from Registration
- EnemyUserViewModel is excluded from automatic registration as it is listed in
ExcludedFromRegisteringTypesEndingWithinioc_config.json.
public class EnemyUserViewModel
{
}
Custom Service Registration
- FriendlyUserViewModel is registered as both
IBaseUserViewModelandIFriendsViewModel, 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 | Versions 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. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.Analyzers (>= 3.3.4)
- Microsoft.CodeAnalysis.CSharp (>= 4.11.0)
- System.Text.Json (>= 9.0.12)
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 |