Monday, April 26, 2010

SharePointServiceLocator + Unity

I Love dependency Injection. It’s a great technique that can help you to build flexible and testable applications.

The SharePoint Service Locator

When I was working on the SharePoint Guidance v2 at patterns & practices, I also worked on something called the SharePointServiceLocator.

The SharePoint Guidance Library includes an implementation of the service locator pattern named the SharePoint service locator. The SharePoint service locator is a reusable component that you can include in your own SharePoint applications. It allows you to decouple consumers of an interface from the implementations of that interface. Instead of creating an object by invoking the constructor of a class, you request an object with a specified interface from the service locator. Decoupling your code from a concrete implementation makes your code easier to test and more modular.

The SharePoint ServiceLocator is a very handy component that allows you to achieve Inversion of Control. But it’s not a dependency Injection container such as Microsoft Unity.

When I was building the SharePoint ServiceLocator, I really wanted to use Microsoft Unity for it. Microsoft Unity is a great Dependency Injection Container. However, at that time, our advisors recommended that we should NOT use Unity, because it is quite complex. The advisors worried that most SharePoint developers would have a hard time understanding Unity.

The problem here is obviously, if you are unfamiliar with dependency injection as a technique, then using a very simple service locator is already a big step. However, if you are comfortable with dependency injection, then you can’t live without the capabilities that such a component provides.

Plugging Unity into the SharePoint ServiceLocator

When I was building the SharePoint ServiceLocator, I did keep in mind that someday I’d like to plug in a different dependency injection container, such as Unity. Now that I’m working for a company that’s actually using the SharePoint Guidance, I figured it would be time to create the components required to plug in Unity as a Dependency Injection Container.

Below is a diagram that describes the design of the SharePoint ServiceLocator:image

As you can see, the SharePoint ServiceLocator is not the actual ServiceLocator…. It’s more of a ServiceLocatorLocator, but that name just sounds silly. But it does locate the actual ServiceLocator for you, which by default is the ActivatingServiceLocator.

Now what we want to do, is use Unity instead of using the Activating ServiceLocator. To do that, we need to create two components:

UnityServiceLocatorAdapter

The Unity container does not implement the IServiceLocator interface. This interface comes from the Common ServiceLocator project and has the goal to create a common interface that library builders can use abstract away dependencies from a specific DI container. So what we need, is an adapter that adapts Unity to the IServiceLocator interface. And the nice thing is.. that component already exists here.

But for completeness, here’s the source code for that:

   1: /// <summary>



   2: /// Adapter that maps Unity to the Commmon Service Locator interface <see cref="IServiceLocator"/>



   3: /// </summary>



   4: public class UnityServiceLocatorAdapter : ServiceLocatorImplBase



   5: {



   6:     private readonly IUnityContainer _unityContainer;



   7:  



   8:     /// <summary>



   9:     /// Creates an instance of the service locator



  10:     /// </summary>



  11:     /// <param name="unityContainer"></param>



  12:     public UnityServiceLocatorAdapter(IUnityContainer unityContainer)



  13:     {



  14:         _unityContainer = unityContainer;



  15:     }



  16:  



  17:     /// <summary>



  18:     /// The unity container that's used by this adapter



  19:     /// </summary>



  20:     public IUnityContainer UnityContainer



  21:     {



  22:         get { return _unityContainer; }



  23:     }



  24:  



  25:     /// <summary>



  26:     ///             When implemented by inheriting classes, this method will do the actual work of resolving



  27:     ///             the requested service instance.



  28:     /// </summary>



  29:     /// <param name="serviceType">Type of instance requested.</param>



  30:     /// <param name="key">Name of registered service you want. May be null.</param>



  31:     /// <returns>



  32:     /// The requested service instance.



  33:     /// </returns>



  34:     protected override object DoGetInstance(Type serviceType, string key)



  35:     {



  36:         return UnityContainer.Resolve(serviceType, key);



  37:     }



  38:  



  39:     /// <summary>



  40:     ///             When implemented by inheriting classes, this method will do the actual work of



  41:     ///             resolving all the requested service instances.



  42:     /// </summary>



  43:     /// <param name="serviceType">Type of service requested.</param>



  44:     /// <returns>



  45:     /// Sequence of service instance objects.



  46:     /// </returns>



  47:     protected override IEnumerable<object> DoGetAllInstances(Type serviceType)



  48:     {



  49:         return UnityContainer.ResolveAll(serviceType);



  50:     }



  51: }






