Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET / ASP.NET Core
Tip/Trick

Completely Selfconfigurating Service with .NET 5

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
30 Aug 2021CPOL1 min read 11.3K   15   12
Why do we need to register all services with service.AddService?
Creating a DI based ASP.Core or other NET application includes Dependency injection but why do we have to register all services on our own?

Introduction

In an ASP.Core application (this is not limited to ASP.Core) when we want to use the build in DI container, we need to create services and then register them in the Startup.cs's ConfigureServices method. I wanted to streamline that and take the old M.E.F approach in fully self registering services.

The approach is easy:

  1. Annotate your service with either the [Service] or the [SingeltonService] attribute
  2. Call ServiceLocator.LoadServices
  3. Profit?

Background

The idea is from the now obsolete Method Extension Framework (M.E.F.) that was .Nets (First?) approach to DI containers where such annotation based service discoveries were popular.

Using the Code

First let's take a look into both attributes:

C#
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class SingeltonServiceAttribute : ServiceAttribute
{
    public SingeltonServiceAttribute(params Type[] registerAs) : base(registerAs)
    {
        
    }
}
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ServiceAttribute : Attribute
{
    public ServiceAttribute(params Type[] registerAs)
    {
        RegisterAs = registerAs;
    }

    public IEnumerable<Type> RegisterAs { get; set; }
}

They are both attributes that represents our markers for the service, but also include a list of Type to express all types the declaring type should be registered with. Now let's annotate a service:

C#
[SingeltonService(typeof(IFooRepository), typeof(IDatabaseRepository))]
public class FooRepository : IFooRepository
{
    public string Connection { get; set; }
    public async Task DoStuff() { }
}

public interface IFooRepository : IDatabaseRepository
{
    Task DoStuff();
}

public interface IDatabaseRepository
{
    string Connection { get; set; }
}

This marks our FooRepository as a Singleton service but also requests that IFooRepository and also IDatabaseRepository should point to the same instance as well. So if we request either FooRepository or IFooRepository or IDatabaseRepository, we always get the SAME instance (because Singleton service).

As we now have registered the service, let's load it into the DI container with the help of our ServiceLocator:

C#
public static class ServiceLocator
{
    public static void LoadServices(IServiceCollection services)
    {
        foreach (var type in AppDomain.CurrentDomain.GetAssemblies().SelectMany
                (f => f.GetTypes())
            .Where(e => e.GetCustomAttribute<ServiceAttribute>(false) != null))
        {
            var serviceAttribute = type.GetCustomAttribute<ServiceAttribute>(false);
            var actorTypes = serviceAttribute.RegisterAs;

            if (serviceAttribute is SingeltonServiceAttribute)
            {
                services.AddSingleton(type);
                foreach (var actorType in actorTypes)
                {
                    services.AddSingleton(actorType, (sCol) => sCol.GetService(type));
                }
            }
            else
            {
                services.AddScoped(type);
                foreach (var actorType in actorTypes)
                {
                    services.AddScoped(actorType, (sCol) => sCol.GetService(type));
                }
            }
        }
    }
}
public class MyStartup
{
    public MyStartup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    
    // This method gets called by the runtime. 
    // Use this method to add services to the container.
    // For more information on how to configure your application, 
    // visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        ServiceLocator.LoadServices(services);
    }
}

And that's it. Calling ServiceLocator.LoadServices starts the Service discovery and loads all services.

Points of Interest

In bigger projects, the ConfigurateServices method can grow quite a lot and I found it very helpful if the service that should configurate itself (DI rules) can also inject itself.

This is not exclusive to any other way of configurating services and should run fine alongside manual registrations.

History

  • 30th August, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Freelancer
Germany Germany
A nice guy.
And WPF Developer.
And asp.net.

like everything I can get my hand on in .net.
But never java.

Comments and Discussions

 
QuestionWatch the app domain for new loaded assemblies Pin
Tim Ford 202131-Aug-21 19:43
Tim Ford 202131-Aug-21 19:43 
SuggestionRe: Watch the app domain for new loaded assemblies Pin
Niemand2531-Aug-21 21:56
professionalNiemand2531-Aug-21 21:56 
GeneralRe: Watch the app domain for new loaded assemblies Pin
GerVenson31-Aug-21 22:10
professionalGerVenson31-Aug-21 22:10 
GeneralRe: Watch the app domain for new loaded assemblies Pin
Chris Schaller1-Sep-21 19:48
professionalChris Schaller1-Sep-21 19:48 
SuggestionEnum instead of subtyping Pin
Niemand2530-Aug-21 22:07
professionalNiemand2530-Aug-21 22:07 
GeneralRe: Enum instead of subtyping Pin
GerVenson31-Aug-21 3:23
professionalGerVenson31-Aug-21 3:23 
SuggestionGreat article but... Pin
Ulisses Albino Magalhães Júnior30-Aug-21 9:39
Ulisses Albino Magalhães Júnior30-Aug-21 9:39 
GeneralRe: Great article but... Pin
GerVenson30-Aug-21 11:24
professionalGerVenson30-Aug-21 11:24 
GeneralRe: Great article but... Pin
Sacha Barber31-Aug-21 2:46
Sacha Barber31-Aug-21 2:46 
Well that is a weird thing, why would you even write code like that, at some point you would want to use the implementation surely, so you would reference the assembly with implementation in it, and this would work.

or you are describing a plugin based thing where everything is 100% isolated and you would load it via MAF (Managed Addin Framework) or some god awful Assembly.Load and list for UnresolvedAssembly event

I think your argument just wouldn't happen that often

GeneralRe: Great article but... Pin
GerVenson31-Aug-21 3:22
professionalGerVenson31-Aug-21 3:22 
GeneralRe: Great article but... Pin
Sacha Barber31-Aug-21 6:44
Sacha Barber31-Aug-21 6:44 
GeneralRe: Great article but... Pin
Ulisses Albino Magalhães Júnior20-Sep-21 2:17
Ulisses Albino Magalhães Júnior20-Sep-21 2:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.