Skip to content

Latest commit

 

History

History
124 lines (97 loc) · 3.88 KB

root-arguments.md

File metadata and controls

124 lines (97 loc) · 3.88 KB

Root arguments

CSharp

Sometimes it is necessary to pass some state to the composition root to use it when resolving dependencies. To do this, just use the RootArg<T>(string argName) method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions.

interface IDependency
{
    int Id { get; }

    public string DependencyName { get; }
}

class Dependency(int id, string dependencyName) : IDependency
{
    public int Id { get; } = id;

    public string DependencyName { get; } = dependencyName;
}

interface IService
{
    string Name { get; }

    IDependency Dependency { get; }
}

class Service(
    [Tag("forService")] string name,
    IDependency dependency)
    : IService
{
    public string Name { get; } = name;

    public IDependency Dependency { get; } = dependency;
}

DI.Setup(nameof(Composition))
    // This hint indicates to not generate methods such as Resolve
    .Hint(Hint.Resolve, "Off")
    .Bind<IDependency>().To<Dependency>()
    .Bind<IService>().To<Service>()

    // Some argument
    .RootArg<int>("id")
    .RootArg<string>("dependencyName")

    // An argument can be tagged (e.g., tag "forService")
    // to be injectable by type and this tag
    .RootArg<string>("serviceName", "forService")

    // Composition root
    .Root<IService>("CreateService");

var composition = new Composition();
        
// service = new Service("Abc", new Dependency(123, "dependency 123"));
var service = composition.CreateService(serviceName: "Abc", id: 123, dependencyName: "dependency 123");
        
service.Name.ShouldBe("Abc");
service.Dependency.Id.ShouldBe(123);
service.Dependency.DependencyName.ShouldBe("dependency 123");

When using composition root arguments, compilation warnings are shown if Resolve methods are generated, since these methods will not be able to create these roots. You can disable the creation of Resolve methods using the Hint(Hint.Resolve, "Off") hint, or ignore them but remember the risks of using Resolve methods.

The following partial class will be generated:

partial class Composition
{
  private readonly Composition _root;

  public Composition()
  {
    _root = this;
  }

  internal Composition(Composition parentScope)
  {
    _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root;
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public IService CreateService(int id, string dependencyName, string serviceName)
  {
    return new Service(serviceName, new Dependency(id, dependencyName));
  }
}

Class diagram:

classDiagram
	class Composition {
		<<partial>>
		+IService CreateService(int id, string dependencyName, string serviceName)
	}
	class Int32
	class String
	Dependency --|> IDependency
	class Dependency {
		+Dependency(Int32 id, String dependencyName)
	}
	Service --|> IService
	class Service {
		+Service(String name, IDependency dependency)
	}
	class IDependency {
		<<interface>>
	}
	class IService {
		<<interface>>
	}
	Dependency o-- Int32 : Argument "id"
	Dependency o-- String : Argument "dependencyName"
	Service o-- String : "forService"  Argument "serviceName"
	Service *--  Dependency : IDependency
	Composition ..> Service : IService CreateService(int id, string dependencyName, string serviceName)