Use root arguments when you need to pass state into a specific root. Define them with RootArg<T>(string argName) (optionally with tags) and use them like any other dependency. A root that uses at least one root argument becomes a method, and only arguments used in that root's object graph appear in the method signature. Use unique argument names to avoid collisions.
Root arguments are useful when runtime values belong to one entry point, not to the whole composition.
Note
Actually, root arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be injected wherever they are required.
using Shouldly;
using Pure.DI;
using static Pure.DI.Tag;
DI.Setup(nameof(Composition))
// Disable Resolve methods because root arguments are not compatible
.Hint(Hint.Resolve, "Off")
.Bind<IDatabaseService>().To<DatabaseService>()
.Bind<IApplication>().To<Application>()
// Root arguments serve as values passed
// to the composition root method
.RootArg<int>("port")
.RootArg<string>("connectionString")
// An argument can be tagged
// to be injectable by type and this tag
.RootArg<string>("appName", AppDetail)
// Composition root
.Root<IApplication>("CreateApplication");
var composition = new Composition();
// Creates an application with specific arguments
var app = composition.CreateApplication(
appName: "MySuperApp",
port: 8080,
connectionString: "Server=.;Database=MyDb;");
app.Name.ShouldBe("MySuperApp");
app.Database.Port.ShouldBe(8080);
app.Database.ConnectionString.ShouldBe("Server=.;Database=MyDb;");
interface IDatabaseService
{
int Port { get; }
string ConnectionString { get; }
}
class DatabaseService(int port, string connectionString) : IDatabaseService
{
public int Port { get; } = port;
public string ConnectionString { get; } = connectionString;
}
interface IApplication
{
string Name { get; }
IDatabaseService Database { get; }
}
class Application(
[Tag(AppDetail)] string name,
IDatabaseService database)
: IApplication
{
public string Name { get; } = name;
public IDatabaseService Database { get; } = database;
}Running this code sample locally
- Make sure you have the .NET SDK 10.0 or later installed
dotnet --list-sdk- Create a net10.0 (or later) console application
dotnet new console -n Sampledotnet add package Pure.DI
dotnet add package Shouldly- Copy the example code into the Program.cs file
You are ready to run the example 🚀
dotnet runWhen using root arguments, compilation warnings are emitted if Resolve methods are generated because these methods cannot create such roots. Disable Resolve via Hint(Hint.Resolve, "Off"), or ignore the warnings and accept the risks.
Limitations: roots with root arguments become methods and are incompatible with generated Resolve methods.
Common pitfalls:
- Reusing ambiguous argument names for different concepts.
- Forgetting to disable or avoid
Resolveusage in these setups. See also: Composition arguments, Resolve hint.
The following partial class will be generated:
partial class Composition
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new Lock();
#else
private readonly Object _lock = new Object();
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IApplication CreateApplication(int port, string connectionString, string appName)
{
if (connectionString is null) throw new ArgumentNullException(nameof(connectionString));
if (appName is null) throw new ArgumentNullException(nameof(appName));
return new Application(appName, new DatabaseService(port, connectionString));
}
}Class diagram:
---
config:
maxTextSize: 2147483647
maxEdges: 2147483647
class:
hideEmptyMembersBox: true
---
classDiagram
DatabaseService --|> IDatabaseService
Application --|> IApplication
Composition ..> Application : IApplication CreateApplication(int port, string connectionString, string appName)
DatabaseService o-- Int32 : Argument "port"
DatabaseService o-- String : Argument "connectionString"
Application *-- DatabaseService : IDatabaseService
Application o-- String : "AppDetail" Argument "appName"
namespace Pure.DI.UsageTests.Basics.RootArgumentsScenario {
class Application {
<<class>>
+Application(String name, IDatabaseService database)
}
class Composition {
<<partial>>
+IApplication CreateApplication(int port, string connectionString, string appName)
}
class DatabaseService {
<<class>>
+DatabaseService(Int32 port, String connectionString)
}
class IApplication {
<<interface>>
}
class IDatabaseService {
<<interface>>
}
}
namespace System {
class Int32 {
<<struct>>
}
class String {
<<class>>
}
}