Adding ServiceLocatorDependencyResolver causes error

 

I'm trying to add the the ServiceLocatorDependencyResolver from the Alloy project to my EPiServer 11 project, but when registering it in the Initialization I get the following error:

An instance of IControllerFactory was found in the resolver as well as a custom registered provider in ControllerBuilder.GetControllerFactory. Please set only one or the other.

A bit unsure of the required settings to make the resolver work. The code in the DependencyResolver InitializationModule looks like this:

 public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.ConfigurationComplete += (o, e) =>
            {
                //Register custom implementations that should be used in favour of the default implementations
                context.Services.AddTransient()
                    .AddHttpContextOrThreadScoped();
            };
        }

        public void Initialize(InitializationEngine context)
        {
            //Add initialization logic, this method is called once after CMS has been initialized
            DependencyResolver.SetResolver(new ServiceLocatorDependencyResolver(context.Locate.Advanced));
        }

All help appreciated.

Edit

The difference I can see is that the Alloy project uses EPiServer.ServiceLocation.StructureMap v1 and my project is using EPiServer.ServiceLocation.StructureMap v2 with specific dependencies on structureap and structuremap.web. So I'm guessing there is a resolver included in there. But the issue I'm having is that I can't make Constructor Injection of EPiServers ServiceLocation interfaces like IContenLoader

        public StartPageController(IContentLoader contentLoader)
        {
            this.contentLoader = contentLoader;
        }
