Dependency injection container

All major frameworks' components have constructor injection with IContainer instance which represents a Dependency Injection container. It provides the following groups of methods:

  • RegisterType - registers mapping between interface and type which implements this interface or registers concrete implementation only
  • RegisterInstance - registers an existing instance which implements some interface
  • Register - special method which allows to define the interception features programmatically

The instance of container is passed through framework classes and used for registering/resolving of other components, e.g.:

/// <summary>
/// Default implementation of navigation service
/// </summary>
public class NavigationService : INavigationService
{
    
    protected IConfigSection Config { get; private set; }
    protected IContainer Container { get; private set; }

    public NavigationService(IConfigSection config, IContainer container)
    {
        PageMapping = container.Resolve<IPageMapping>();
    }
} 

This approach has advantages and disadvantages. For example, it allows simplifying creation of unit test (see example below) by avoiding the usage of static instances and /or dependencies at concrete implementations of classes and simplifies the ability to extend/replace frameworks' subsystems. However it hides what the services consume.

 

Aspect-oriented programming features

Since the version 0.6.1, the framework supports the ability to define custom interceptors which can be attached to methods. This is possible if special proxy class is created for intercepted service at compile time:

using System.Reflection;
using PhoneCore.Framework.IoC.Interception.Proxies;
namespace PhoneCore.Framework.Storage
{
    public class FileSystemServiceProxy : ProxyBase, PhoneCore.Framework.Storage.IFileSystemService
    {
        public System.IO.Stream OpenFile(System.String path, System.IO.FileMode mode)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path, mode); 
            return RunBehaviors(methodInvocation).GetReturnValue<System.IO.Stream>();
        }

        public void CreateDirectory(System.String path)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path); 
            RunBehaviors(methodInvocation);
        }

        public System.Boolean DirectoryExists(System.String path)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path); 
            return RunBehaviors(methodInvocation).GetReturnValue<System.Boolean>();
        }
    }
}

As you can see, it has stub implementation of interface methods and is inherited from ProxyBase class. The implementation runs the chain of behaviors using the special object of MethodInvocation type. This object contains all necessary information of the executing method of real object. Custom behavior can validate, log, profile, disable and etc. execution of method.

There are two ways how you can attach behaviors to class for which proxy is already generated:

  • Configuration:
 <interception>
        <behaviors>
          <behavior name="execute" type="PhoneCore.Framework.IoC.Interception.Behaviors.ExecuteBehavior,PhoneCore.Framework" />
        </behaviors>
        <components>
          <component interface="PhoneCore.Framework.UnitTests.Stubs.Container.IClassA,PhoneCore.Framework.UnitTests"
                 proxy="PhoneCore.Framework.UnitTests.Stubs.Container.ClassAProxy,PhoneCore.Framework.UnitTests"
                 name ="ClassAProxy">
            <behaviors>
              <clear />
              <behavior name="execute" type="PhoneCore.Framework.IoC.Interception.Behaviors.ExecuteBehavior,PhoneCore.Framework" />
              <behavior name="trace" type="PhoneCore.Framework.IoC.Interception.Behaviors.TraceBehavior,PhoneCore.Framework" />
            </behaviors>
          </component>
          <component interface="PhoneCore.Framework.UnitTests.Stubs.Container.IClassB,PhoneCore.Framework.UnitTests"
                 proxy="PhoneCore.Framework.UnitTests.Stubs.Container.ClassBProxy,PhoneCore.Framework.UnitTests"
                 name ="ClassBProxy">
            <behaviors>
              <clear />
              <behavior name="profile" type="PhoneCore.Framework.IoC.Interception.Behaviors.ProfileBehavior,PhoneCore.Framework" />
            </behaviors>
          </component>

 

There are the default execute behavior and custom ones for each component. 

  • Programmatic:

 

container.Register(Component.For<IClassC>().ImplementedBy<ClassC>()
                                          .Proxy<ClassCProxy>()
                                          .AddBehavior(new ExecuteBehavior())
                                          .Singleton());

Proxy generation tool

There is the example of batch file which show how you can auto generate proxy classes using special tool provided via NuGet package or project source codes:

 

@echo off
pushd

:: CD to script's directory
cd /D %~dp0

if '%1'=='' ( 
	Set Mode=Debug
) else (
	Set Mode=%1
)
set proxyTargetProject="%~dp0PhoneCore.Framework.UnitTests"
echo %proxyTargetProject%
set doPause=1
if not "%2" == "" set doPause=0
%systemroot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "PhoneCore.Framework.sln" /t:Build /p:Configuration=%Mode%;TargetFrameworkVersion=v4.0
echo run proxy gen utility
PhoneCore.Framework.ProxyGen\bin\%Mode%\PhoneCore.Framework.ProxyGen.exe %proxyTargetProject% %proxyTargetProject%\bin\%Mode% %proxyTargetProject%\Proxies
%systemroot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe  PhoneCore.Framework.UnitTests\PhoneCore.Framework.UnitTests.csproj /t:Test /p:Configuration=%Mode%;TargetFrameworkVersion=v4.0
@if ERRORLEVEL 1 goto fail

:fail
if "%doPause%" == "1" pause
popd
exit /b 1

:end
popd
if "%doPause%" == "1" pause
exit /b 0

It receives the following parameters:

  • Target project root folder where application.config is located
  • Assemblies location
  • Output directory

Important: the tool is looking for name attribute of component node. It interprets the value of the attribute as class name and output file name. If it isn't defined, proxy won't be generated. You can once generate the proxy and remove this attribute to prevent further processing of the component node until the appropriate interface is changed.

Last edited Feb 12, 2012 at 8:48 AM by Ilya_Builuk, version 6

Comments

No comments yet.