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()
-
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:
- AllTypes
- AllNonAbstractClasses
- AllAbstractClasses
- AllInterfaces
- 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:
-
DerivingFrom - Only match types deriving from
T
-
DerivingFromOrEqual - Only match types deriving from or equal to
T
-
WithPrefix(value) - Only match types with names that start with
value
-
WithSuffix(value) - Only match types with names that end with
value
-
WithAttribute - Only match types that have the attribute
[T]
above their class declaration -
WithoutAttribute - Only match types that do not have the attribute
[T]
above their class declaration -
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 - InNamespace(value) - Only match types that are in the given namespace
- InNamespaces(value1, value2, etc.) - Only match types that are in any of the given namespaces
- MatchingRegex(pattern) - Only match types that match the given regular expression
-
Where(predicate) - Finally, you can also add any kind of conditional logic you want by passing in a predicate that takes a
Type
parameter
-
DerivingFrom - Only match types deriving from
-
AssemblySources = The list of assemblies to search for types when populating InitialList. It can be one of the following:
- FromAllAssemblies - Look up types in all loaded assemblies. This is the default when unspecified.
-
FromAssemblyContaining - Look up types in whatever assembly the type
T
is in - FromAssembliesContaining(type1, type2, ..) - Look up types in all assemblies that contains any of the given types
- FromThisAssembly - Look up types only in the assembly in which you are calling this method
- FromAssembly(assembly) - Look up types only in the given assembly
- FromAssemblies(assembly1, assembly2, ...) - Look up types only in the given assemblies
- FromAssembliesWhere(predicate) - Look up types in all assemblies that match the given predicate
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.
-
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 justAllTypes
, to ensure that we don't bindIFoo
to itself -
Bind an interface to all classes implementing it within a given namespace
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>().InNamespace("MyGame.Foos"));
-
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$"));
-
Bind all types with the prefix "Widget" and inject into Foo
Container.Bind<object>().To(x => x.AllNonAbstractTypes().WithPrefix("Widget")).WhenInjectedInto<Foo>();
-
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 usingAllInterfaces
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.