Skip to content

LightCore 1.0 to 1.5.1 Documentation

Peter Bucher edited this page Jan 24, 2018 · 11 revisions

Documentation for LightCore 1.0 - 1.5.1

The LightCore documentation contains an Introduction, informations about XML-Configuration and a lot more.

Index

You will find additional informations under the following blogposts.

Blogposts on LightCore 1.0 (German)

Article serie about LightCore 1.0 from Roberto Bez (German)

Additional Resources

Introduction

You will learn the basics of LightCore with this introduction. For the advanced features, please choose on the right menu bar.

Know the framework

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

Simple Example

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.

Registering Dependencies

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.

Advanced features

Container injection

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.

Passing arguments at runtime

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});

Create registrations on demand

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.

Open Generic

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>>;

IEnumerable / Array

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)
{
    /...
}

Factory

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);
}

Lazy (Since .NET 4.0)

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.
}

Resolve anything (automatic resolve any concrete type)

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.
}

XML Configuration

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.

Syntax

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>

Open generic types

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>.

Aliase

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.

Type Aliases

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

Lifecycle Type Aliases

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

LightCoreConfiguration Attributes

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).

TypeAlias Attributes

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".

Registration Attributes

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.

RegistrationGroup Attributes

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.

Configure LightTCore for XML

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.

ASP.NET WebForms / MVC Integration

You will find all you need within the assembly LightCore.Integration.Web.dll.

LightCore.Integration.Web Namespace

The namespace LightCore.Integration exposes basic functions that are needed for using LightCore in ASP.NET Webforms applications.

ContainerAccessorNotImplementedException

If you added the DepenencyInjectionModule but forgotten to implement the IContainerAccessor-interface on the Global.asax, the exception ContainerAccessorNotImplementedException will be thrown.

DependencyInjectionModule

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"/>

IContainerAccessor

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>(); 

LightCore.Integration.Web.Lifecycle

HttpRequestLifecycle

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.

LightCore.Integration.Web.Mvc

You will find LightCore support for ASP.NET MVC in this namespace.

ControllerFactory

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").

ControllerRegistrationModule

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();

Lifecycles

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">

TransientLifecycle

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.

SingletonLifecycle

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>();

ThreadSingletonLifecycle

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>();

HttpRequestLifecycle

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.

Additional themes to lifecycle

Define a default lifecycle

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>();

Develop a lifecycle your own

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.

Set lifecycle in XML configuration

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.

Extensibility

LightCore can be extended on two points:

  1. Lifecycles
  2. Registration Modules

Lifecycles

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.

Registration Modules

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.