Home > Mobile >  How to register a service with multiple interfaces in a WPF application?
How to register a service with multiple interfaces in a WPF application?

Time:03-25

I have a small WPF application which uses IServiceCollection to inject dependencies within the App.xaml.cs file. Also, I have created a single view model which is responsible for managing the canvas on the main window. On the canvas, there are buttons which can have commands (I call them activities) added to them dynamically. All the commands need access to the view model so I have injected it into the activities.

// App.xaml.cs
private void ConfigureServices(ServiceCollection services)
{
     services.AddSingleton<HyperViewModel>();
     services.AddSingleton<HideEditor>();
     …
}

// ViewModel 
public partial class HyperViewModel : INotifyPropertyChanged, IHyperData, 
    IHyperLayout, IHyperContext, IHyperController { ... }

// Example activity 
public class HideEditor : Activity
{
    private HyperViewModel _hypermodel;
    public void HideEditor(HyperViewModel hypermodel) 
    {
        _hypermodel = hypermodel;; 
    }

    // Activity handler
    protected override void HandleActivity()
    {
        // using _hypermodel in here works great
        …
    }
}

Everything there works as expected and I can get access to the view model within my activity classes as expected. As I have developed my view model class is getting bigger, so I want to separate my dependencies into various interfaces. The view model class shows some of those interfaces in the example above. I have added the narrowed interfaces into the IOC container and injected one of them into my HideEditor activity..

// App.xaml.cs
private void ConfigureServices(ServiceCollection services)
{
    services.AddSingleton<HyperViewModel>();
    services.AddSingleton<IHyperData,HyperViewModel>();
    services.AddSingleton<IHyperLayout,HyperViewModel>();
    services.AddSingleton<IHyperContext,HyperViewModel>();
    services.AddSingleton<IHyperController,HyperViewModel>();
    services.AddSingleton<HideEditor>();
    …
}

// Example activity 
public class HideEditor : Activity
{
    private IHyperLayout _hyperlayout;
    public void HideEditor(IHyperLayout hyperlayout) 
    {
        _hyperlayout = hyperlayout; 
    }

    // Activity handler
    protected override void HandleActivity()
    {
        // using _hyperlayout in here injects the view model
        // but it seems to be a different instance
        …
    }
}

I would expect that the injected IHyperLayout interface would allow me to access the view model singleton through the interface that was injected at startup. However, the properties are different and it seems that it is a new uninitialized version of the view model. I am confused as to why the interface injected doesn’t point to the singleton instance.

CodePudding user response:

Seems, that I come across the answer almost immediately after I spend all the time to ask it. In any case it was simple. The instances were created each time I added them as singletons. The GetRequiredService delegate allowed me to select the correct instance. From then, I was able to access the instance through the dependency injection.

services.AddSingleton<HyperViewModel>();
services.AddSingleton<IHoneycombData>(x => x.GetRequiredService<HoneycombService>());
services.AddSingleton<IHyperContext>(x => x.GetRequiredService<HyperViewModel>());
services.AddSingleton<IHyperController>(x => x.GetRequiredService<HyperViewModel>());

Here is a link to an example where I found the answer

CodePudding user response:

If you want to prettify the syntax, improve readability and also add some convenience, I can offer you this custom extension methods:

Usage

To register the interfaces explicitly:

private void ConfigureServices(ServiceCollection services)
{
  services.AddMultiExportSingleton<HyperViewModel>()
    .AsService<IHoneycombData>()
    .AsService<IHyperContext>()
    .AsService<IHyperController>()
  .AddSingleton<SomeOtherType>()
  .BuildServiceProvider();
}

To register all implemented interfaces of a concrete type:

private void ConfigureServices(ServiceCollection services)
{
  services.AddMultiExportSingleton<HyperViewModel>()
    .AsImplementedServices()
  .AddSingleton<SomeOtherType>()
  .BuildServiceProvider();
}

Implementation

IMultiExportServiceCollection.cs
A facade for the current IServiceCollection to extend its features.

// Use the original .NET namespace for convenience
namespace Microsoft.Extensions.DependencyInjection
{
  using System;

  public interface IMultiExportServiceCollection : IServiceCollection
  {
    IServiceCollection ServiceCollection { get; }
    Type TImplementation { get; }
  }
}

MultiExportServiceCollection.cs
Implementation of the facade for the current IServiceCollection to extend its features.

