5

I've seen a ton of examples of how to do DI, and the example always shows two dependencies. But what about when you have 50 or 100 dependencies (or more) in a large-ish application? The Program.cs file will get unwieldy with 100+ lines of builder.Services.AddScoped<IDependency1, Dependency1>();, not to mention problems where multiple developers are adding new dependencies and their branches conflict.

Does it make sense to have another file/method for these dependencies, so people aren't all modifying Program.cs? Something like,

AddDependencies(builder.Services);

//another file for this
public void AddDependencies(IServiceCollection services) 
{
    services.AddScoped<IDependency1, Dependency1>();
}

Or is there something else entirely that addresses this kind of thing?

5
  • Two dependencies? Aren't we looking at an interface and it's implementation? This is C# right? Commented May 31, 2023 at 16:44
  • @candied_orange - in the Microsoft documentation, for example, they have 4 dependencies, showing the different lifetimes. So yes, C# (that's why the tag is asp.net-core), and my example code is me spitballing a proposed solution to having 100+ dependencies.
    – NovaDev
    Commented May 31, 2023 at 16:56
  • 1
    How is this question any different from what we do when any kind of code starts growing too big to be a single block/file?
    – Flater
    Commented Jun 1, 2023 at 0:49
  • 1
    @Flater - I think it's different by virtue of what the file/method is in .NET Core vs any other assembly. Is there any harm to deviating from the pattern that Microsoft has set up? (It seems not). Because it's the DI container, are there better ways to handle a large number of dependencies? (It seems so). Microsoft hasn't addressed how to handle a large number of dependencies that I know of, so I'm asking here.
    – NovaDev
    Commented Jun 1, 2023 at 12:28
  • @NovaDev DI registration is just code. Any and all practices revolving around the separation and layering of code can apply, the reasoning is the same.
    – Flater
    Commented Jun 1, 2023 at 23:33

3 Answers 3

6

A software with this many dependencies is probably structured in modules or at least the classes can be grouped in some way. For example, you may have some data access layer, some frontend, and maybe some special kind of connector.

If you now add the dependencies of each group in their own function, conflicts occur only if multiple developers modify the same group. Using extension methods, the code looks similar to the default service collection functions.

I used this approach in multiple projects and I am pretty happy about it.

// Program.cs:
services.AddFrontend();
services.AddDataAccessLayer(configuration.GetSection("DataAccess"));
services.AddSpecialConnector();

// FrontendDi.cs:
public static class FrontendDi
{
    public static void AddFrontend(this IServiceCollection services)
    {
        services.AddSingleton(...);
        ...
    }
}

// DataAccessLayerDi.cs:
public static class DataAccessLayerDi
{
    public static void AddDataAccessLayer(this IServiceCollection services, IConfiguration config)
    {
        services.AddSingleton<...>();
        services.AddOptions<...>(config);
        ...
    }
}

// SpecialConnectorDi.cs:
public static class SpecialConnectorDi
{
    public static void AddSpecialConnector(this IServiceCollection services)
    {
        services.AddSingleton(...);
        ...
    }
}

One nice side effect of this approach is, that you can easily enable and disable whole modules by calling the DI function conditionally.

// Program.cs:
services.AddFrontend();
services.AddDataAccessLayer(configuration.GetSection("DataAccess"));
if (requiresSpecialConnector)
{
    services.AddSpecialConnector();
}
4

The Program.cs file will get unwieldy with 100+ lines of [setting up dependencies]

While we generally strive to avoid long, 100+ line methods, sometimes they are unavoidable. Setting up all of the DI bits is mostly boiler-plate code that is largely set-it-and-forget-it. You only need to revisit it when you add another dependency (or remove one) - and when you do, it's very straightforward to read, even though it's a lot of lines.

Does it make sense to have another file/method for these dependencies, so people aren't all modifying Program.cs?

You can certainly create a new method for this (whether in Program.cs or another file), but it doesn't actually solve either of the problems you've outlined. There will still be a lot of lines, and there's still the potential for conflicts - you've just moved them from Main() to AddDependencies(). That said, AddDependencies is a well-defined, single purpose, well-named method, so setting up all the dependencies there will make for more readable and maintainable code, IMO.

In some languages, you could potentially use reflection (or packages which use reflection, as Rowan Freeman's answer suggests), but this is not necessarily an option in all. Setting up Main or AddDependencies() to use reflection (or another dependency) involves writing new code as well. You'll have to evaluate whether that is an appropriate option for your case.

1

One option to address this is to use the scrutor NuGet package.

Register services using assembly scanning and a fluent API.

Not the answer you're looking for? Browse other questions tagged or ask your own question.