Generated code - Setting up and using Dependency Injection
Preface
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:
// C#
CustomerEntity c = new CustomerEntity();
' VB.NET
Dim c As 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
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.
Important: |
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. |
Important: |
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 to add a
dependencyInjectionInformation section to your application's config file. To do so, 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=4.0.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.
// C#
[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.
}
}
' VB.NET
<DependencyInjectionInfo(GetType(IEntity2), "ConcurrencyPredicateFactoryToUse", _
ContextType = DependencyInjectionContextType.NewInstancePerTarget, _
TargetNamespaceFilter = "Northwind"), Serializable()> _
Public Class GeneralConcurrencyPredicateFactory
Implements IConcurrencyPredicateFactory
Public Function CreatePredicate(predicateTypeToCreate As ConcurrencyPredicateType, _
containingEntity As Object) As IPredicateExpression Implements _
IConcurrencyPredicateFactory.CreatePredicate
' the factory code as you'd write it.
End Function
End Class
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.
// C#
/// <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);
}
}
' VB.NET
''' <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.
// C#
ManagerEntity m = new ManagerEntity();
// no scope active, m.ConcurrencyPredicateFactoryToUse is set to an instance of
// GeneralConcurrencyPredicateFactory.
FamilyCarEntity 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
BoardMemberEntity 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(OuterTestScope outerScope = new OuterTestScope())
{
using(NestedTestScope 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
BoardMemberEntity 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.
}
}
' VB.NET
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