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?
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) { } }
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.
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.
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
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?
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
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>()));
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!
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:
A bit unsure of the required settings to make the resolver work. The code in the DependencyResolver InitializationModule looks like this:
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