Click here to Skip to main content
15,881,559 members
Articles / Operating Systems / Windows
Article

Inversion of Control (IoC) in XAML

Rate me:
Please Sign up or sign in to vote.
4.78/5 (17 votes)
16 Jan 2007CPOL9 min read 63.8K   533   36   2
An article about Inversion of Control in XAML

Introduction

The code presented here allows the use of XAML to implement the Inversion of Control pattern (IoC, also known as Dependency Injection). Using IoC with XAML allows us to write lowly-coupled applications that are easy to test. The code given here was constructed in Visual Studio 2005 using the November 2006 CTP of WPF.

Background

Martin Fowler gives an excellent description of the IoC pattern here. At its simplest, IoC is a pattern where a component gets its dependencies injected from the outside (usually by a container) instead of having the dependencies hard-coded within the component. The main advantage of IoC is that your components are decoupled from their dependencies. That makes them much easier to move around and hence reuse them, as well as unit-test them.

A way of seeing the IoC pattern is to see it as an implementation of the factory pattern. The dependencies of a component are wired into it by an object factory. Most IoC frameworks allow this wiring to take place declaratively instead of coding a factory class per component in an imperative language (e.g. C#).

IoC is a pattern mostly used in the Java community where the Spring Framework, implementing IoC (among other things), is very pervasive. The framework was partially ported to .NET in the Spring.NET Framework and since then, IoC has been catching some speed in the .NET community.

The XAML language, part of .NET Framework 3.0, offers an alternative to using IoC. There is very little to add to XAML to obtain usable IoC that is type-checked by the compiler. XAML is an instantiation language: its constructs describe how to instantiate different objects and setting dependencies. XAML is now part of the .NET Framework and is therefore going to be known by a majority of .NET developers by the end of 2007. This makes XAML a low-entry-level candidate for implementing IoC.

XAML is based on XML and is therefore declarative by nature. That makes it a good candidate for IoC. The declarative power of XAML has already been recognized by many groups, for instance, the MyXAML open-source project.

Using the Code

The main example I'm going to show is a classic IoC. The following UML diagram shows the main classes (the colour code separates application classes from the mini-IoC Framework we provide). PersonValidator is a component with two dependencies: an alert service and a persistence service. Both of those dependencies are modeled as interfaces. Both interfaces have one implementation shown here, but they could have others. The main point is that PersonValidator only depends on the interfaces, not on their implementation. Something else knows about the interface implementation and wires them into the component and that is XAML.

Main IoC example - UML diagram

The C# code using the component is the following:

C#
private static void TestPersonValidation()
{
   PersonValidator validator = ObjectFactory.GetObject<
     PersonValidator>(typeof(PersonValidator).FullName);

    validator.ValidatePersons(42, 12, 79, 14);
}

The validator instance isn't instantiated by new, but by an object factory. ObjectFactory is part of the code we added and joined to this article to make XAML IoC-friendly. We will come back to what it exactly does later. For this section, it suffices to know that ObjectFactory goes into a XAML dictionary and finds a resource identified with the Key corresponding to the value of MyLib.PersonValidator ( this is the string returned by typeof(PersonValidator).FullName).

The validator instance comes all configured with its persistence and alert services and ready to perform its ValidatePersons method. Let's look at how this configuration happened. The XAML code responsible for this configuration is inside the file TestPersonValidation.xaml:

XML
<my:PersonValidator x:Shared="false" 
    x:Key="MyLib.PersonValidator">
    <my:PersonValidator.PersistenceService>
        <my:ResourcePersistenceService/>
    </my:PersonValidator.PersistenceService>
    <my:PersonValidator.AlertService>
        <my:ConsoleAlertService/>
    </my:PersonValidator.AlertService>
</my:PersonValidator>

This configuration is contained inside a ResourceDictionary which is a Windows Presentation Foundation (WPF) class containing different entries. Each entry is an object instantiation description: a recipe to create an object.

The type of the object is defined by the XML element: my:PersonValidator. The XML prefix my is mapped to a CLR namespace by the XAML namespace declaration: xmlns:my="clr-namespace:MyLib;assembly=MyLib". We will explain the x:Shared attribute in a following section. x:Key is the key of the object in the dictionary.

Both service properties are declared in what is known as XAML property element syntax. Both are simple declarations of the type of the implementation of each interface. This XAML could have been written in the more compact format using markup extensions:

XML
<my:PersonValidator x:Shared="false" x:Key="
    MyLib.PersonValidator"
    PersistenceService="{x:Type 
        my:ResourcePersistenceService}"
    AlertService="{x:Type my:ConsoleAlertService}"/>

Points of Interest