Note that this class derrives from ServiceLocatorImplBase, which comes from the Microsoft.Practices.ServiceLocation.dll assembly (the common servicelocator project).



UnityServiceLocatorFactory



Secondly, we need a component that can acually create the UnityServiceLocatorAdapter and fill it with the typemappings that are stored in Config.



Here’s the source code for that component:





   1: /// <summary>



   2: /// Factory that can create the unity service locator and fill it with typemappings



   3: /// </summary>



   4: public class UnityServiceLocatorFactory : IServiceLocatorFactory



   5: {



   6:     /// <summary>



   7:     /// Create the service locator



   8:     /// </summary>



   9:     /// <returns></returns>



  10:     public IServiceLocator Create()



  11:     {



  12:         return new UnityServiceLocatorAdapter(new UnityContainer());



  13:     }



  14:  



  15:     /// <summary>



  16:     /// Load typemappings into the service locator



  17:     /// </summary>



  18:     /// <param name="serviceLocator">The service lcoator to load typemappings into</param>



  19:     /// <param name="typeMappings">The typemappings to load</param>



  20:     public void LoadTypeMappings(IServiceLocator serviceLocator, IEnumerable<TypeMapping> typeMappings)



  21:     {



  22:         if (serviceLocator == null) throw new ArgumentNullException("serviceLocator");



  23:         if (typeMappings == null) throw new ArgumentNullException("typeMappings");



  24:  



  25:         var unityServiceLocator = serviceLocator as UnityServiceLocatorAdapter;



  26:         if (null == unityServiceLocator) throw new ArgumentException("ServiceLocator must be of type UnityServiceLocatorAdapter", "serviceLocator");



  27:  



  28:         IUnityContainer container = unityServiceLocator.UnityContainer;



  29:  



  30:         foreach(TypeMapping mapping in typeMappings)



  31:         {



  32:             Type fromType = GetTypeFromStrings(mapping.FromAssembly, mapping.FromType);



  33:             Type toType = GetTypeFromStrings(mapping.ToAssembly, mapping.ToType);



  34:  



  35:             if (fromType == null)



  36:             {



  37:                 throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not get type for '{0}'", mapping.FromType));



  38:             }



  39:  



  40:             if (toType == null)



  41:             {



  42:                 throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not get type for '{0}'", mapping.ToType));



  43:             }



  44:  



  45:  



  46:             container.RegisterType(fromType, toType);



  47:         }



  48:     }



  49:  



  50:     private static Type GetTypeFromStrings(string assemblyName, string typeName)



  51:     {



  52:         Assembly assembly = Assembly.Load(assemblyName);



  53:         return assembly.GetType(typeName, true, false);



  54:     }



  55: }




Configuring the SharePoint ServiceLocator to use the new factory



Now that we have both the factory and the adapter, we need a way to tell the SharePointServiceLocator to actually use these components. You simply do that by registering a typemapping between the IServiceLocatorFactory and the UnityServiceLocatorFactory from a farm scoped feature receiver, like this:




new ServiceLocatorConfig()
.RegisterTypeMapping<IServiceLocatorFactory, UnityServiceLocatorFactory>();






Changes you’ll have to make to the SharePoint Guidance Library







When we built the functionality to plug in different service locators, we tested of course if you could actually plug in a different service locator. And you can.. which is nice. However, we never had the time to actually create this Unity plugin. And since we never tried this.. it doesn’t work nicely out of the box.



So you’ll have to make 2 small changes to the library to get it to work nicely.