// Use the original .NET namespace for convenience
namespace Microsoft.Extensions.DependencyInjection
{
  using System;
  using System.Collections;
  using System.Collections.Generic;

  public class MultiExportServiceCollection : IMultiExportServiceCollection
  {
    public IServiceCollection ServiceCollection { get; }
    public Type TImplementation { get; }
    public int Count => this.ServiceCollection.Count;
    public bool IsReadOnly => this.ServiceCollection.IsReadOnly;
    public ServiceDescriptor this[int index]
    {
      get => this.ServiceCollection[index];
      set => this.ServiceCollection[index] = value;
    }

    public MultiExportServiceCollection(IServiceCollection serviceCollection, Type tImplementation)
    {
      this.ServiceCollection = serviceCollection;
      this.TImplementation = tImplementation;
    }

    public int IndexOf(ServiceDescriptor item) => this.ServiceCollection.IndexOf(item);

    public void Insert(int index, ServiceDescriptor item) => this.ServiceCollection.Insert(index, item);

    public void RemoveAt(int index) => this.ServiceCollection.RemoveAt(index);

    public void Add(ServiceDescriptor item) => this.ServiceCollection.Add(item);

    public void Clear() => this.ServiceCollection.Clear();

    public bool Contains(ServiceDescriptor item) => this.ServiceCollection.Contains(item);

    public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => this.ServiceCollection.CopyTo(array, arrayIndex);

    public bool Remove(ServiceDescriptor item) => this.ServiceCollection.Remove(item);

    public IEnumerator<ServiceDescriptor> GetEnumerator() => this.ServiceCollection.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => this.ServiceCollection.GetEnumerator();
  }
}

DependencyInjection.cs
A set of extension method to help registering services with an IServiceCollection.

// Use the original .NET namespace for convenience
namespace Microsoft.Extensions.DependencyInjection
{
  using System;
  using System.Collections;

  static class DependencyInjection
  {
    /// <summary>    /// 
    /// Register a service implementation with multiple interfaces. Use <see cref="AsService{TService}(IMultiExportServiceCollection)"/> to attach more service interfaces to the service implementation <typeparamref name="TImplementation"/>
    /// or use <see cref="AsImplementedServices(IMultiExportServiceCollection)"/> to register all implemented interfaces of the implementation.
    /// </summary>
    /// <typeparam name="TImplementation"></typeparam>
    /// <param name="serviceCollection"></param>
    /// <returns>An <see cref="IMultiExportServiceCollection"/> which implements <see cref="ICollection"/> and aggreagates multiple service interfaces mapped to a single implementation.</returns>
    public static IMultiExportServiceCollection AddMultiExportSingleton<TImplementation>(this IServiceCollection serviceCollection) where TImplementation : class
    {
      serviceCollection.AddSingleton<TImplementation>();
      return new MultiExportServiceCollection(serviceCollection, typeof(TImplementation));
    }

    /// <summary>
    /// Add another interface service to the implementation registered with a call to <see cref="AddMultiExportSingleton{TImplementation}(IServiceCollection)"/>.
    /// </summary>
    /// <typeparam name="TService"></typeparam>
    /// <param name="serviceCollection"></param>
    /// <returns>An <see cref="IMultiExportServiceCollection"/> which implements <see cref="ICollection"/> and aggreagates multiple service interfaces mapped to a single implementation.</returns>
    public static IMultiExportServiceCollection AsService<TService>(this IMultiExportServiceCollection serviceCollection) where TService : class
    {
      serviceCollection.AddSingleton(serviceProvider => serviceProvider.GetService(serviceCollection.TImplementation));
      return serviceCollection;
    }

    /// <summary>
    /// Adds all implemented interface services to the implementation 
    /// previously registered with a call to <see cref="AddMultiExportSingleton{TImplementation}(IServiceCollection)"/>.
    /// </summary>
    /// <param name="serviceCollection"></param>
    /// <returns>An <see cref="IServiceCollection"/>.</returns>
    public static IServiceCollection AsImplementedServices(this IMultiExportServiceCollection serviceCollection)
    {
      Type[] interfaces = serviceCollection.TImplementation.GetInterfaces();
      foreach (Type interfaceType in interfaces)
      {
        serviceCollection.AddSingleton(interfaceType, serviceProvider => serviceProvider.GetService(serviceCollection.TImplementation));
      }

      return serviceCollection;
    }
  }
}
  • Related