There are several points of interest about how to make XAML IoC friendly. We raise those points here and show how we addressed them. The library augmenting XAML (also shipped with this article) is called XamlIoc and has the XML namespace ioc in XAML (in the following examples).

XAML outside WPF

XAML was primarily designed to work with Windows Presentation Foundation (WPF). Visual Studio 2007 (codename Orcas) XAML designer (codename cider) is also expecting WPF objects. Currently, there are quite a few glitches when working with XAML with objects other than out-of-the-box WPF objects. Those glitches should be fixed by the time Visual Studio 2007 goes into release (sometime in 2007).

The major problem is that classes referenced in a XAML file within a project must be defined outside that project. This actually causes a compilation error. This is why we've created the MyLib project in the sample code.

Another problem is that even if you refer to classes compiled outside the project, the designer raises warnings and errors. This seems to be strictly a designer issue since the project does compile.

Yet another problem: when you refer to other CLR namespaces and assemblies in an XML namespace declaration, the designer complains that it cannot find your namespaces in your assembly. Again, that seems to be a designer issue only; compilation goes fine.

Those three problems also affect development in WPF, so we are confident that they will be addressed before the product goes into release.

Default Constructors

Actually, this is a limitation of XAML that we didn't try to get around. A class must have a default constructor to be used in XAML. Data is passed to the class strictly by properties.

Resource Dictionary

We use resource dictionaries to store the object instantiation description. How does ObjectFactory know where to find the dictionary? It must be set up in the application configuration file (or web.config for web applications):

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
      <section name="iocConfigSection" 
      type="XamlIoc.Configuration.IocConfigSection, 
      XamlIoc"/>
    </configSections>
    <iocConfigSection mainContainer="Container.xaml">
        <standAloneResources>
            <!--<standAloneResource key="myListOfInt" 
            uri="StandAlone/ListOfInt.xaml" 
            isShared="false"/>-->
        </standAloneResources>
    </iocConfigSection>
</configuration>

The attribute mainContainer on iocConfigSection specifies the file containing the resource dictionary. This file must have a resource dictionary as the root element. If no file is specified, ObjectFactory uses the resource dictionary of the WPF-Application if there is one. This is a neat feature, since it allows the sharing of object descriptions between WPF-objects (e.g. Windows) and objects calling ObjectFactory explicitly.

The standAloneResources element isn't used in the code sample. It is useful if you want to have other XAML files defining objects. Those files are standalone and aren't resource dictionaries. As mentioned above, ResourceDictionary is a WPF class. This class comes with a nice feature: MergedDictionaries. This is a property where you can specify child-dictionary. This allows you to spread your object descriptions in multiple files.

XML
<wpf:ResourceDictionary
    xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ioc="clr-namespace:XamlIoc;assembly=XamlIoc"
        xmlns:my="clr-namespace:MyLib;assembly=MyLib"
    >
    <wpf:ResourceDictionary.MergedDictionaries>
        <wpf:ResourceDictionary Source="TestPersonValidation.xaml"/>
        <wpf:ResourceDictionary Source="TestShared.xaml"/>
        <wpf:ResourceDictionary Source="TestGenerics.xaml"/>
    </wpf:ResourceDictionary.MergedDictionaries>
</wpf:ResourceDictionary>

x:Shared

A little-known XAML attribute is x:Shared. In a resource dictionary item, it tells the Framework if a new object should be created every time the resource is queried or if the same instance should be shared. This allows the creation of singletons in the application. By default, all the instances are shared. This is why in WPF, by default, all application-wide resources are singletons. It avoids the creation of an object every time the resource is queried.

This is all nice and well in WPF where you typically put styles and brushes in the application resources. But for IoC, you'll probably want to create a new object every time. For this reason, we recommend that you use x:Shared="false" when you do not want singleton. Shared resources are created on demand (lazy-init).

StaticResource

The standard way to refer to resources within another resource is the use of either StaticResource or DynamicResource XAML markup extensions. DynamicResource uses a mechanic called DependencyProperty. It isn't useful for IoC, since it would put the constraint on how to build the objects. StaticResource has one known limitation: even if the resource referenced is tagged as x:Shared="false", the resource is instantiated only once.

This means that if we only used out-of-the-box XAML, we wouldn't be able to assemble objects from multiple object descriptions. This would severely limit us in terms of scaling the object descriptions, since we would have to duplicate object descriptions instead of reusing them.

For this reason, we introduced another markup extension: ObjectFactoryExtension. It is used the same way as StaticResource or DynamicResource, but calls ObjectFactoryto query the resource. This respects the x:Shared="false" statement. A usage example of ObjectFactoryExtension:

