Sitecore & DI – “Unable to resolve service for type …” Error

While I was setting up Dependency Injection in my Sitecore Helix solution, I was greeted with below YSOD :

Unable to resolve service for type 'SCDemo.Feature.Navigation.Repositories.INavRepo' while attempting to activate 'SCDemo.Feature.Navigation.Controllers.NavigationController'.

Unable to resolve service for type ‘SCDemo.Feature.Navigation.Repositories.INavRepo’ while attempting to activate ‘SCDemo.Feature.Navigation.Controllers.NavigationController’.

This is the Controller code:


public class NavigationController  :  Controller
{
    INavRepo _navRepo;

    public NavigationController (INavRepo navRepo)
    {
        _navRepo= navRepo;
    }

    // Action methods

}

Error message was load & clear – my service registration failed, so I started to look into the code which actually register these services.
I started to debug the Foundation project I’m having for Dependency Injection.
This Foundation project (or module) facilitate us to have single Composition Root by registering all the dependencies & Controllers using the Microsoft Dependency Injection Abstractions library. 

This Foundation project is replica of Sitecore Habitat’s Dependency project & it has one class “MvcControllerServicesConfigurator” which register services & Controllers:


public void Configure(IServiceCollection serviceCollection)
{
    serviceCollection.AddMvcControllers("*.Feature.*");
    serviceCollection.AddClassesWithServiceAttribute("*.Feature.*");
    serviceCollection.AddClassesWithServiceAttribute("*.Foundation.*");
}

The code above registers all the MVC Controllers which matches the *.Feature.* as well as services which matches *.Feature.* & *.Foundation.*.

This is done using an Extension class “ServiceCollectionExtensions” where these AddMvcControllers and AddClassesWithServiceAttribute methods along with few Helper methods are defined.

While looking into the this Extension class, I noticed that “AddClassesWithServiceAttribute” method is not getting any services for registration from Feature projects.


public static void AddClassesWithServiceAttribute(this IServiceCollection serviceCollection, params Assembly[] assemblies)
{
    var typesWithAttributes = assemblies
                              .Where(assembly => !assembly.IsDynamic)
                              .SelectMany(GetExportedTypes)
                              .Where(type => !type.IsAbstract && !type.IsGenericTypeDefinition)
                              .Select(type => new { type.GetCustomAttribute<ServiceAttribute>()?.Lifetime, ServiceType = type, ImplementationType = type.GetCustomAttribute<ServiceAttribute>()?.ServiceType })
                              .Where(t => t.Lifetime != null);

    foreach (var type in typesWithAttributes)
    {
        if (type.ImplementationType == null)
            serviceCollection.Add(type.ServiceType, type.Lifetime.Value);
        else
            serviceCollection.Add(type.ImplementationType, type.ServiceType, type.Lifetime.Value);
    }
}

This method uses reflection & filters all the classes with Service attribute, so any service which needs to be register must have the Service attribute.

What is this Service attribute?

This is a custom class, which inherit Attribute class & can be used as Attribute for other classes.


[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ServiceAttribute : Attribute
{
    public ServiceAttribute()
    {

    }

    public ServiceAttribute(Type serviceType)
    {
        this.ServiceType = serviceType;
    }

    public Lifetime Lifetime { get; set; } = Lifetime.Singleton;
    public Type ServiceType { get; set; }
}

Here default Lifetime is used as Singleton, but you can use Transient Lifetime while defining the attribute.

All these classes are included with DI project I used from Sitecore Habitat.

I noticed  that I didn’t have Service attribute on my Repo class (which I wanted to register):


public class NavRepo : INavRepo
{
    public void DoStuff()
    {

        // Do stuff

    }

So I added the Service attribute to the implementation of INavRepo:


[Service(typeof(INavRepo))]
public class NavRepo : INavRepo
{
    public void DoStuff()
    {
        // Do stuff
    }
}

This way our service registration code in Foundation DI project will know that INavRepo is resolved with NavRepo class.
Everything worked after this, so if you are getting the same error, probably you forgot to add the Service Attribute.

Hope this will help.

Advertisements