-
Notifications
You must be signed in to change notification settings - Fork 4
LightCore 1.0 to 1.5.1 Documentation
The LightCore documentation contains an Introduction, informations about XML-Configuration and a lot more.
- Introduction
- Advanced features
- XML configuration
- ASP.NET WebForms / MVC Integration
- Lifecycles
- Extensibility
You will find additional informations under the following blogposts.
- Topics on myCSharp.de with the search term "lightcore"
- Topics on stackoverflow.com with the search term "lightcore""
You will learn the basics of LightCore with this introduction. For the advanced features, please choose on the right menu bar.
Only one reference to LightCore.dll is needed. Aside this, there are a few optional components:
LightCore.dll
- Core component, not optional
LightCore.Configuration.dll
- Optional, only needed if LightCore should be configured with xml
LightCore.Integration.Web.dll
- Optional integration for ASP.NET and ASP.NET MVC
LightCore.CommonServiceLocator.dll
- Optional adapter for LightCore to use with the CSL (Common Service Locator)
Additional files within the deployment of LightCore
LightCore.xsd
- Should be added to your solution or project. That gives you Intellisense for the LightCore Configuration in Visual Studio
SampleConfiguration.xml
- Examples for a LightCore Konfiguration, with .NET app- / web.config and without the .NETconfiguration api
You can add registrations on the application startup with the ContainerBuilder class. If you call the Build() method on the ContainerBuilder, it will return an configured container. It is usual to hold the container once per application in the application scope (static):
For simple usage, you will register types and use the container to get instances.
See this simple example:
var builder = new ContainerBuilder();
builder.Register<IFoo, Foo>();
IContainer container = builder.Build();
IFoo foo = container.Resolve<IFoo>();
You may use two generic type arguments, within the Register() method, the first represents the contract, and the second the implementation. After that, you can get an instance with by one Resolve() call.
As we’ve seen, the Register() method can be called with two generic type arguments. But there is another way to register dependencies. You can pass two Type instances to the Register() method. The two type arguments are used typically if you generate a Type on runtime.
Type typeOfContract = typeof(IFoo);
Type typeOfImplementation = typeof(Foo);
builder.Register(typeOfContract, typeOfImplementation);
Another option is the use of delegates / lambda expressions for registerations. These are fast and flexible on resolution. With the argument (in the code below c), it's possible to take advance of the container to resolve inner dependencies. This way, you can build a huge object hierarchy only with delegates.
builder.Register<IFoo>(c => new Foo());
builder.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
builder.Register<IFoo>(c => FooFactory.GetFoo());
Its also possible, to register dependencies over xml. See: here. If this is not enough, you can write your own implementation of a RegistrationModule. Such a implementation will contain registration-logic.
Dependency injection cannot be used everywhere, and it's also not supposed to do so. There is an alternative to use LightCore as a service locator to get instances.
That can be instances, which are only valid in a methodscope -therefore local variables. To achieve such as dynamic creation, the container instance is registered to the contract IContainer.
Imagine an article service, which have a "SaveAll" method. On this method, an instance of ISaveService is needed, but only for the duration of the method call.
public class ArticleService : IArticleService
{
private readonly IContainer _container;
public ArticleService(IContainer container) {
this._container = container;
}
public SaveAll() {
var saveService = this._container.Resolve<ISaveService>();
// do something with the service.
}
}
Given the fact, that the container is registered to itself, the container detects automatically, whether a dependency is from contract IContainer or not. It's possible with ease, to use the LightCore container as service locator selective.
Easy explained: Allover where a service locator is needed, just put an IContainer dependency in your constructor. Whether there other arguments too, or not, it just works everywhere.
LightCore supports passing arguments at runtime, since version 1.4. They can be named or not, and in every case typed. If there is no type declared, LightCore chooses automatically string. That happends on the XML-Configuration.
You can see a few different constructors where called, on the following code. Additional to this possibility, its also possible to pass in a dictionary with <string, object> direct. The key (string) must have the name of the argument, and the value (object), the value of the argument.
var fooFromAnonymousType = container.Resolve<FooWithArguments>(new AnonymousArgument(new
{
arg1 = "Peter",
arg2 = true
}));
var fooFromPassedArgs = container.Resolve<FooWithArguments>("Peter", true);
var fooFromAList = container.Resolve<FooWithArguments>(new List<object> {"Peter", true});
LightCore can create registrations on demand. With this, support of open generic types is possible, and a lot more features.
All of the above possiblities can be used explicit with a Resolve()-Call, or implicit with a declared constructor argument, or a property.
Given a registered open generic type is registered, it is possible to use it with any typearguments. If that happend, every time - if not exists - the container adds implicit a registration.
IRepository<Foo> repository = conainer.Resolve<IRepository<Foo>>;
If there is a request for a dependency as a array or ienumerable type. The container returns all instances, which are registered.
public FooBar(IEnumerable<IFooPlugin> fooPlugins)
{
/...
}
If a Func dependency is requested, with one or more typearguments, LightCore creates a factory on-the-fly for injection.. Therefore it's possible to use factories instead of container injection.
It's special, that the typearguments (T1, T2, TResult), all - except TResult - can used as constructor arguments. That means, LightCore generates a factory, and you pass arguments you like. The factory returns every time a new instance (transient behaviour). See the following example.
public FooBar(Func<FooRepository> factory, Func<string, int, FooRepository> factoryWithArguments)
{
FooRepository repositoryOne = factory();
FooRepository repositoryTwo = factory();
FooRepository repositoryWithArguments = factoryWithArguments("Peter", 4);
}
Since .NET 4.0, a new type named Lazy is available. To create a new lazy instance, you have to pass a factory (Func). On the first reques to the value (.Value property), the factory will be called. You get the same instance on subsequent requests to the property. With this strategy, you can save performance, if it's possible, not all services are needed every time, and it's the case, that the creation of an service takes very much time.
public FooBar(Lazy<FooService> lazyFooService)
{
FooService fooService = lazyFooService.Value; // factory executed.
FooService fooServiceTwo = lazyFooService.Value; // same value as above.
}
There exists not only a few cases, ony a concrete class is available, and the concrete class have dependencies e.g. to abstractions. If e.g. a FooService is requested, and the FooService has a dependency to a ILogger, LightCore automatically registers the concrete type (FooService) to itself and resolves it. That means: The best constructor will be choosed and the dependencies injected, as by a usual dependency. If a concrete type used as dependency anywhere, the same will happend. This kind of resolution holds the lowest priority, if LightCore asking for RegistrationSource's.
FooService fooService = container.Resolve(); // FooService is a concrete type.
public FooBar(FooService fooService)
{
ILogger = fooService.Logger; // logger was injected by LightCore.
}
The dependencices, arguments and more can be declared via XML, that gives you the advance to replace the configuration without recompilation.
A disadvance is the missing strong typing, therefore write-, or runtime-failts can be happen.
With the XML-Syntax, all kinds of registrations are possible to use, except of delegates / lambda expressions and closed generic types. So, mostly everything that you can express with code, you can also express with XML.
LightCore can read XML out of a standard .NET-configurationfile (app.config or web.config). For this, it's needed to add the LightCore sectionhandler in the configurationfile. It's important, that you name the section as LightCoreConfiguration.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="LightCoreConfiguration" type="LightCore.Configuration.XamlConfigSectionHandler, LightCore.Configuration" />
</configSections>
[...]
A example configuration:
<LightCoreConfiguration xmlns="clr-namespace:LightCore.Configuration;assembly=LightCore.Configuration">
<LightCoreConfiguration.TypeAliases>
<TypeAlias Alias="HttpRequest" Type="LightCore.Integration.Web.Lifecycle.HttpRequestLifecycle, LightCore.Integration.Web" />
</LightCoreConfiguration.TypeAliases>
<LightCoreConfiguration.Registrations>
<Registration ContractType="LightCore.TestTypes.IRepository{2}, LightCore.TestTypes" ImplementationType="LightCore.TestTypes.Repository{2}, LightCore.TestTypes" />
<Registration ContractType="LightCore.TestTypes.IFoo" ImplementationType="LightCore.TestTypes.Foo">
<Registration.Arguments>
<Argument Value="Alexander" />
<Argument Value="44" Type="int" Name="AgeProperty"/>
<Argument Value="true" Type="bool" />
</Registration.Arguments>
</Registration>
</LightCoreConfiguration.Registrations>
<LightCoreConfiguration.RegistrationGroups>
<RegistrationGroup Name="Real">
<RegistrationGroup.Registrations>
<Registration ContractType="System.ComponentModel.IDisposable" ImplementationType="System.IO.Stream, mscorlib"/>
</RegistrationGroup.Registrations>
</RegistrationGroup>
<RegistrationGroup Name="Fake">
<RegistrationGroup.Registrations>
<Registration ContractType="System.ComponentModel.IDisposable" ImplementationType="MyDisposableType, MyProject"/>
</RegistrationGroup.Registrations>
</RegistrationGroup>
</LightCoreConfiguration.RegistrationGroups>
</LightCoreConfiguration>
You can register open generic types using the common .NET Syntax: "LightCore.TestTypes.Repository`2, LightCore.TestTypes"
or using the LightCore variant of syntax:: "LightCore.TestTypes.Repository{2}, LightCore.TestTypes"
The number in the registration represents how many typparameters the type contains, e.g. bspw. Repository<Foo, int>.
A concept withing the XML-configuration makes it possible to declare aliase for a type. That can be very helpful by often used types such as new lifecycles for LightCore.
The aliases below are out-of-the-box registered and can be used immediately.
Is used for arguments of a registration.
Alias | Type |
---|---|
Int32, Integer, int | System.Int32, mscorlib |
String, string | System.String, mscorlib |
Boolean, bool | System.Boolean, mscorlib |
Guid | System.Guid, mscorlib |
Is used to declare the default lifecycle (see top) or a lifecycle for a registration. (LightCore.Integration.Web.dll contains the HttpRequestLifecycle and have to be manually added to the aliases, if needed.
Types in the XML-file have to be declared throught an alias or fullqualified. For example: "Name.Space.Class, Assembly" => "LightCore.Lifecycle.SingletonLifecycle, LightCore".
Alias | Type |
---|---|
Transient | LightCore.Lifecycle.TransientLifecycle, LightCore |
Singleton | LightCore.Lifecycle.SingletonLifecycle, LightCore |
ThreadSingleton | LightCore.Lifecycle.ThreadSingletonLifecycle, LightCore |
Attribute name | Description | Valid values |
---|---|---|
ActiveRegistrationGroups | Optional attribute, which is used to declare the RegistrationGroups to use (the active and registered one). | Groupname which should be active, one group or a few, delimited by comma: "XML" or "XML, Utils". |
DefaultLifecycle | Optional attribute, which declares the default-lifecycle for a registration. | Alias or full qualified type for a lifecycle (implements ILifecycle interface). |
Attribute name | Description | Valid values |
---|---|---|
Alias | Alias name to be used. | A string. |
Type | Type to which the alias points. | A full qualified declaration, e.g.: "LightCore.Lifecycle.SingletonLifecycle, LightCore". |
A registration can hold arguments and a registrationgroup registrations and again arguments.
Attribute name | Description | Valid values |
---|---|---|
ContractType | Alias for a type or a full qualified type declaration, which is the contract. | A full qualified decleration, e.g.: "TestProject.IFoo, TestProject". |
ImplementationType | Alias for a type or a full qualified type declaration, which is implementation. | A full qualified decleration, e.g.: "TestProject.IFoo, TestProject". |
Lifecycle | Optional Attribute for the default lifecycle which is used for registrations | Alias or a full qualified type decleration for a lifecycle (implements ILifecycle interface). |
Enabled | Optional attribute which means the registration is disabled on false and enabled on true. (default is: true). | true or false. |
Attribute name | Description | Valid values |
---|---|---|
Name | A name for the group. | A string. |
Enabled | Optional attribute which means the registration is disabled on false and enabled on true. (default is: true). | true). true or false. |
LightCore can read registrations with RegistrationModule implementations (see: Extensibility). You can choose between the following variants:
- with the app.config / web.config.
var builder = new ContainerBuilder();
RegistrationModule xamlModule = new XamlRegistrationModule();
builder.RegisterModule(xamlModule);
- with a filename that points to a file contains the XML.
var builder = new ContainerBuilder();
string configurationFilePath = "LightCore-Configuration.xml";
RegistrationModule xamlModule = new XamlRegistrationModule(configurationFilePath);
builder.RegisterModule(xamlModule);
- with an stream instance, which contains the XML (e.g. from a database).
var builder = new ContainerBuilder();
Stream configurationStream = GetStreamFromDatabase();
RegistrationModule xamlModule = new XamlRegistrationModule(configurationStream);
builder.RegisterModule(xamlModule);
The different way's to register types are combinable.
You will find all you need within the assembly LightCore.Integration.Web.dll.
The namespace LightCore.Integration exposes basic functions that are needed for using LightCore in ASP.NET Webforms applications.
If you added the DepenencyInjectionModule but forgotten to implement the IContainerAccessor-interface on the Global.asax, the exception ContainerAccessorNotImplementedException will be thrown.
The DependencyInjectionModule represents a HttpModule which is responsible doing Dependency Injection in ASP.NET WebForms applications. On this process, the module uses property-injection, this is the only case where LightCore uses property-injection. You have to register the HttpModule in your web.config. Consider: On IIS7 and integrated mode, you should register the module also in System.webServer section:
<add
name="LightCoreDependencyInjectionModule"
type="LightCore.Integration.Web.DependencyInjectionModule, LightCore.Integration.Web"/>
On WebForms applications, the IContainerAccessor-interface must be implemented in the application class (e.g. Global.asax.cs). The interface exists for the DependencyInjectionModule to find the current IContainer-instance.
public interface IContainerAccessor
{
IContainer Container { get; }
}
Moreover you can use this interface on your own, to get instances in a service locator way. This is e.g. the only way to use a DI-Container if you are in a ASP.NET MVC ActionFilter-attribute, since you (and the framework) could not be responsible for the instantiation.
IContainerAccessor accessor = (IContainerAccessor)HttpContext.Current.ApplicationInstance;
IContainer container = accessor.Container;
IUserService userService = container.Resolve<IUserService>();
This class represents a lifecycle for creating instances with LightCore. If you declare this lifecycle in a registration, the instance are shared with a HttpRequest, this is a per-request behaviour.
You can additionaly implement own strategies for a lifecycle, only by implement the ILifecycle interface.
You will find LightCore support for ASP.NET MVC in this namespace.
To use dependency injection on ASP.NET MVC, you must register the ControllerFactory in your global.asax:
ControllerBuilder.Current.SetControllerFactory(new ControllerFactory(_container));
The ControllerFactory-constructor needs a IContainer-instance of your application (on top "_container").
To register ASP.NET MVC controllers in n assemblies automatically to make it ready for using with LightCore, you must use the ControllerRegistrationModule:
var builder = new ContainerBuilder();
ControllerRegistrationModule controllerModule = new ControllerRegistrationModule(new[] { Assembly.GetExecutingAssembly() });
builder.RegisterModule(mvcModule);
_container = builder.Build();
With classes that implement the interface ILifecycle, its possible to controll the lifetime / reuse of instances created from a LightCore-Container.
LightCore supports out-of-the-box four lifecycle-implementations. The TransientLifecycle, SingletonLifecycle, ThreadSingletonLifecycle and HttpRequestLifecycle.
The lifecycle to use have to be declared on the registration.
Via Code:
var builder = new ContainerBuilder();
builder.Register<IFoo, Foo>().ControlledBy<TransientLifecycle>();
Via Xml:
<Registration Lifecycle="Transient" ContractType="LightCore.TestTypes.IFoo" ImplementationType="LightCore.TestTypes.Foo">
The TransientLifecycle creates every time a new instance of the registered type. This is the standard lifecycle of LightCore.
var builder = new ContainerBuilder();
builder.Register<ILampenfassung, Glühbirne>().ControlledBy<TransientLifecycle>();
Important
For all other lifecycle it is important to know that he lifetime and reuse possiblity is chained to the container. If you create a new container, all instances are no more available. Given that fact, its common to hold one container instance on the application scope / static.
The SingletonLifecycle is a kind of singleton pattern. The type registered on SingletonLifecycle, once requested, returns the same instance for all request.
var builder = new ContainerBuilder();
builder.Register<ILampenfassung, Glühbirne>().ControlledBy<SingletonLifecycle>();
The ThreadSingletonLifecycle is kind of SingletonLifecycle. The difference is that every thread have its own instance, and it will be reused on the corresponding threads. One thread = one instance.
var builder = new ContainerBuilder();
builder.Register<ILampenfassung, Glühbirne>().ControlledBy<ThreadSingletonLifecycle>();
With the HttpRequestLifecycle one instance is shared from begin to the end of one request, and only in that request. If the request ends, the instance is abandoned.
var builder = new ContainerBuilder();
builder.Register<ILampenfassung, Glühbirne>().ControlledBy<HttpRequestLifecycle>();
Important
To use the HttpRequestLifecycle the LightCore.Integration.Web.dll have to referenced in in your project.
It is possible to change the default lifecycle. Call the method DefaultControllerBy of the ContainerBuilder.
A untyped variant of this method, which takes a Type instance, is available too.
var builder = new ContainerBuilder();
builder.DefaultControlledBy<SingletonLifecycle>();
If you have a need for a own lifecycle, its easy and possible. Your new lifecycle class have to implement the interface ILifecycle from the namespace LightCore.Lifecycle.
In the following example, we have developed a fictive lifecycle, which - because of security reason - not accept to reuse an instance more than three times.
public class SecurityLifecycle : ILifecycle
{
private int _count;
private object _instance;
private readonly object _lock = new object();
public object ReceiveInstanceInLifecycle(Func<object> newInstanceResolver)
{
lock(_lock)
{
if (this._instance == null || this._count > 3)
{
this._instance = newInstanceResolver();
this._count = 0;
}
this._count += 1;
return this._instance;
}
}
}
As example, you are free to explore the buildin lifecycles.
Its possible to set the lifecycle on the registration of types with XML too.
<?xml version="1.0" encoding="utf-8"?>
<LightCoreConfiguration xmlns="clr-namespace:LightCore.Configuration;assembly=LightCore.Configuration">
<LightCoreConfiguration.TypeAliases>
<TypeAlias Alias="SingletonLifecycle" Type="LightCore.Lifecycle.SingletonLifecycle, LightCore" />
</LightCoreConfiguration.TypeAliases>
<LightCoreConfiguration.Registrations>
<Registration
ContractType="LightCore.TestTypes.ILampenfassung{2}, LightCore.TestTypes"
ImplementationType="LightCore.TestTypes.Gluehbirne{2}, LightCore.TestTypes"
Lifecycle="SingletonLifecycle" />
</LightCoreConfiguration.Registrations>
</LightCoreConfiguration>
Its a good practice to use aliases for the lifecycle, you want to use. The lifecycles included in LightCore have already aliases registered, so you can use e.g. "Singleton", "ThreadSingleton", etc..
<LightCoreConfiguration.TypeAliases>
<TypeAlias Alias="SingletonLifecycle" Type="LightCore.Lifecycle.SingletonLifecycle, LightCore" />
</LightCoreConfiguration.TypeAliases>
On the type registration, the alias of the lifecycle is declared.
<Registration
ContractType="…"
ImplementationType="…"
Lifecycle="SingletonLifecycle" />
Its possible to change the default lifecycle within the XML configuration.
<LightCoreConfiguration
xmlns="clr-namespace:LightCore.Configuration;assembly=LightCore.Configuration"
DefaultLifecycle="LightCore.Lifecycle.SingletonLifecycle, LightCore">
You can find additional information to XML configuration on: XML Configuration.
LightCore can be extended on two points:
- Lifecycles
- Registration Modules
You will find more informationen about the existing lifecycles and the principle behind lifecycles on Lifecycles.
To create a new lifecycle, your new class have to implement the interface ILifecycle.
/// <summary>
/// Represents a lifecycle where instances can be reused.
/// </summary>
public interface ILifecycle
{
/// <summary>
/// Handle the reuse of instances.
/// </summary>
/// <param name="newInstanceResolver">The function for lazy get an instance.</param>
object ReceiveInstanceInLifecycle(Func<object> newInstanceResolver);
}
A factory for the needed dependency is passed in the method. This factory can be called and returns that result directly. That would be the same as the TransientLifecycle from LightCore.
The Xml-registration of LightCore contains a RegistrationModule implementation. This implementation makes possible, to read data out of the xml file and to postprocess the data, if needed.
The abstract class for a new RegistrationModule is simple:
/// <summary>
/// Represents an abstract registration module for implementing custom registrations.
/// </summary>
public abstract class RegistrationModule
{
/// <summary>
/// Registers all candidates.
/// </summary>
/// <param name="containerBuilder">The ContainerBuilder.</param>
public abstract void Register(IContainerBuilder containerBuilder);
}
LightCore calls the Register()-method, in case of, that the module was added (registered) to LightCore.
A possible implementation of a RegistrationModule could be a extension for convention-based registration.