I am creating a IPlugin and need to register a singleton service in the IOC by interface
In this case: IWebhookSubscriptionStore.
By default my plugin wants to use a MemoryWebhookSubscriptionStore, so on IPlugin.Register()
we do this:
public class WebhookFeature : IPlugin
{
public void Register(IAppHost appHost)
{
var container = appHost.GetContainer();
container.RegisterAutoWiredAs<MemoryWebhookSubscriptionStore, IWebhookSubscriptionStore>();
}
}
However, my plugin also wants to allow developers to specify their own implementation of IWebhookSubscriptionStore so presumably we will have a public property on the plugin to allow them to specify theirs in an initializer of the feature
public IWebhookSubscriptionStore SubscriptionStore { get; set; }
So that they can do something like this:
Plugins.Add(new WebhookFeature
{
SubscriptionStore = new MyCustomSubscriptionStore(),
});
My question is: what is the common pattern that permits this?, since whatever type they provide must also be registered in the IOC.
So presumably my plugin with need to register it in the IOC in the Register() method for them too.
Should I give them a public property, which has a default value, or perhaps better to force them to specify their type in the ctor of the plugin?
Does the SubscriptionStore need to be changeable during execution of services, or it’s needed to set up only once? I think you have a second case, and you can add the constructor to WebHookFeature with optional SubscriptionStore parameter and then register it in IOC in Register() method. For example CorsFeature uses this way.
You should check to see if the user has has registered a dependency before overriding it with your default, e.g:
public void Register(IAppHost appHost)
{
var container = appHost.GetContainer();
if (!container.Exists<IWebhookSubscriptionStore>())
container.RegisterAutoWiredAs<MemoryWebhookSubscriptionStore, IWebhookSubscriptionStore>();
}
The Plugins Register() gets called after AppHost.Configure() so you need to check that you’re not overriding an existing registration as last registration wins.
I think the design I want is to have a default store and register that if no other store is found in the container.
Just like MemoryCacheClient is the default ICacheClient.
Or allow the developer to provide their own store in the ctor() override. Which should be the only store in the IOC container (regardless of whether there was one registered already).
public class WebhookFeature : IPlugin
{
private readonly IWebhookSubscriptionStore subscriptionStore;
public WebhookFeature()
{
}
public WebhookFeature(IWebhookSubscriptionStore store)
{
Guard.AgainstNull(() => store, store);
subscriptionStore = store;
}
public void Register(IAppHost appHost)
{
var container = appHost.GetContainer();
if (subscriptionStore != null)
{
container.Register(subscriptionStore);
}
else
{
if (!container.Exists<IWebhookSubscriptionStore>())
{
container.RegisterAutoWiredAs<MemoryWebhookSubscriptionStore, IWebhookSubscriptionStore>();
}
subscriptionStore = container.Resolve<IWebhookSubscriptionStore>(); // just for parity
}
}
}
It’s a little ugly having the member variable subscriptionStore hanging around, but that’s only because the default MemoryWebhookSubscriptionStore uses ICacheClient under hood, and I cannot find a great way to instantiate MemoryWebhookSubscriptionStore in the ctor so that it uses the currently registered ICacheClient in the IOC at that time.
I don’t actually need it yet, it is just there to track whether they provided a store of their own in the ctor() override.
I’d rather get rid of it all together. Can you see a way to do that?
public class WebhookFeature : IPlugin
{
public void Register(IAppHost appHost)
{
var container = appHost.GetContainer();
if (!container.Exists<IWebhookSubscriptionStore>())
container.RegisterAutoWiredAs<MemoryWebhookSubscriptionStore, IWebhookSubscriptionStore>();
}
}
If they want to provide a different IWebhookSubscriptionStore they can register it in the IOC.
OK, well I was designing it to allow them to specify their store in a ctor override explicitly, which keeps everything nicely together (in one line) in the registration of the feature. I thought this would be better usability:
Just good documentation should suffice. Your example on how to use a feature can just show how to provide your own IWebhookSubscriptionStore. If you want to add it in the constructor than you’re going to need all that default boilerplate back.
I prefer to avoid properties for just holding dependencies as Users won’t know all you’re doing is just registering it in the IOC and that they can resolve it themselves if they need to.