Setting up and using Dependency Injection
Dependency Injection (DI) is a term which has a couple of different definitions. In LLBLGen Pro Runtime Framework, the key goal for Dependency Injection is not to introduce a full blown DI framework with the feature-set of frameworks like StructureMap or Spring.NET, but to provide a feature in the LLBLGen Pro framework which would make it easy to inject instances of a given type into another type, or better: to set a property on an object X to an object Y.
For example, it should be easy to specify 'Inject an instance to MyConcurrencyPredicateFactory into all entity classes' and it will be done for you. Another goal was to have this feature available to the LLBLGen Pro developer without the requirement to use a factory. So the following line of code should make use of the DI mechanism and inject all objects CustomerEntity depends on:
var c = new CustomerEntity();
After this line of code, the object c contains for example an instance of MyConcurrencyPredicateFactory as its ConcurrencyPredicateFactoryToUse and also an instance of CustomerValidator as its Validator.
LLBLGen Pro's DI mechanism is meant to inject instances of classes, thus objects, by setting properties on objects to other objects, for example to set the Validator property of all CustomerEntity instances to an instance of CustomerValidator.
It's not optimized to e.g. set values to entity fields. This in general should be avoided as you then would have definitions of field values in the code but e.g. also in DI config files. If you need this kind of behavior, use a DI framework like Spring.NET or StructureMap.
Specifying Dependency Injection information on a class
LLBLGen Pro will search for types which are annotated with an attribute and will use these types as instance types to inject into target types. Inject means: Set the specified property P of the instance of the target type to an instance of the instance type. The attribute to make a type an instance type is called DependencyInjectionInfoAttribute.
Say you wrote a CustomerValidator class, by deriving from ValidatorBase. To inject an instance of CustomerValidator into every CustomerEntity instance created in your entire application, which means: setting the Validator property of every CustomerEntity instance to an instance of CustomerValidator, all you have to do is adding a DependencyInjectionInfo attribute to the CustomerValidator class.
It doesn't matter in which project the CustomerValidator class is defined. This frees you from adding the validator classes and other classes you want to use as instance types, to the generated code project.
LLBLGen Pro requires you to enable the discovery of types which have the DepedendencyInjectionInfoAttribute so it knows which types are instance types. There are several ways for LLBLGen Pro to discover instance types, which are discussed in the following sections.
Enabling Dependency Injection Info discovery
There are several ways LLBLGen Pro can find instance types. Two will be discussed in this sub-section. The third, using scopes, is discussed in the next section which discusses scopes in detail.
Auto-discovery of instance types
Auto-discovery is only available on .NET full. The .Netstandard 2.0+ build of the runtime doesn't support auto-discovery of assemblies for Dependency Injection.
LLBLGen Pro can automatically find all instance types available in your application. This is called auto-discovery of instance types. LLBLGen Pro at runtime will check every assembly reachable by the code of your application to see if it contains one or more types which have the DependencyInjectionInfoAttribute defined on them.
If that's the case, the type is used as an instance type. Auto-discovery can take some time, typically 2-4 seconds. This is a one-time hit which is the time taken to discover all types in all assemblies used in your application. After your application has been started, all information is available to the DI mechanism inside the LLBLGen Pro framework and no types have to be discovered after that. The 2-4 auto-discovery performance hit therefore only occurs once, when the application starts.
To avoid a discovery over a lot of assemblies in your application's app-domain, the automatic discovery of instance types in the assemblies used in your application is switched off by default. To switch it on, add the following line to the appSettings section of your application's config file:
<add key="autoDependencyInjectionDiscovery" value="true"/>
Once this line is present, LLBLGen Pro's runtime library will at startup of your application try to find all instance types in the application. This information is then stored in a central place which is used by all types, so this discovery has to be done once.
Auto-discovery doesn't work in website applications most of the time, so you should define Dependency Injection configuration settings in the web.config file as defined in the Manual discovery through dependencyInjectionInformation sections in the .config file below. You then also should specify the assemblies with the fullName attribute and the assembly's full name, instead of the file name like in the following example.
Example:
<assembly fullName="MyAuditorAssembly, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null"/>
This is necessary because the reference to the assembly with the Dependency Injection classes isn't there, as the pages are often compiled in separate assemblies. After deployment, it might be that the references are there, but that's not always the case. Better is to define this in the config file using the dependencyInjectionInformation section.
When the injectable types are defined inside the webapplication however, the auto-discovery works.
Auto-discovery could fail when it runs into an assembly which isn't usable with reflection. This is sometimes the case when the assembly for example is obfuscated. Auto-discovery in these situations will then result in a security exception or other exception. If you run into these situations, use manual discovery, described below.
Manual discovery through dependencyInjectionInformation sections in the .config file
You don't need to use auto-discovery to use LLBLGen Pro's DI mechanism: you can also specify in which assemblies the instance types are located. To do this, you have two choices:
-
To use the RuntimeConfiguration system. This is supported on .NET full and .Netstandard 2.0+. For more information about how to use it, please visit the dependency injection section of the RuntimeConfiguration system documentation.
-
to add a dependencyInjectionInformation section to your application's config file. This is described in more detail below and is supported on .NET Full.
To specify the dependency injection information in the config file, add the following line to the configSections element of the application's config file.
This has to be the first child element of configuration. You are already familiar with this if you use catalog/schema name overwriting as discussed in Application configuration through config files.
<section name="dependencyInjectionInformation"
type="SD.LLBLGen.Pro.ORMSupportClasses.DependencyInjectionSectionHandler, SD.LLBLGen.Pro.ORMSupportClasses, Version=5.1.0.0, Culture=neutral, PublicKeyToken=ca73b74ba4e3ff27"/>
After you've added that section line to the configSections element, you can add a dependencyInjectionInformation section to the config file. See the example below
<dependencyInjectionInformation>
<additionalAssemblies>
<assembly filename="MyAuthorizers.dll"/>
<assembly fullName="SD.Examples.Injectables, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</additionalAssemblies>
<instanceTypeFilters>
<instanceTypeFilter namespace="Northwind"/>
</instanceTypeFilters>
</dependencyInjectionInformation>
This section tells LLBLGen Pro that the assembly MyAuthorizers.dll contains instance types. LLBLGen Pro will then load this assembly and will try to find any type which is annotated with the DependencyInjectionInfoAttribute attribute. All instance types found are used in the application. The different elements in the section have the following meaning:
- additionalAssemblies This element allows you to specify assemblies to check for DependencyInjectionInfoAttribute annotated types (Instance types), and you can do that by using a full assembly name (so the assembly is either loaded from the application bin folder or GAC) or a filename (which has to be an absolute path or a filename local to the folder). Specify assemblies using assembly elements. An example of both is given above: for filenames, use the attribute filename, for assembly names, use the attribute fullName.
- instanceTypeFilters You can also specify instance type filters. By default no filters are specified, which means all instance types are enabled. If you specify one or more instance type filters, only the instance type filters matching the filter will be enabled. The specified namespace fragment means that you can specify the first part of a namespace to get a match. So if you specify "Northwind", both the namespaces "Northwind.DAL" and "Northwind.BL" will match but "GUI.Northwind" will not. You can use instance filters if you have multiple groups of instance types defined in the assembly or assemblies specified and you only want to use a subset of them.
The dependencyInjectionInformation section in a .config file allows you to be both flexible in when to use which instance types, and also be able to avoid the performance penalty at application startup. It also allows you to add validators, authorizers, auditors and other classes injectable into entities, after the application has been compiled and deployed.
For example, if you want to enable auditing to your web application after it has been deployed, you can simply write your Auditor class in a new VS.NET class library, add the DependencyInjectionInfoAttribute to it, compile it and add a dependencyInjectionInformation section to the web.config and the Auditor class will be used in your application, without any changes to your application's code.
A target type can be a regular class type but can also be an interface, e.g. IEntity2. Interface definitions are overruled by specific target types. This means that if you've defined two Validator classes, V1 and V2, where V1 is for all entity classes (so you defined the target type to be IEntity (SelfServicing) or IEntity2 (Adapter)) and V2 is for CustomerEntity instances only (so you've defined the target type to be CustomerEntity), it will make the V2 definition overrule the V1 definition for CustomerEntity instances, as V2 is specified with a class target type, and V1 with an interface target type.
Instance type example
Below is an example of an instance type. There are more examples shown in the Authorization and Auditing sections. The instance type is a general ConcurrencyPredicateFactory, as it implements the IConcurrencyPredicateFactory interface, so it can be used to produce filters at runtime for concurrency control. (See for more information about concurrency control: concurrency control in SelfServicing or concurrency control in Adapter.)
The DependencyInjectionInfoAttribute has various parameters to specify how LLBLGen Pro should inject the instance and also if you want a new instance every time or the same instance. See for details about DependencyInjectionInfoAttribute the LLBLGen Pro reference manual.
[DependencyInjectionInfo(typeof(IEntity2), "ConcurrencyPredicateFactoryToUse",
ContextType = DependencyInjectionContextType.NewInstancePerTarget,
TargetNamespaceFilter = "Northwind")]
[Serializable]
public class GeneralConcurrencyPredicateFactory : IConcurrencyPredicateFactory
{
public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate,
object containingEntity)
{
// the factory code as you'd write it.
}
}
This class can be placed in your own VS.NET project or even in a separate project, it doesn't have to be stored in the generated code project. All entities in the Northwind namespace (or namespaces starting with that fragment) will get their ConcurrencyPredicateFactoryToUse property set to a new instance of this instance type.
This is defined by the first parameter of the attribute, which specifies the target type, which as you can see, can be an interface, but can also be a base class of a hierarchy or an entity type. You can also specify (not illlustrated here) if subtypes of the specified target type will receive the instance as well, or not.
Dependency Injection scopes
It is sometimes necessary to have at different places in your code the ability to modify the defined Dependency Injection information in a small scope of the code, or you want the Dependency Injection information to depend on the state of the application or data at runtime.
You can do this by using so called DependencyInjectionScope classes. These scopes inherit from the DependencyInjectionScope base class and define DI information you would otherwise define via attributes. This is another way to define instance and target types without the necessity of attributes on classes.
Scopes can be nested and they then override info of their outer scope, if they define the same info. Scopes also override information discovered by either automatic discovery or by dependencyInjectionInformation sections, as long as they're active.
A scope is active after it's been instantiated and it's active on the thread it's been created on. After it has been disposed, the scope data is no longer used. This means that for example you could, in a single-threaded windows forms application, start a scope when the application is started and simply dispose it when the application is terminated.
Scopes add info to the already known Dependency Injection information and if that information is for the same target type + property, it overrules the already known information. When an entity is instantiated all known Dependency Injection information is used to inject the instance types into the entity instance.
For example, in the assembly specified in the dependencyInjectionInformation section of
the application's config file, a CustomerValidator class is defined
for all CustomerEntity instances and a CustomerAuditor, also for all
CustomerEntity instances. In a scope S
, the CustomerAuthorizer class
is specified as instance type, for all CustomerEntity instances. S
also defines SpecialCustomerValidator, for all CustomerEntity
instances.
This setup means that when S
is active, a CustomerEntity instance will
get an instance of SpecialCustomerValidator as the value of its
Validator, as S
overrules the already discovered data, an instance
of CustomerAuditor as the value of its AuditorToUse property, and
an instance of CustomerAuthorizer as the value of its
AuthorizerToUse property.
Defining two Dependency Injection scopes example
The following code illustrates the definition of two DependencyInjectionScope derived classes. These classes are used in the next example.
/// <summary>Testscope to illustrate nested scope usage.</summary>
public class OuterTestScope : DependencyInjectionScope
{
protected override void InitializeScope()
{
AddInjectionInfo(typeof(EmployeeSpecificCPF), typeof(EmployeeEntity),
"ConcurrencyPredicateFactoryToUse", DependencyInjectionTargetKind.Hierarchy,
DependencyInjectionContextType.NewInstancePerTarget);
}
}
/// <summary>Testscope to illustrate nested scope usage.</summary>
public class NestedTestScope : DependencyInjectionScope
{
protected override void InitializeScope()
{
// define an absolute injection. This means that employee and boardmember will get the
// EmployeeSpecificCPF, manager will get the NestedManagerSpecificCPF
AddInjectionInfo(typeof(NestedManagerSpecificCPF), typeof(ManagerEntity),
"ConcurrencyPredicateFactoryToUse", DependencyInjectionTargetKind.Absolute,
DependencyInjectionContextType.NewInstancePerTarget);
}
}
''' <summary>Testscope to illustrate nested scope usage.</summary>
Public Class OuterTestScope
Inherits DependencyInjectionScope
Protected Overrides Sub InitializeScope()
AddInjectionInfo(GetType(EmployeeSpecificCPF), GetType(EmployeeEntity), _
"ConcurrencyPredicateFactoryToUse", DependencyInjectionTargetKind.Hierarchy, _
DependencyInjectionContextType.NewInstancePerTarget)
End Sub
End Class
''' <summary>Testscope to illustrate nested scope usage.</summary>
Public class NestedTestScope
Inherits DependencyInjectionScope
Protected Overrides Sub InitializeScope()
'' define an absolute injection. This means that employee and boardmember will get the
'' EmployeeSpecificCPF, manager will get the NestedManagerSpecificCPF
AddInjectionInfo(GetType(NestedManagerSpecificCPF), GetType(ManagerEntity), _
"ConcurrencyPredicateFactoryToUse", DependencyInjectionTargetKind.Absolute, _
DependencyInjectionContextType.NewInstancePerTarget)
End Sub
End Class
Using nested scopes example
The scopes we just defined can be used in regular code and can be nested. The code below illustrates this. The example assumes that there is Dependency Injection information discovered either through auto-discovery or via the config section so all entities will receive an instance of GeneralConcurrencyPredicateFactory as the value for their ConcurrencyPredicateFactoryToUse property.
var m = new ManagerEntity();
// no scope active, m.ConcurrencyPredicateFactoryToUse is set to an instance of
// GeneralConcurrencyPredicateFactory.
var f = new FamilyCarEntity();
// no scope active and FamilyCarEntity isn't mentioned in any scope anyway, so
// f.ConcurrencyPredicateFactoryToUse is set to an instance of
// GeneralConcurrencyPredicateFactory.
// using one scope
using(OuterTestScope outerScope = new OuterTestScope())
{
m = new ManagerEntity();
// scope is active, ManagerEntity is a derived entity of Employee, so
// m.ConcurrencyPredicateFactoryToUse is set to an instance of EmployeeSpecificCPF
var bm = new BoardMemberEntity();
// similar to bm, which is a BoardMemberEntity and which is a derived entity of
// Manager.
f = new FamilyCarEntity();
// FamilyCar isn't mentioned in OuterTestScope, so it gets the regular
// GeneralConcurrencyPredicateFactory instance.
}
// using nested scopes
using(var outerScope = new OuterTestScope())
{
using(var innerScope = new NestedIH1TestScope())
{
m = new ManagerEntity();
// Two scopes are active. NestedTestScope defines a new instance type for
// ConcurrencyPredicateFactoryToUse for the ManagerEntity specifically, and thereby
// overrules OuterTestScope for ManagerEntity so m.ConcurrencyPredicateFactoryToUse
// is set to an instance of NestedManagerSpecificCPF
var bm = new BoardMemberEntity();
// Although BoardMember is a derived type of Manager, the NestedTestScope specifically
// defined that the instance type for ConcurrencyPredicateFactoryToUse is only for
// ManagerEntity instances, not for the hierarchy that entity is in, so
// bm.ConcurrencyPredicateFactoryToUse is set to the definition set in OuterTestScope:
// an instance of EmployeeSpecificCPF
f = new FamilyCarEntity();
// FamilyCar isn't mentioned in NestedTestScope either, so it will gets the regular
// GeneralConcurrencyPredicateFactory instance.
}
}
Dim m As New ManagerEntity()
' no scope active, m.ConcurrencyPredicateFactoryToUse is set to an instance of
' GeneralConcurrencyPredicateFactory.
Dim f As New FamilyCarEntity()
' no scope active and FamilyCarEntity isn't mentioned in any scope anyway, so
' f.ConcurrencyPredicateFactoryToUse is set to an instance of
' GeneralConcurrencyPredicateFactory.
' using one scope
Using outerScope As New OuterTestScope()
m = New ManagerEntity()
' scope is active, ManagerEntity is a derived entity of Employee, so
' m.ConcurrencyPredicateFactoryToUse is set to an instance of EmployeeSpecificCPF
Dim bm As New BoardMemberEntity()
' similar to bm, which is a BoardMemberEntity and which is a derived entity of
' Manager.
f = New FamilyCarEntity()
' FamilyCar isn't mentioned in OuterTestScope, so it gets the regular
' GeneralConcurrencyPredicateFactory instance.
End Using
' using nested scopes
Using outerScope As New OuterTestScope()
Using innerScope As New NestedIH1TestScope()
m = New ManagerEntity()
' Two scopes are active. NestedTestScope defines a new instance type for
' ConcurrencyPredicateFactoryToUse for the ManagerEntity specifically, and thereby
' overrules OuterTestScope for ManagerEntity so m.ConcurrencyPredicateFactoryToUse
' is set to an instance of NestedManagerSpecificCPF
Dim bm As New BoardMemberEntity()
' Although BoardMember is a derived type of Manager, the NestedTestScope specifically
' defined that the instance type for ConcurrencyPredicateFactoryToUse is only for
' ManagerEntity instances, not for the hierarchy that entity is in, so
' bm.ConcurrencyPredicateFactoryToUse is set to the definition set in OuterTestScope:
' an instance of EmployeeSpecificCPF
f = New FamilyCarEntity();
' FamilyCar isn't mentioned in NestedTestScope either, so it will gets the regular
' GeneralConcurrencyPredicateFactory instance.
End Using
End Using