Mdk.DISourceGenerator 1.2.0

dotnet add package Mdk.DISourceGenerator --version 1.2.0
NuGet\Install-Package Mdk.DISourceGenerator -Version 1.2.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="Mdk.DISourceGenerator" Version="1.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Mdk.DISourceGenerator --version 1.2.0
#r "nuget: Mdk.DISourceGenerator, 1.2.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 Mdk.DISourceGenerator as a Cake Addin
#addin nuget:?package=Mdk.DISourceGenerator&version=1.2.0

// Install Mdk.DISourceGenerator as a Cake Tool
#tool nuget:?package=Mdk.DISourceGenerator&version=1.2.0

Build and test Build, test, pack and publish Nuget

Summary

The DISourceGenerator package is designed to help clean up your service registration code when using the default Dependency Injection (DI) container in .NET. It allows you to use custom attributes to register your services, keeping the DI metadata close to the implementation classes.

  • Installation: The package is available on NuGet as Mdk.DISourceGenerator.
  • Attribute Usage: The package provides attributes like AddScoped, AddSingleton, and AddTransient for different lifetimes. You can use these attributes on your classes to register them with the DI container. For example, [AddScoped] class MyClass { ... } is equivalent to services.AddScoped<MyClass>();.
  • The incremental source generator translates the attributes to registration code for the default DI container.

DISourceGenerator

If you have a lot of services registered in the default DI container, your registration code can become some sort of a mess.

Using custom attributes can make your registration much cleaner. Attributes with registration information keep DI metadata close to the implementation classes the attributes are assigned to.

DISourceGenerator generates service registration source code based on attributes assigned to classes. Adding one line of code in ConfigureServices registers all services both in the host assembly as in directly and transitively referenced assemblies.

The examples section of the GitHub repository contains a Blazor application and a Minimal API project, in which this registration strategy is implemented.

Installation

The source generator is available as a NuGet package: Mdk.DISourceGenerator

Attribute usage

Following examples focus on scoped registration. Use AddSingleton or AddTransient for other lifetimes.

Simple classes and interfaces

[AddScoped]
class MyClass { ... }

corresponds to services.AddScoped<MyClass>();

[AddScoped<IMyInterface>]
class MyClass: IMyInterface { ... }

corresponds to services.AddScoped<IMyInterface, MyClass>();

Generic attributes require C# 11. If you are still on a earlier version use [AddScoped(typeof(IMyInterface))]

Multiple attributes on one class
[AddScoped<IMyInterface1>]
[AddScoped<IMyInterface2>]
class MyClass: IMyInterface1, IMyInterface2 { ... }

corresponds to

services.AddScoped<IMyInterface1, MyClass>();
services.AddScoped<IMyInterface2, MyClass>();

Generic classes and interfaces

Unbound generic registration:
[AddScoped]
class MyClass<T> { ... }

corresponds to services.AddScoped(typeof(MyClass<>));

[AddScoped(typeof(IMyInterface<>))]
class MyClass<T>: IMyInterface<T> { ... }

corresponds to services.AddScoped(typeof(IMyInterface<>), typeof(MyClass<>));

Bound generic registration:
[AddScoped<MyClass<int>>]
class MyClass<T> { ... }

corresponds to services.AddScoped<MyClass<int>>();

[AddScoped<IMyInterface<int>>]
class MyClass<T>: IMyInterface<T> { ... }

corresponds to services.AddScoped<IMyInterface<int>, MyClass<int>>();

Multiple generic type parameters

Multiple generic type parameters are also supported, for example:

[AddScoped]
class MyClass<T, U> { ... }

corresponds to services.AddScoped(typeof(MyClass<,>));

Generated source

If there's a direct or transitive reference to the DISourceGenerator package and DIAttributes are used in an assembly, a registration method is generated per assembly.

This method registers all services in an assembly and also includes static method calls to all generated methods in referenced assemblies, both direct and transitive:

// <auto-generated />

using Microsoft.Extensions.DependencyInjection;

namespace Mdk.DISourceGenerator;

/// <summary>Dependency injection registrations for MinimalApi.</summary>
public static partial class DIRegistrations
{
    /// <summary>Registers the services for MinimalApi and referenced assemblies.</summary>
    public static IServiceCollection RegisterServicesMinimalApi(this IServiceCollection services)
    {
        if (registeredServicesMinimalApi)
            return services;

        services.RegisterServicesBusinessBaseLogic();
        services.RegisterServicesBusinessLogic();

        services.AddScoped<global::MinimalApi.MinimalApiService>();

        registeredServicesMinimalApi = true;

        return services;
    }

    private static bool registeredServicesMinimalApi;
}

The naming convention for the static method is DIRegistrations.RegisterServices{AssemblyName}(...).

So in startup only one method call is needed to register all services based on DIAttributes:

using Mdk.DISourceGenerator;

builder.Services.RegisterServicesMinimalApi();

Inspecting generated source code

In the Solution Explorer of Visual Studio generated source can be found in:
{AssemblyName} > Dependencies > Analyzers > Mdk.DISourceGenerator > Mdk.DISourceGenerator.DISourceGenerator > DISourceGenerator.{AssemblyName}.g.cs

You can make the generated source more visible in your project and even add the generated files to source control. Copy and paste following PropertyGroup and ItemGroup into the .csproj-file.

<PropertyGroup>
  
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  
  <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
  
  <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
  
  <None Include="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
</ItemGroup>

This configuration is for all source generators in the project. Use git ignore to prevent output of (other) source generators to be included in source control.

Analyzers

The package also contains analyzers to help you use the attributes correctly. No registration is generated for attributes for which analyzers generate errors. This prevents compiler errors in the generated code.

DI0001: Implementation type missing on DI attribute

If the service type is a bound generic type en the class type is generic as well, the implementation type is mandatory.

Severity: Error

Example:

[AddScoped<IGenericType<int>>]
class GenericType<T> : IGenericType<T> { }

Should be:

[AddScoped<IGenericType<int>, GenericType<int>>]
class GenericType<T> : IGenericType<T> { }

DI0002: Interface missing on class

If the service type is an interface, the class type must implement this interface.

Severity: Error

Example:

[AddScoped<IInterface>]
class Implementation { }

Should be:

[AddScoped<IInterface>]
class Implementation : IInterface { }

DI0003: Implementation type is not the same as class type

If the implementation type is non generic and is set on the attribute, it must be the same as the class type.

Severity: Error

Example:

[AddScoped<IInterface, Implementation>]
class ClassType : IInterface { }

typeof(Implementation) == typeof(ClassType)

References

Articles

Repositories

There are no supported framework assets in this 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
1.2.0 1,393 1/17/2024
1.1.0 971 1/9/2024
1.0.1 630 1/5/2024
1.0.0 632 1/4/2024