Devlooped.Extensions.DependencyInjection.Attributed 1.3.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Devlooped.Extensions.DependencyInjection.Attributed --version 1.3.0                
NuGet\Install-Package Devlooped.Extensions.DependencyInjection.Attributed -Version 1.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="Devlooped.Extensions.DependencyInjection.Attributed" Version="1.3.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Devlooped.Extensions.DependencyInjection.Attributed --version 1.3.0                
#r "nuget: Devlooped.Extensions.DependencyInjection.Attributed, 1.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 Devlooped.Extensions.DependencyInjection.Attributed as a Cake Addin
#addin nuget:?package=Devlooped.Extensions.DependencyInjection.Attributed&version=1.3.0

// Install Devlooped.Extensions.DependencyInjection.Attributed as a Cake Tool
#tool nuget:?package=Devlooped.Extensions.DependencyInjection.Attributed&version=1.3.0                

Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies.

Usage

After installing the nuget package, a new [Service(ServiceLifetime)] attribute will be available to annotate your types:

[Service(ServiceLifetime.Scoped)]
public class MyService : IMyService, IDisposable
{
    public string Message => "Hello World";

    public void Dispose() { }
}

public interface IMyService 
{
    string Message { get; }
}

The ServiceLifetime argument is optional and defaults to ServiceLifetime.Singleton.

NOTE: The attribute is matched by simple name, so you can define your own attribute in your own assembly. It only has to provide a constructor receiving a ServiceLifetime argument.

A source generator will emit (at compile-time) an AddServices extension method for IServiceCollection which you can call from your startup code that sets up your services, like:

var builder = WebApplication.CreateBuilder(args);

// NOTE: **Adds discovered services to the container**
builder.Services.AddServices();
// ...

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGet("/", (IMyService service) => service.Message);

// ...
app.Run();

NOTE: the service is available automatically for the scoped request, because we called the generated AddServices that registers the discovered services.

And that's it. The source generator will discover annotated types in the current project and all its references too. Since the registration code is generated at compile-time, there is no run-time reflection (or dependencies) whatsoever.

MEF Compatibility

Given the (more or less broad?) adoption of MEF attribute (whether .NET MEF, NuGet MEF or VS MEF) in .NET, the generator also supports the [Export] attribute to denote a service (the type argument as well as contract name are ignored, since those aren't supported in the DI container).

In order to specify a singleton (shared) instance in MEF, you have to annotate the type with an extra attribute: [Shared] in NuGet MEF (from System.Composition) or [PartCreationPolicy(CreationPolicy.Shared)] in .NET MEF (from System.ComponentModel.Composition).

How It Works

The generated code that implements the registration looks like the following:

static partial class AddServicesExtension
{
    public static IServiceCollection AddServices(this IServiceCollection services)
    {
        services.AddScoped(s => new MyService());
        services.AddScoped<IMyService>(s => s.GetRequiredService<MyService>());
        services.AddScoped<IDisposable>(s => s.GetRequiredService<MyService>());
        
        return services;
    }

Note how the service is registered as scoped with its own type first, and the other two registrations just retrieve the same (according to its defined lifetime). This means the instance is reused and properly registered under all implemented interfaces automatically.

NOTE: you can inspect the generated code by setting EmitCompilerGeneratedFiles=true in your project file and browsing the generated subfolder under obj.

If the service type has dependencies, they will be resolved from the service provider by the implementation factory too, like:

services.AddScoped(s => new MyService(s.GetRequiredService<IMyDependency>(), ...));

Advanced Scenarios

Your Own ServiceAttribute

If you want to declare your own ServiceAttribute and reuse from your projects, so as to avoid taking a (compile-time) dependency on this package from your library projects, you can just declare it like so:

[AttributeUsage(AttributeTargets.Class)]
public class ServiceAttribute : Attribute
{
    public ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Singleton) { }
}

NOTE: since the constructor argument is only used by the source generation to detemine the registration style, but never at run-time, you don't even need to keep it around in a field or property!

With this in place, you only need to add this package to the top-level project that is adding the services to the collection!

The attribute is matched by simple name, so it can exist in any namespace.

If you want to avoid adding the attribute to the project referencing this package, set the $(AddServiceAttribute) to true via MSBuild:

<PropertyGroup>
  <AddServiceAttribute>false</AddServiceAttribute>
</PropertyGroup>

Choose Constructor

If you want to choose a specific constructor to be used for the service implementation factory registration (instead of the default one which will be the one with the most parameters), you can annotate it with [ImportingConstructor] from either NuGet MEF (System.Composition) or .NET MEF (System.ComponentModel.Composition).

Customize Generated Class

You can customize the generated class namespace and name with the following MSBuild properties:

<PropertyGroup>
    <AddServicesNamespace>MyNamespace</AddServicesNamespace>
    <AddServicesClassName>MyExtensions</AddServicesClassName>
</PropertyGroup>

They default to Microsoft.Extensions.DependencyInjection and AddServicesExtension respectively.

Sponsors

Clarius Org Christian Findlay C. Augusto Proiete Kirill Osenkov MFB Technologies, Inc. SandRock Eric C Andy Gocke

Sponsor this project  

Learn more about GitHub Sponsors

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
2.1.0-beta 69 11/12/2024
2.0.0 2,993 2/29/2024
1.3.2 7,540 12/13/2022
1.3.1 758 12/13/2022
1.3.0 796 12/6/2022
1.2.2 907 11/18/2022
1.2.1 861 11/16/2022
1.2.0 989 11/10/2022
1.1.3 786 10/31/2022
1.1.2 908 10/3/2022
1.1.1 876 10/3/2022
1.1.0 871 9/28/2022
1.0.3 899 9/27/2022