#187141 Edited, Jan 13, 2018 0:06
  •  

    As it seems the documentation is not up to date with the latest version of the EPiServer.ServiceLocation.StructureMap so does anybody have any details on how to enable constructor injection using version 2 of the EPiServer.ServiceLocation.StructureMap?

    #187299 Jan 18, 2018 9:38
  • Joshua Folkerts
    Member since: 2008
     

    Have you added the new packag eof episerver.servicelocation.structuremap.  Also, make sure you clean any old dlls out of the bin you are not using.  by default, the service locator is already wired up for you. but if that is not the case, you can use this.

     public class ServiceLocatorDependencyResolver : IDependencyResolver
        {
            readonly IServiceLocator _serviceLocator;
    
            public ServiceLocatorDependencyResolver(IServiceLocator serviceLocator)
            {
                _serviceLocator = serviceLocator;
            }
    
            public object GetService(Type serviceType)
            {
                if (serviceType.IsInterface || serviceType.IsAbstract)
                {
                    return GetInterfaceService(serviceType);
                }
                return GetConcreteService(serviceType);
            }
    
            private object GetConcreteService(Type serviceType)
            {
                try
                {
                    // Can't use TryGetInstance here because it won’t create concrete types
                    return _serviceLocator.GetInstance(serviceType);
                }
                catch (StructureMapException)
                {
                    return null;
                }
            }
    
            private object GetInterfaceService(Type serviceType)
            {
                object instance;
                return _serviceLocator.TryGetExistingInstance(serviceType, out instance) ? instance : null;
            }
    
            public IEnumerable<object> GetServices(Type serviceType)
            {
                return _serviceLocator.GetAllInstances(serviceType).Cast<object>();
            }
        }
     [InitializableModule]
        [ModuleDependency(typeof(ServiceContainerInitialization))]
        public class WebsiteDependencyResolverInitialization : IConfigurableModule
        {
            public void ConfigureContainer(ServiceConfigurationContext context)
            {
                context.ConfigurationComplete += (o, e) =>
                {
                    context.Services.AddTransient<IContentRenderer, ErrorHandlingContentRenderer>();
                };
            }
    
            public void Initialize(InitializationEngine context)
            {
                DependencyResolver.SetResolver(new ServiceLocatorDependencyResolver(context.Locate.Advanced));
            }
    
            public void Uninitialize(InitializationEngine context)
            {
            }
        }
    #187316 Jan 18, 2018 23:48
  •  

    The project is using the v2 of EPiServer.ServiceLocation.StructureMap and I have cleaned the bin folder, restored Nuget packages but still the error remains. If I try to inject IContentLoader in a controller I get the error regarding missing parametereless constructor and if I try to add the ServiceLocatorDependencyResolver I get the IControllerFactory error.

    #187319 Jan 19, 2018 8:53
  • Antti Alasvuo
    Member since: 2010
     

    Hi Tobias, using v2 of EPiServer.ServiceLocation.StructureMap, my StructureMapDependencyResolver is the same as Jonas writes on this old thread: https://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=63515 

    I've updated the initialization to be like this:

    using EPiServer.Framework;
    using EPiServer.Framework.Initialization;
    using EPiServer.ServiceLocation;
    using StructureMap;
    using System;
    using System.Web.Mvc;
    
    namespace SomeApp.Web.Core.Initialization
    {
        /// <summary>
        /// Initialization module that sets the dependency resolver
        /// </summary>
        [ModuleDependency(typeof(ServiceContainerInitialization))]
        [InitializableModule]
        public class StructureMapDependencyResolverInitialization : IConfigurableModule
        {
            /// <summary>
            /// Configures the container
            /// </summary>
            /// <param name="context">EPiServer service configuration context</param>
            public void ConfigureContainer(ServiceConfigurationContext context)
            {
                // EPiServer.ServiceLocation.StructureMap.2.0.0\lib\net461\EPiServer.ServiceLocation.StructureMap.dll
                // use the episerver extension: EPiServer.ServiceLocation.ServiceConfigurationContextExtensions.StructureMap()
                var container = context.StructureMap();
    
                if (container == null)
                {
                    string message = "ServiceConfigurationContext.StructureMap() returned null. StructureMapDependencyResolver cannot be initialized.";
                    throw new InvalidOperationException(message);
                }
                // set resolver
                DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
                // after initialization configuration for container
                container.Configure(StructureMapDependencyResolverInitialization.AfterInitializationConfiguration);
            }
    
            /// <summary>
            /// Initializes the instance.
            /// </summary>
            public void Initialize(InitializationEngine context)
            {
            }
    
            /// <summary>
            /// Uninitializes the instance.
            /// </summary>
            public void Uninitialize(InitializationEngine context)
            {
            }
    
            /// <summary>
            /// Not implemented.
            /// </summary>
            public void Preload(string[] parameters)
            {
            }
    
            private static void AfterInitializationConfiguration(ConfigurationExpression expression)
            {
                if (expression != null)
                {
                        // default lifecycle is transient according to documentation
                        // if you need to replace some implementation
                        //expression.For<ISomeService>().Use<MyServiceImplementation>();
                }
            }
         }
    }

    And then the constructor injection works and in the AfterInitializationConfiguration you can switch also implementation for something if you have the need.

    #187360 Edited, Jan 19, 2018 22:47
  •  

    Activating the StructureMapDependencyResolver causes the following error:

    Unable to create a build plan for concrete type ServiceAccessor<LayoutModel>
    
    new ServiceAccessor<LayoutModel>(Object, IntPtr method)
     ┗ Object = **Default**
            IntPtr method = Required primitive dependency is not explicitly defined
    
    
    1.) Attempting to create a BuildPlan for Instance of ServiceAccessor<LayoutModel> -- ServiceAccessor<LayoutModel>
    2.) new Injected`1(*Default of ServiceAccessor<LayoutModel>*)
    3.) Injected<LayoutModel>
    4.) Instance of Injected<LayoutModel>
    5.) new _Page_Features_StartPage_Views_Index_cshtml(){
       Set Injected<LayoutModel> LayoutModel = *Default of Injected<LayoutModel>*
    }
    6.) ASP._Page_Features_StartPage_Views_Index_cshtml
    7.) Instance of ASP._Page_Features_StartPage_Views_Index_cshtml
    8.) Container.GetInstance(ASP._Page_Features_StartPage_Views_Index_cshtml)

    I'm using the LayoutModel approach suggested in this article instead of the IPageViewModel<>: http://marisks.net/2015/05/18/episerver-strongly-typed-layout-model-without-ipageview/

    So is there a way to have both the LayoutModel and the constructor Injection working at the same time? It has worked before using the following setup

    public void ConfigureContainer(ServiceConfigurationContext context)
            {
                context.Container.Configure(ConfigureContainer);
    
                DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container));
            }
    
    private static void ConfigureContainer(ConfigurationExpression container)
            {
                //Implementations for custom interfaces can be registered here.
                container.For<IContentRenderer>().Use<ErrorHandlingContentRenderer>();
                container.For<LayoutModel>().HybridHttpOrThreadLocalScoped().Use<LayoutModel>();
    }

    But this is no longer possible

    #187375 Jan 22, 2018 9:46
  • Antti Alasvuo
    Member since: 2010
     

    Tobias, I don't use injection to the view page but Injected<SomeType> works correctly on controllers in my solution.

    Did you change your code to use the configuration as I pasted above?

    #187383 Jan 22, 2018 10:46
  •  

    Yes I tried the setup but it give me the result specified in my last answer. I seems like I need to resolve the LayoutModel before setting the StructureMapDependencyResolver as as resolver since that cannot resolve the dependency, I don't know.

    All custom dependencies work with the initial setup but trying to activate the resolver to use constructor injection of EPiServer.ServiceLocation related service breaks the setup. I can still use the Injected<IContentLoader> approach but not the contructor injection.

    Before this version I could do this

    public void ConfigureContainer(ServiceConfigurationContext context)
            {
                context.Container.Configure(ConfigureContainer);
    
                DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container));
            }
    
            private static void ConfigureContainer(ConfigurationExpression container)
            {
                //Swap out the default ContentRenderer for our custom
                container.For<IContentRenderer>().Use<ErrorHandlingContentRenderer>();
                container.For<ContentAreaRenderer>().Use<AlloyContentAreaRenderer>();
    
                //Implementations for custom interfaces can be registered here.
    
                container.For<LayoutModel>().Use<LayoutModel>().SetLifecycleTo<HttpContextLifecycle>();
            }

    And that would lead me into thinking I could do this

    context.StructureMap().Configure(c =>
                    {
                        c.For<IContentRenderer>().Use<ErrorHandlingContentRenderer>();
                        c.For<LayoutModel>().Use<LayoutModel>().SetLifecycleTo<HttpContextLifecycle>();
                        c.For<IContentReferenceCache>().Use<DefaultContentReferenceCache>();
                    }
     
               );
    
                 DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.StructureMap()));

    But i doesn't work

    #187393 Edited, Jan 22, 2018 14:06
  • Johan Björnfot
    Member since: 2004
     

    The reason that your code does not work is that the type ServiceAccessor<LayoutModel> is not registered in container (note that the type LayoutModel is but not ServiceAccessor<LayoutModel>). 

    You can solve it in any of the follwing ways:

    1) Register your models using EPiServer registration API instead of directly towards StructureMap as (note the AddServiceAccessor call):

        context.Services
                    .AddTransient<IContentRenderer, ErrorHandlingContentRenderer>()
                    .AddHttpContextScoped<LayoutModel, LayoutModel>().AddServiceAccessor()
                    .AddTransient<IContentReferenceCache, DefaultContentReferenceCache>();

    2) Remove the registration for LayoutModel from IConfigurableModule and instead register it through ServiceConfigurationAttribute like:

     [ServiceConfiguration(Lifecycle = ServiceInstanceScope.HttpContext)]
        public class LayoutModel
    {...

    3) Register ServiceAccessor<LayoutModel> directly towards StructureMap by adding row to your direct StructureMap configuration

     c.For<LayoutModel>().Use<LayoutModel>().SetLifecycleTo<HttpContextLifecycle>();
                    c.For<ServiceAccessor<LayoutModel>>().Use(co => new ServiceAccessor<LayoutModel>(() => co.GetInstance<LayoutModel>()));
    #187541 Jan 25, 2018 13:32
  •  

    Yes, a combination Anttis and Johans solutions solved it. Anttis solution solved the issue with the IControllerFactory and then either placing the third option in Johans solution in the AfterInitializationConfiguration or use the second option directly solved the LayoutModel issue.

    I went with option 2 since it made it a bit clearer and directly connected to the LayoutModel and keeping the main IoC declarations in the InitializationModule

    Big thanks!

    #187546 Jan 25, 2018 16:02
  • valdis iljuconoks
    Member since: 2011
     

    Try to avoid using `Injected<T>` as this is anti-pattern anyway (under the hood). If possible - inject dependencies directly in target type via constructor.

    #187552 Jan 26, 2018 0:29