Monday, December 17, 2012

Addressing the challenges of TDD in SharePoint – Attribute based registration of types to Service Locator


Dependency injection bindings through an IOC container can be achieved by different ways. For e.g. you can use an XML file based configuration for registering types or use an imperative code block for registration. Both of these methods use an explicit way if registering and resolving types.
Another common and an easy approach is to use attributes to register and resolve types. In this post, we'll extend the SharePoint service locator, to register types based on attributes to the container, which can be later used to resolve the mappings.

Creating the attribute:
public class SetAutoRegistration : Attribute, ISpAttribute
{   
    public Type InterfaceType { get; private set; }

    public string Key { get; private set; }

    public SetAutoRegistration(Type interfaceType) : this(interfaceType, string.Empty)
    {
    }

    public SetAutoRegistration(Type interfaceType, string key)
    {
        InterfaceType = interfaceType;
        Key = key;
    }
}

Creating a mappings object to define type mappings:
public class Mapping
{
    public Type FType { get; private set; }
   
    public Type TType { get; private set; }
   
    public string Key { get; private set; }

    public Mapping(Type fType, Type tType, string key)
    {
        FType = fType;
        TType = tType;
        Key = key;
    }
}

Extending the ServiceLocator class to add type based mappings:
[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public void RegisterTypeMapping(Type fromType, Type toType)
{
    RegisterTypeMapping(fromType, toType, null);
}

[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public void RegisterTypeMapping(Type fromType, Type toType, string key)
{
    var typeMappings = GetConfigData();
    var newTypeMapping = new TypeMapping(fromType, toType, null) { InstantiationType = InstantiationType.NewInstanceForEachRequest };
    RemovePreviousMappingsForFromType(typeMappings, newTypeMapping);
    typeMappings.Add(newTypeMapping);
    SetTypeMappingsList(typeMappings);
}

[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public void RemoveTypeMappings(Type type)
{
    var typeMappings = GetConfigData();
    foreach (var mapping in typeMappings.ToArray())
    {
        if (mapping.FromType == type.AssemblyQualifiedName)
        {
            typeMappings.Remove(mapping);
        }
    }

    SetTypeMappingsList(typeMappings);
}

Using reflection to register types to service locator during feature activating:
var assembly = Assembly.Load(assemblyToProbe);
var typesToAdd =
    assembly.GetTypes().Where(x => x.GetCustomAttributes(typeof (SetAutoRegistration), false).Length > 0);
types.AddRange(typesToAdd);
var mappings = BuildMappingsFromTypes(types);
foreach (var mapping in mappings)
{
    if (mapping.Key == string.Empty)
        serviceLocatorConfig.RegisterTypeMapping(mapping.FType, mapping.TType);
    else
        serviceLocatorConfig.RegisterTypeMapping(mapping.FType, mapping.TType, mapping.Key);
}

Attribute based registration in action:
[SetAutoRegistration(typeof(IMyRepository))]
public class MyRepository : BaseRepository<MyEntity>, IMyRepository
{
You can use the same approach to register types to activating service locator for the test cases.

No comments: