November Happy Hour will be moved to Thursday December 5th.
November Happy Hour will be moved to Thursday December 5th.
Hi Chris,
I don't think there's a way to initialize different modules per site, as the initialization is tied to the global IIS process.
What you can do is introduce a facade of IService. Have multiple implementations of this interface, but only inject one implementation via DI. This single implementation should check the context site and then fetch and call the correct impl accordingly.
Here's a basic pseudo-code example:
public interface IService
{
void Foo();
}
public class ServiceOne : IService
{
void Foo() {
// do some specific logic for ServiceOne
}
}
public class ServiceTwo : IService
{
void Foo() {
// do some specific logic for ServiceTwo
}
}
public class ServiceFacade : IService
{
private IService contextualService;
ServiceFacade() {
// inspect SiteDefinition.Current and set contextualService to either ServiceOne or ServiceTwo
}
void Foo() {
return contextualService.Foo();
}
}
You could get a bit fancy with ServiceFacade and have it leverage reflection to create the proper impl, or it could be as simple as a switch statement.
Thanks Dylan for your suggestion.
It think the key takeaway with this is we need to rely on SiteDefinition, which can be a little risky. I have this currently working by using StructureMap to name my service implementations:
context.StructureMap().Configure(config => config.For<IService>().Use<SiteAService>().Named("SiteA"));
Then when we need to retrieve it:
var serviceFunc = ServiceLocator.Current.GetInstance<Func<string, IService>>();
var service = serviceFunc(SiteDefinition.Current.Name); // But we assume the site is configured properly to be named "SiteA"
Or (more cleanly) it can be retrieved like this:
var service = ServiceLocator.Current.GetInstance<IService>(SiteDefinition.Current.Name);
As I was working through this, though, I remembered we still have access to the current site's StartPage via SiteDefinition.Current.StartPage, so instead of relying on the Name of the current site during retrieval, I can hardcode a value on the StartPage model to reference. Only minor drawback with this is we need to use an interface on all of the sites' StartPage, and each site needs to have a unique StartPage content type (which I think is normal).
So:
public interface ISiteHomePage : IContentData
{
string SiteName { get; }
}
[ContentType(DisplayName = "[Site A] Home Page", GUID = "{GUID HERE}")]
public class SiteAHomePage : PageData, ISiteHomePage
{
[Ignore]
public string SiteName => "SiteA";
}
Then:
var siteName = _contentLoader.Get<ISiteHomePage>(ContentReference.StartPage).SiteName; // Or SiteDefinition.Current.StartPage
var service = ServiceLocator.Current.GetInstance<IService>(siteName);
The next step of this (if anyone wants to tackle it), is to make this support contructor-based injection, instead of relying on ServiceLocator.
With that, though, I'm not a fan of using SiteDefinition, but I don't see another way we can do this.
Is there a way to run an InitializationModule for only a specific site in a multi-site solution?
Further explaination... Say I have a multi-site solution, sharing the same Episerver codebase, but each site still has some uniqueness to it. In my case, I have a global `IService` interface that needs to be implemented for each site. Each site has it's own InitializationModule (IConfigurableModule), where I register the service's implementation for that site. The issue is since all InitializationModules are scanned and initalized, eventually the implementation of `IService` for Site A gets "overwritten" by the implementation of `IService` for Site B.