Search code examples
wpfdependency-injectionprismunity-containerprism-7

WPF App using Prism 7: How to Registration and Navigation for a VM using a Interface with Multiple Concrete Classes


I am using Prism 7 to create a WPF application. In one of the View Models I am attempting to inject an interface called IActivityService. IActivityService has multiple concrete classes but for the purposes of the question, TestActivtyService and ExerciseActivityService. Below is how I have them registered:

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
      containerRegistry.Register<IActivityService, TestActivtyService>(InstanceNames.TestActivtyService);
      containerRegistry.Register<IActivityService, ExerciseActivityService>(InstanceNames.ExerciseActivityService);

      containerRegistry.RegisterForNavigation<Shell, ShellViewModel>(ViewNames.Shell);
      containerRegistry.RegisterForNavigation<Home, HomeViewModel>(ViewNames.Home);
      containerRegistry.RegisterForNavigation<ActivityManagement, ActivityManagementViewModel>(ViewNames.TestManagement);
}

The ActivityManagementViewModel has a constructor that utilizes IActivityService.

private readonly IActivityService _activityService;

public ActivityManagementViewModel(IRegionManager regionManager, IActivityService activityService)
    : base(regionManager)
{
    this._activityService = activityService;
}

I am having trouble navigating to the ActivityManagement View which is using the ActivityManagementViewModel.

private void NavigateToTesting()
{
    //Navigate to ActivityManagement with TestActivtyService injected as IActivityService
    RegionManager.RequestNavigate(RegionNames.MainRegion, ViewNames.ActivityManagement.GetUri());
}

private void NavigateToExercise()
{
    //Navigate to ActivityManagement with ExerciseActivityService injected as IActivityService
    RegionManager.RequestNavigate(RegionNames.MainRegion, ViewNames.ActivityManagement.GetUri());
}

Questions

  1. When I navigate to ActivityManagement view it always uses the last concrete class registered to inject into ActivityManagementViewModel as IActivityService. Why does this happen?
  2. How do I navigate to the view using the concrete class of choice at runtime?

The reason why I want to do this is because the view UI and requirements are exactly the same for each of the concrete implementations of the interface. The difference is the data & data repositories the concrete classes use. It doesn't make sense to me to recreate the same exact view and view models for each concrete implementation to display the data the same exact way. It would be also not be good to have to always have to add them when a new concrete implementation is introduced.

If I am missing something or if there is a better way of achieving this, please educate me.


Solution

  • When you register multiple types with the same interface with unity without naming, the last one wins by overwriting the previous ones. If you register with naming, you only have named registrations and no default registration, thus you'll get an error when resolving the view model.

    How do I navigate to the view using the concrete class of choice at runtime?

    I'd create a factory and inject that, like:

    internal class ActivityServiceFactory : IActivityServiceFactory
    {
        public IActivityService Create( ActivityServiceType activityServiceType )
        {
            switch (activityServiceType)
            {
                case ActivityServiceType.Test: return new TestActivtyService();
                case ActivityServiceType.Exercise: return new ExerciseActivityService();
                default: throw new IllegalArgumentException();
            }
        }
    }
    

    And then resolve the activity service in the view model. Adapt the factory as needed, e.g. different kinds of parameters of the products or singleton products. Evil but reasonable here, probably, would be to simply inject the container into the factory.