XML
<wpf:ResourceDictionary
    xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ioc="clr-namespace:XamlIoc;assembly=XamlIoc"
        xmlns:my="clr-namespace:MyLib;assembly=MyLib"
    >
    <!--    TestShared    -->
    <my:Address x:Shared="false" x:Key="
    protoAddress"/>
    <my:Person x:Shared="false" 
       x:Key="objectFactoryPerson" my:Address="
       {ioc:ObjectFactory protoAddress}"/>
    <my:Person x:Shared="false" x:Key="
       staticResourcePerson" my:Address="{
          wpf:StaticResource protoAddress}"/>
</wpf:ResourceDictionary>

Here we have an address that is defined as a non-shared resource and two persons using that address description. The first person, objectFactoryPerson, uses ObjectFactoryExtension while the second, staticResourcePerson, doesn't. The following code tests both person object descriptions:

C#
private static void TestShared()
{
    Person objectFactoryPerson1 = ObjectFactory.GetObject
       <Person>("objectFactoryPerson");
    Person objectFactoryPerson2 = ObjectFactory.GetObject
       <Person>("objectFactoryPerson");
    Person staticResourcePerson1 = ObjectFactory.GetObject
       <Person>("staticResourcePerson");
    Person staticResourcePerson2 = ObjectFactory.GetObject
       <Person>("staticResourcePerson");

    Console.WriteLine(
        "object factory persons equal:  "
        + object.ReferenceEquals(objectFactoryPerson1, 
          objectFactoryPerson2));
    Console.WriteLine(
        "static resource persons equal:  "
        + object.ReferenceEquals(staticResourcePerson1, 
          staticResourcePerson2));
    Console.WriteLine(
        "object factory addresses equal:  "
        + object.ReferenceEquals(objectFactoryPerson1
          .Address, objectFactoryPerson2.Address));
    Console.WriteLine(
        "static resource addresses equal:  "
        + object.ReferenceEquals(staticResourcePerson1
          .Address, staticResourcePerson2.Address));
}

The main test is the last line of code. In the case where the person object description uses StaticResource, the reference address is the same reference every time the person object is created. Of course, we could use StaticResource when we do not mind singletons, but for consistency we suggest to always use ObjectFactoryExtension to refer to another resource.

Generics

Generics are not supported by XAML. Actually, they are supported in the a very special case with the x:TypeArguments markup extension. The object defined needs to be at the root of a XAML file and using the x:Classmarkup extension, which defines a class deriving from the defined object class. This support isn't good for us in general, since it would force us to derive a class every time we want to use it.

We derived a solution to this problem from one of Mike Hillberg's Blog posts on WPF. We created a GenericExtension capable of instantiating any generic class and also setting properties on it. For example:

XML
<ioc:ObjectContainer x:Key="myListOfInt">
    <ioc:Generic
        xmlns:sys="clr-namespace:System;assembly=mscorlib" 
        xmlns:sysGenCol="clr-namespace:System.Collections.Generic;assembly=mscorlib" 
        ioc:TypeName="sysGenCol:List"
        FirstTypeArgument="{x:Type TypeName=sys:Int32}">
        <ioc:Generic.PropertyDictionary>
            <sys:Int32 x:Key="Capacity">12</sys:Int32>
        </ioc:Generic.PropertyDictionary>
    </ioc:Generic>
</ioc:ObjectContainer>

Unfortunately, the solution is a bit convoluted.

First, XAML doesn't see markup extensions as markup extensions when they are at the root of a resource dictionary element: it sees it as if we would want instantiate the markup extension object. For that reason, we had to create an ObjectContainer containing GenericExtension. ObjectFactoryknows about ObjectContainer and returns the child of the container.

Second, since we use the markup extension to create the type, we do not have the object we want to create at hand and we cannot set properties on it. GenericExtension has the property PropertyDictionary that accepts a key-value pair property name/value. This does work. In the example, we set the property Capacity to 12. The problem is that this is done through reflection and hence we loose a nice feature of XAML: static compiling check.

Conclusion

With little effort, XAML offers a nice alternative for using IoC in .NET applications. IoC helps to design loosely coupled components and can therefore help greatly to create manageable large-scale applications.

History

  • 17 January, 2007 -- Original version posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect CGI
Canada Canada
Vincent-Philippe is a Senior Solution Architect working in Montreal (Quebec, Canada).

His main interests are Windows Azure, .NET Enterprise suite (e.g. SharePoint 2013, Biztalk Server 2010) & the new .NET 4.5 platforms.

Comments and Discussions

 
GeneralXaml inversion Pin
Mohamed Hachem14-Oct-07 9:27
Mohamed Hachem14-Oct-07 9:27 
GeneralThank you! Pin
Josh Smith17-Jan-07 14:37
Josh Smith17-Jan-07 14:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.