The first change I made is to remove the new() operator from all generic methods that create or register typemapings, such as the ServiceLocatorConfig.RegisterTypeMapping or TypeMapping.Create<>() methods. The new() operator defines that all physical classes that are used as typemappings have a default constructor. This seemed like a good idea with the ActivatingServiceLocator, but with unity, I like to use constructor injection, so the new() operator doesn’t make sense.



The second change was in the TypeMappings.Create<> method. I changed the ToType from the AssemblyQualifiedName of a type to the Full typename of a type. For some reason, on my machine, using the AssemblyQualifiedName doesn’t work. I don’t know what the reason for this is.. but using the Full typename works. So that’s something else I changed.



Hope you’ll find this useful.



_Erwin

6 comments:

  1. First of all - the reason you had to change ToType to the FullName in the TypeMapping class was beacuse you were using assembly.GetType(typeName, true, false);
    when you could be using Type.GetType(typeName) in your GetTypeFromString method. Assembly.GetType doesn't accept a fully qualified name - it's not that it doesn't work on your machine.

    Second, I also had to make changes to the SharePointServiceLocator to ensure that the default type mappings would be created using their default constructors. By default, Unity will not do this and the ServiceLocator does not take this into account. I ended up extending InstantiationType to include types that require use of the Default contstructor.

    Your unity adapter also does not take into account other features provided by the default locator, such as registering singleton instances, etc.

    After changing this, I found it worked to register types at the farm level, but still the site level mappings were not being persisted properly.. this required more fiddling as the SharePointServiceLocator seems to make some assumptions about the default locator vs custom locators.

    I ended up just going with Unity and a Dependency Manager singleton class that allows overriding the container, and discarding the ServiceLocator, as it doesn't provide me with any tangible benefit over just using Unity and configuring it within my project..
    This way, I have a simpler, cleaner code base that provides me with full dependency injection and testability - which was all I really wanted anyway :)

    ReplyDelete
    Replies
    1. đàn vang lên trong khoảnh khắc này lại phù hợp một cách kỳ dị với tâm tình Vương Lâm, hắn đang chuẩn bị rời đi lại phải ngừng ngay lại.

      Vương Lâm chậm rãi đi tìm tiếng đàn, trên con đường cách đó không xa có một nhánh sông, con sông này bao quanh Hồng thành, là một trong những dòng sông ở Thiên Yêu thành.

      Trên sông có vài con thuyền hoa, tiếng đàn được truyền ra từ một trong những con thuyền đó.
      dong tam mu lậu cho thuê phòng trọ cho thuê phòng trọ nhac san cuc manh tư vấn pháp luật qua điện thoại công ty luật hà nội số điện thoại tư vấn luật dịch vụ thành lập doanh nghiệp
      Vương Lâm lẳng lặng đứng trên con đường sát bờ sông lắng nghe tiếng đàn. Với thị lực của hắn chỉ cần đảo mắt đã nhìn thấy một người con gái đang đánh đàn trên thuyền hoa… ….

      Cô gái này chỉ để lộ ra bóng lưng.

      Bóng lưng của nàng cũng giống như tiếng đàn, lộ ra vẻ bi thương và cô độc. Trước người nàng không xa có vài người thanh niên đang vui vẻ uống rượu, những tiếng cười đùa

      Delete
  2. I'm looking into this myself now and Mark's comment is not making me happy.

    The service locator without Unity isn't that good since i need a reference to the locator everywhere and i would like to have only 1 reference to the container and let everything else resolve itself automatically by constructor injection.

    @Mark. how did you roll out unity in SharePoint? did you make use of the global.asax and how did you deploy this then?

    ReplyDelete
  3. @geertvdcruijsen Basically, the way that I used unity was to deploy a unity config file to _layouts. The unity container is basically a singleton that reads the config file and populates the mappings when it is first accessed. I did a bit more work around abstracting my container wrapper so that different configurations could be passed to it.. but that is the basic idea of what i did.

    ReplyDelete