Search code examples
xamarin.formsprismdryioc

How to reserve registered service usage with passing params in constructor?


I am a newbie to OOP and IOC container. I need to understand how to use depency injection. My app's simple structure is as below.

The code below belongs to CacheService:

using Akavache;

    public class CacheService : ICacheService
    {
        protected IBlobCache Cache;

        public CacheService(IBlobCache blobCache, string applicationName)
        {
            Cache = blobCache;
            BlobCache.ApplicationName = applicationName;
        }
    }

I have registered this service as below in App.cs Because I need to use the same service with 2 different types.

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterSingleton<IMockApiService, MockApiService>();
    containerRegistry.RegisterSingleton<IEssentialsService, EssentialsService>();

    containerRegistry.RegisterInstance<CacheService>(new CacheService(BlobCache.LocalMachine, "MyAppName"), "LocalMachineCache");
    containerRegistry.RegisterInstance<CacheService>(new CacheService(BlobCache.UserAccount, "MyAppName"), "UserAccountCache");
}

My ViewModel code is as below:

public class SettingsPageViewModel : BindableBase
{
    protected ICacheService CacheService { get; private set; }

    public SettingsPageViewModel(ICacheService cacheService)
    {
        CacheService = cacheService;
    }
}

Is above usage possible or not? If it is possible how should I modify my structure? Thank you in advance.


Solution

  • As far as I can tell, this usage is not possible because your IoC container would not know how to resolve ICacheService, since you registered the instances as CacheService. When DryIoc tries to resolve SettingsPageViewModel it would encounter ICacheService and find the type not being registered. At least this is how the Unity container behaves.

    Anyway, even if you registered "LocalMachineCache" and "UserAccountCache" as ICacheService I doubt that it'd work, since there would be no way to know for DryIoc which one shall be used with SettingsPageViewModel, hence you'd have to define which one to resolve.

    According to its documentation, Prism resolves dependencies by the respective parameter name (unless I did not misunderstand this, see here). Assuming that you need the local machine cache in your settings, you could register it as "localMachineCache" (renamed it to stick with common C# coding conventions in the constructor of SettingsPageViewModel) and give the parameter that very name

    containerRegistry.RegisterInstance<ICacheService>(new CacheService(BlobCache.LocalMachine, "MyAppName"), "localMachineCache");
    containerRegistry.RegisterInstance<ICacheService>(new CacheService(BlobCache.UserAccount, "MyAppName"), "userAccountCache");
    
    public SettingsPageViewModel(ICacheService localMachineCache)
    {
        CacheService = localMachineCache;
    }
    

    And accordingly if you need both

    public SettingsPageViewModel(ICacheService localMachineCache, ICacheService userAccountCache)
    {
        MachineCacheService = localMachineCache;
        UserAccountCacheService = userAccountCache;
    }
    

    Edit

    Although it is written in the docs, named services don't seem to work the way the documentation states. However, depending on the choice of IoC container it's still possible injecting services by their names.

    DryIoc

    With the made parameter of IContainer.Register you can specify a factory function to create instances of a specified type. Since Prism puts an IoC abstraction over the framework used, you'll first have to obtain the DryIoc IContainer instance

    var container = ((DryIocContainerExtension)containerRegistry).Instance;
    

    now you can specify how to create instances of SettingsPageViewModel with

    container.Register(Made.Of(
        () => new SettingsPageViewModel(Arg.Of<ICacheService>("LocalMachineCache"))));
    

    The string passed to Arg.Of is the service key, the service has been registered with and tells DryIoc which ICacheService to resolve (please note that I've used your original name).

    Unity

    With unity there are at least two ways to achieve this. The first one is to obtain an instance via UnityContainerExtension (similar to the DryIoc version) and use RegisterFactory

    unityContainer.RegisterFactory<SettingsPageViewModel>(
        container => new SettingsPageViewModel(container.Resolve<ICacheService>("LocalMachineCache")));
    

    The other option is to add the DependencyAttribute to the constructor parameters of the viewmodel

    public SettingsPageViewModel([Unity.Dependency("LocalMachineCache")] ICacheService localMachineCache)
    {
        CacheService = localMachineCache;
    }