One reason why I don’t like service locator

Let’s say I want to use the new (brilliant) ISelectionFactory to easily create a list of something for the editor to choose. The data the editor uses is fetched from EPiServers IContentRepository.

public class SomeDataSelectionFactory : ISelectionFactory
{
    readonly IContentRepository _content; 

    public SomeDataSelectionFactory(IContentRepository content)
    {
        _content = content;
    }

    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
    {
        return _content
            .GetChildren<PageData>(ContentReference.StartPage)
            .Select(c => new SelectItem { Value = c.ContentLink.ID, Text = c.PageName });
    }
}

Unfortunately this won’t work since the class is not created from the container. Instead we get an error in edit mode saying the page that has a property that uses the selection factory can’t be previewed and if we follow the link in the javascript console log that reports a 500 error we find the expected “No parameterless constructor defined for this object”.

Service Locator

Since constructor injection doesn’t work we can use service location, either via the ServiceLocator or Injected.

public class SomeDataSelectionFactory : ISelectionFactory
{
    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
    {
        return ServiceLocator.Current.GetInstance<IContentRepository>()
            .GetChildren<PageData>(ContentReference.StartPage)
            .Select(c => new SelectItem { Value = c.ContentLink.ID, Text = c.PageName });
    }
}
public class SomeDataSelectionFactory : ISelectionFactory
{
    Injected<IContentRepository> _content;

    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
    {
        return _content
            .Service
            .GetChildren<PageData>(ContentReference.StartPage)
            .Select(c => new SelectItem { Value = c.ContentLink.ID, Text = c.PageName });
    }
}

These two options are basically equivalent since Injected uses the ServiceLocator behind the scenes. My beef with any of these approaches become apparent when trying to initiate the class, for instance in a test scenario.

The test

When writing a test for the selection factory in the first example it become very clear that I need to supply a IContentRepository because of the constructor. The service locator based approaches happily create the object but will fail at runtime because it can’t resolve the dependency to IContentRepository. This means I need to make sure that before the test is run I have set a service locator and make sure that the relevant dependencies are registered.

var container = new Container();
container.Configure(x => x.For<IContentRepository>().Use(contentRepository));
ServiceLocator.SetLocator(new StructureMapServiceLocator(container));

This is not complicated to do but that’s beside the point. When a new up something I can clearly see what the object needs in the constructor. Service location hides all those details away. That’s not good infrastructure magic that just works. That’s magic where I need to know a lot about the involved rabbits and hats and that’s one reason why I don’t like the service locator.

  • http://twitter.com/SergVro Sergii Vorushylo

    Probably, a separate constructor could be an option:
    public class SomeDataSelectionFactory : ISelectionFactory
    {
    readonly IContentRepository _contentRepository;

    public SomeDataSelectionFactory()
    {
    _contentRepository = ServiceLocator.Current.GetInstance();
    }

    public SomeDataSelectionFactory(IContentRepository contentRepository)
    {
    _contentRepository = contentRepository;
    }

    public IEnumerable GetSelections(ExtendedMetadata metadata)
    {
    return _contentRepository
    .GetChildren(ContentReference.StartPage)
    .Select(c => new SelectItem { Value = c.ContentLink.ID, Text = c.PageName });
    }
    }

  • http://twitter.com/fredrikhaglund Fredrik Haglund

    Hear, hear …

  • http://www.tech-fellow.lv/ Valdis Iljuconoks

    I would probably create additional .ctor for unit tests to inject repository explicitly. Not as clean as it should be, but at least it would give me a clue that there is some magic going on if I’ll create instance without dependencies (parameter less constructor).

  • Bartosz Sekuła

    How about:
    public class SomeDataSelectionFactory
    {
          private IContentRepository repository;
          public SomeDataSelectionFactory() 
          {
                    this.repository = ServiceLocator.Current.GetInstance();
           }
          public SomeDataSelectionFactory(IContentRepository repo) {this.repository = repo}
    }

    • http://www.popkram.com Stefan Forsberg

      Yes that works but it also means that the SomeDataSelectionFactory class must know about the ServiceLocator. 

  • Andreas Nicolaisen

    I think you are quite right, and the Service Locator pattern is by many regarded as an anti-pattern just as you have described here. Check out http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx to read even more. 

    /Andreas Nicolaisen

  • Duong

    Hi, this is a bug which is fixed on our development branch. Though, I am not sure whether it will be released on the R2 or together with the coming Edit UI update.