One thing that I have been missing with my IoC container in Silverlight is the ability to configure it using a config file. In “regular” .NET, this is an option that most containers support in one way or another. It makes it easy to have different config files for dev, stage and production for example, which is really nice. In Silverlight the story is different… Why? Well, I assume it is because it is a bit hard to switch config files in Silverlight as the file gets embedded in the XAP file, which makes it less usable than it does in the bigger sister.
Having that said, I still believe it could be a good option for some apps, and decided to build my own implementation…like I always do…
But before I start doing that, I want to answer, or at least give some insight from my point of view, about the pros and cons of using XML based config compared to code based.
The upside to using XML based config is that it is easy to reconfigure. It is even possible to reconfigure the app without even having to recompile the application, which is something that can be very useful in some situations. The downside is the lack of checks. As the file is not a compiled and type safe way to configure it, any errors will not be visible until runtime. So it is pretty simple to configure the app wrong and thus kill it.
The upside to code-based configuration is obviously the type checking and security you get around the configuration. The compiler will automatically tell you when you have done something wrong. On the other hand, if you need to make a change, you have to recompile the application, or at least the assembly responsible for the configuration. And you might also end up with a whole heap of configuration code in the application…
So as you can see, there are pros and cons in both camps. But not having the option to chose is just not my cup of tea. So something had to be done…
My goal was to be able to configure the IoC container using XAML in the App.xaml file like this
<serviceLocation:ServiceLocation>
<serviceLocation:ServiceLocation.Locator>
<unity:UnityServiceLocatorEx />
</serviceLocation:ServiceLocation.Locator>
<serviceLocation:ServiceLocation.Aliases>
<serviceLocation:Alias Name="IService" Type="FiftyNine.Ag.MVVM.Test.Common.IService, FiftyNine.Ag.MVVM.Test.Common, Version=1.0.0.0" />
<serviceLocation:Alias Name="ILogger" Type="FiftyNine.Ag.MVVM.Test.Common.ILogger, FiftyNine.Ag.MVVM.Test.Common, Version=1.0.0.0" />
</serviceLocation:ServiceLocation.Aliases>
<serviceLocation:InstanceRegistration Interface="ILogger">
<services:DebugLogger />
</serviceLocation:InstanceRegistration>
<serviceLocation:TypeRegistration Interface="IService"
Type="FiftyNine.Ag.MVVM.TestLibrary.Service, FiftyNine.Ag.MVVM.TestLibrary, Version=1.0.0.0">
<serviceLocation:ValueConstructorParameter ParameterName="value" Value="Hello World!!!" />
<serviceLocation:ValueConstructorParameter ParameterName="value2">
<sys:Int32>2</sys:Int32>
</serviceLocation:ValueConstructorParameter>
<serviceLocation:DependencyConstructorParameter ParameterName="logger" Type="ILogger" />
</serviceLocation:TypeRegistration>
</serviceLocation:ServiceLocation>
The config syntax is not that hard to grasp, which is really the part of the goal. Even a person who haven’t seen this config before, like you, can pretty much figure out what it does. The only thing that might throw some people off a bit is the ServiceLocator.Locator element. This defines what “locator” to use. The “locator” is a class implementing an extended IServiceLocator interface, making it possible to switch IoC container in a very simple way. In my case I have, like pretty much always, decided to use Unity. But creating the an implementation for Ninject or any other IoC container should take about 5 minutes.
The above configuration is, as mentioned before, placed in the App.xaml, and more specifically in the Application.ApplicationLifetimeObjects element. This obviously means that the ServiceLocation class that I have created has to implement the IApplicationService interface. And looking at the other configuration settings, we can pretty much agree on the fact that my class looks like this
public class ServiceLocation : IApplicationService
{
public void StartService(ApplicationServiceContext context) { ... }
public void StopService() { ... }
public IServiceLocatorEx Locator { ... }
public List<Alias> Aliases { ... }
public List<ServiceRegistration> Registrations { ... }
}
Ok, so the actual types for the properties aren’t evident by just looking at the configuration, but other than that, we can see that the ServiceLocation class isn’t complicated. And no, the ServiceLocation class does not implement IServiceLocator or anything. It is not the actual locator. It is just a class responsible for configuring the service locator.
But before we start looking at the actual implementation of this class, I want to have a look at the supporting classes and interfaces, starting with the IServiceLocatorEx.
The IServiceLocatorEx interface is, as pretty much expected, an extension of the IServiceLocator from Microsofts’ Common Service Locator project. The extension to it is the addition of some registration methods that make it possible to register types and through the interface.
public interface IServiceLocatorEx : IServiceLocator
{
void RegisterType(Type from, Type instanceType);
void RegisterType(Type from, Type instanceType, object[] constructorParameters);
void RegisterInstance(Type from, object instance);
}
This is only used by the ServiceLocation class, and not used by the rest of the application, which only uses the regular IServiceLocator.
The IServiceLocator is extended in this way to decouple the ServiceLocation class from the actual IoC container in use. Without this extension, it would not be possible to add registrations to the IoC container, thus making this whole thing impossible…
But as you can see, the added methods are ridiculously easy to implement for most, if not all, IoC containers. In my case, I implemented a UnityServiceLocatorEx by inheriting from UnityServiceLocator and then adding the extended methods like this
public class UnityServiceLocatorEx : UnityServiceLocator, IServiceLocatorEx
{
public UnityServiceLocatorEx() : base(new UnityContainer())
{
}
public void RegisterType(Type from, Type instanceType)
{
this.GetInstance<IUnityContainer>().RegisterType(from, instanceType);
}
public void RegisterType(Type from, Type instanceType, object[] constructorParameters)
{
this.GetInstance<IUnityContainer>().RegisterType(from, instanceType, new InjectionConstructor(constructorParameters));
}
public void RegisterInstance(Type from, object instance)
{
this.GetInstance<IUnityContainer>().RegisterInstance(from, instance);
}
}
As you can see, it is a piece of cake to implement this. The only thing to remember is that the constructor has to be parameter less to work in the XAML. So we have to instantiate the IoC and pass it to the base class on our own…
The next type that we can see in the ServiceLocation class is the Alias class. And Alias is basically just that. It is a named alias for a type. It is just there to make the configuration section shorter and easier to read. So instead of having to use the full type definition all over the place, we can just use the shorter alias for that type. In the ServiceLocation class, the aliases are exposed as a List<Alias>.
The actual Alias class is very simple. It exposes 2 properties, one string property called Name, and one property of type Type with the name Type. The Type property is adorned with an TypeConverter attribute, defining the type converter as a type called TypeTypeConverter. I will come back to this converter, but let’s first look at the Alias class in all its glory
public class Alias
{
public string Name
{
get;
set;
}
[TypeConverter(typeof(TypeTypeConverter))]
public Type Type
{
get;
set;
}
}
Once again, very simple! It is pretty rare that my code is anything but simple…
The TypeTypeConverter is a pretty simple class inheriting from TypeConverter. Is overrides the CanConvertFrom() method by just verifying that the type coming in is a string, and it overrides the ConvertFrom() method by using Type.GetType() method.
public class TypeTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
return (sourceType == typeof(string)) ? true : base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return (value is string) ? Type.GetType((string)value, true) : base.ConvertFrom(context, culture, value);
}
}
And then there was just one more property on the ServiceLocation class that we haven’t looked at, and that is the Registrations property, which is of type List<ServiceRegistration>. The ServiceRegistration class is an abstract base class that defines a type that implements a defined interface. It has 2 properties, one called Dependencies and one called Interface.
The Dependencies property is a list of ServiceDependency classes that defines what other services this registration depends on. This makes it easy to tell the system that my "ServiceA” requires and implementation of interface “IServiceB” to be added before it can add the service, for example making sure that a service is not registered until the logger has been registered.
The Interface property is a string that defines the interface that it is responsible for. It is a string and not a type as it can either be a type, or an alias.
As mentioned, the ServiceRegistration class is an abstract base class. This obviously means that there are classes involved that inherit from it. These are TypeRegistration and InstanceRegistration, which are responsible for registering services in different ways. The TypeRegistrationis responsible for registering a service by defining the type and having the system instantiate it, either on start as a singleton or at every call. The InstanceRegistration on the other hand is expecting you to create the instance in XAML.
The InstanceRegistration is the easiest one. It only has a single property called Instance, of type object. It is however also adorned with the ContentProperty attribute, defining the Instance property as the “content property”. This basically means that any non-prefixed XAML element added as content to the InstanceRegistration element is used to populate the Instance property. As in the following code sample
<serviceLocation:InstanceRegistration Interface="ILogger">
<services:DebugLogger />
</serviceLocation:InstanceRegistration>
The TypeRegistration is a bit more complicated as it has a few more properties. It looks like this
[ContentProperty("ConstructorParameters")]
public class TypeRegistration : ServiceRegistration
{
public TypeRegistration()
{
...
}
[TypeConverter(typeof(TypeTypeConverter))]
public Type Type { get; set; }
public List<ConstructorParameter> ConstructorParameters{ get; set; }
public bool IsSingleton{ get; set; }
}
The Type property is the type to use when implementing the interface. Remember that we are not creating the instance, we are just defining it! The IsSingleton property defines whether or not the type should be created as a singleton or per call. And the ConstructorParameters property that I skipped over, is a list of parameters to send to the constructor on creation. It is also the “content property” for the class.
Using these properties, we can defines the interface that is implemented, by using the Interface property from the base class, the Type that implements it, the instantiation mode as well as any parameters to be passed to the constructor. It should cover most scenarios I think.
The only little thing to mention is the fact that ConstructorParameter is an abstract base class. The actual implementations are ValueConstructorParameter and DependencyConstructorParameter. The ValueConstructorParameter is responsible for defining a value to be passed as a parameter to the constructor. It does so using a property of type object called Value. The DependencyConstructorParameter class on the other hand is responsible for defining a parameter which is actually another dependency. It does this by exposing a property of type string with the name Type, which can either be an actual type it is dependent on or an alias.
Ok, we have now covered all the supporting types involved, which has taken a bit more space than I had expected. Sorry about that! But it is finally time to have a look at the actual implementation of the ServiceLocation class.
As shown before, the public interface for the class is actually pretty simple. The internals make it a bit more complicated though. But let’s start from the top!
The first couple of thing that hasn’t been shown in the previous code snippet, is the fact that it is adorned by a ContentProperty attribute, and contains 2 private members that keeps track of things. So the top of my class actually looks like this
[ContentProperty("Registrations")]
public class ServiceLocation : IApplicationService
{
Dictionary<ServiceRegistration, List<Type>> _dependencies = new ...
List<Type> _registeredInterfaces = new ...
public ServiceLocation()
{
Aliases = new List<Alias>();
Registrations = new List<ServiceRegistration>();
}
...
But the real “magic” is actually in the implementation of the IApplicationService interface, which looks like this
#region IApplicationService Members
public void StartService(ApplicationServiceContext context)
{
HookUpRegistrations();
}
public void StopService()
{
}
#endregion
Ok, so there wasn’t a whole lot of magic going on at all. That whole code snippet just showed that the interface was implemented, and that all the functionality from the StartService() method was delegated to a method called HookUpRegistrations().
The HookUpRegistrations() method does do all the heavy lifting as expected. It is responsible for…wait for it…wait for it��hooking up the defined registrations… Wow! I would never have guessed…
It does this by first verifying that there is a Locator defined. It can’t really work without one… Next it goes through each and everyone of the registrations and pulls out any defined dependencies, and store them in the _dependencies dictionary with the registration as the key, and the list of dependencies as the value.
Next it gets all the registrations that have no dependencies and registers them with the service locator. As a part of registering them, their interfaces are added to the _registeredInterfaces list so that we know what interfaces has been registered, and thus can be used when registering a type that has a dependency to the interface.
private void HookUpRegistrations()
{
if (Locator == null)
throw new Exception("Have to set a Locator");
Registrations.ForEach((reg) => _dependencies.Add(reg, GetAllDependenciesForRegistration(reg)));
var dependencyLessRegistrations = _dependencies.Where((kv) => kv.Value.Count == 0).Select((kv) => kv.Key).ToList();
dependencyLessRegistrations.ForEach(RegisterService);
...
}
Now that all the registrations with no dependencies have been added, it is time to start doing the dirty job of figuring out the order in which to add the services that do have dependencies. This can be a tricky job…but my way of solving it isn’t really that tricky. There might be better ways, but this is what I do…
I get a LINQ query to pull out all registrations that have not yet been added, and where all dependencies are available in the _registeredInterfaces list. Next I register each one of the found registrations, and re-run the query. I keep looping through this until I find no more registrations using my query, and if there are registrations left in the list I throw an exception as some dependencies are missing to be able to fulfill the needs of the remaining registrations.
Finally I set the locator provider of the ServiceLocator class…
private void HookUpRegistrations()
{
...
var readyRegistrations = Registrations.Where((reg) => !_registeredInterfaces.Contains(GetTypeFromString(reg.Interface)) && AllDependeciesAvailable(reg)).ToList();
while (readyRegistrations.Count > 0)
{
readyRegistrations.ForEach(RegisterService);
readyRegistrations = Registrations.Where((reg) => !_registeredInterfaces.Contains(GetTypeFromString(reg.Interface)) && AllDependeciesAvailable(reg)).ToList();
}
if (_registeredInterfaces.Count < Registrations.Count)
throw new Exception("Could not satisfy all registrations...");
Microsoft.Practices.ServiceLocation.ServiceLocator.SetLocatorProvider(() => Locator);
}
The remaining helper methods that are used for registering the actual services and so on is fairly simple, so I won’t cover them in detail. But I would like to mention one thing though. When using the Type.GetType() method, passing in a string definition of a type, Silverlight requires the string to contain the version number. If you forget this, it throws an exception that doesn’t make a whole lot of sense as it says that loading the type conflicts with an assembly already loaded in the app domain. But as long as you remember to add the version as above, it works fine…
Ok, this turned out to be another massive post. I really have to learn how to keep them shorter. But I don’t like just leaving a couple of comments and a code sample. Sorry, that’s just the way I am…
I hope you got something out of it. Maybe not to the degree that you will start using it straight up, but at least you might be able to pull out a bit of code for your own version or your own need…
Code download: XAML-based IoC Container Config.zip (110.07 kb)
Cheers!