Convention Based Binding - mariaheine/Zenject-But-Wiki GitHub Wiki

Convention based binding can come in handy in any of the following scenarios:

  • You want to define a naming convention that determines how classes are bound to the container (eg. using a prefix, suffix, or regex)
  • You want to use custom attributes to determine how classes are bound to the container
  • You want to automatically bind all classes that implement a given interface within a given namespace or assembly

Using "convention over configuration" can allow you to define a framework that other programmers can use to quickly and easily get things done, instead of having to explicitly add every binding within installers. This is the philosophy that is followed by frameworks like Ruby on Rails, ASP.NET MVC, etc. Of course, there are both advantages and disadvantages to this approach.

They are specified in a similar way to Non Generic bindings, except instead of giving a list of types to the Bind() and To() methods, you describe the convention using a Fluent API. For example, to bind IFoo to every class that implements it in the entire codebase:

Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());

Note that you can use the same Fluent API in the Bind() method as well, and you can also use it in both Bind() and To() at the same time.

For more examples see the examples section below. The full format is as follows:

x.InitialList().Conditional().AssemblySources()

Where:

  • InitialList = The initial list of types to use for our binding. This list will be filtered by the given Conditionals. It can be one of the following (fairly self explanatory) methods:

    1. AllTypes
    2. AllNonAbstractClasses
    3. AllAbstractClasses
    4. AllInterfaces
    5. AllClasses
  • Conditional = The filter to apply to the list of types given by InitialList. Note that you can chain as many of these together as you want, and they will all be applied to the initial list in sequence. It can be one of the following:

    1. DerivingFrom - Only match types deriving from T
    2. DerivingFromOrEqual - Only match types deriving from or equal to T
    3. WithPrefix(value) - Only match types with names that start with value
    4. WithSuffix(value) - Only match types with names that end with value
    5. WithAttribute - Only match types that have the attribute [T] above their class declaration
    6. WithoutAttribute - Only match types that do not have the attribute [T] above their class declaration
    7. WithAttributeWhere(predicate) - Only match types that have the attribute [T] above their class declaration AND in which the given predicate returns true when passed the attribute. This is useful so you can use data given to the attribute to create bindings
    8. InNamespace(value) - Only match types that are in the given namespace
    9. InNamespaces(value1, value2, etc.) - Only match types that are in any of the given namespaces
    10. MatchingRegex(pattern) - Only match types that match the given regular expression
    11. Where(predicate) - Finally, you can also add any kind of conditional logic you want by passing in a predicate that takes a Type parameter
  • AssemblySources = The list of assemblies to search for types when populating InitialList. It can be one of the following:

    1. FromAllAssemblies - Look up types in all loaded assemblies. This is the default when unspecified.
    2. FromAssemblyContaining - Look up types in whatever assembly the type T is in
    3. FromAssembliesContaining(type1, type2, ..) - Look up types in all assemblies that contains any of the given types
    4. FromThisAssembly - Look up types only in the assembly in which you are calling this method
    5. FromAssembly(assembly) - Look up types only in the given assembly
    6. FromAssemblies(assembly1, assembly2, ...) - Look up types only in the given assemblies
    7. FromAssembliesWhere(predicate) - Look up types in all assemblies that match the given predicate

Examples:

Note that you can chain together any combination of the below conditionals in the same binding. Also note that since we aren't specifying an assembly here, Zenject will search within all loaded assemblies.

  1. Bind IFoo to every class that implements it in the entire codebase:

    Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());

    Note that this will also have the same result:

    Container.Bind<IFoo>().To(x => x.AllNonAbstractTypes());

    This is because Zenject will skip any bindings in which the concrete type does not actually derive from the base type. Also note that in this case we have to make sure we use AllNonAbstractTypes instead of just AllTypes, to ensure that we don't bind IFoo to itself

  2. Bind an interface to all classes implementing it within a given namespace

    Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>().InNamespace("MyGame.Foos"));
  3. Auto-bind IController every class that has the suffix "Controller" (as is done in ASP.NET MVC):

    Container.Bind<IController>().To(x => x.AllNonAbstractTypes().WithSuffix("Controller"));

    You could also do this using MatchingRegex:

    Container.Bind<IController>().To(x => x.AllNonAbstractTypes().MatchingRegex("Controller$"));
  4. Bind all types with the prefix "Widget" and inject into Foo

    Container.Bind<object>().To(x => x.AllNonAbstractTypes().WithPrefix("Widget")).WhenInjectedInto<Foo>();
  5. Auto-bind the interfaces that are used by every type in a given namespace

    Container.Bind(x => x.AllInterfaces())
        .To(x => x.AllNonAbstractClasses().InNamespace("MyGame.Things"));

    This is equivalent to calling Container.BindInterfacesTo<T>() for every type in the namespace "MyGame.Things". This works because, as touched on above, Zenject will skip any bindings in which the concrete type does not actually derive from the base type. So even though we are using AllInterfaces which matches every single interface in every single loaded assembly, this is ok because it will not try and bind an interface to a type that doesn't implement this interface.

⚠️ **GitHub.com Fallback** ⚠️