Any way to name multiples of the same type?

2019-03-04 02:11发布

问题:

Note: using .NET Core 2.0 [Microsoft.Extensions.DependencyInjection].

Here's what I would like to do:

IServiceCollection collection = new ServiceCollection();
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel>(nameof(View1));
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel>(nameof(View2));

But I can't figure out a way to name services added to the collection. If the type used repeats without a name specified, the collection appears to simply overwrite the existing service with the new one. Ideally, I would be able to reuse a type, but differentiate it by supplying a name.

Please ignore the contrived example.

Q: Is this possible?

回答1:

Given the fact that the ServiceDescriptor class doesn't have a Name property or any way to set a string identifier and the classes for resolving services are marked internal, I would say the answer is no.


However, it's not very difficult to build your own extensions to fake it.

NamedServiceDescriptor

class NamedServiceDescriptor
{
    public NamedServiceDescriptor(string name, Type serviceType)
    {
        this.Name = name;
        this.ServiceType = serviceType;
    }

    public string Name { get; private set; }
    public Type ServiceType { get; private set; }

    public override bool Equals(object obj)
    {
        if (!(obj is NamedServiceDescriptor))
            return false;

        var other = (NamedServiceDescriptor)obj;

        return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) &&
            ServiceType.Equals(other.ServiceType);
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode() ^ 
            ServiceType.GetHashCode();
    }
}

Extension Methods

public static class ServiceCollectionExtensions
{
    internal static readonly IDictionary<NamedServiceDescriptor, Type> nameToTypeMap 
        = new ConcurrentDictionary<NamedServiceDescriptor, Type>();

    public static IServiceCollection AddSingleton<TService, TImplementation>(
        this IServiceCollection serviceCollection, 
        string name)
        where TService : class where TImplementation : class, TService
    {
        nameToTypeMap[new NamedServiceDescriptor(name, typeof(TService))] 
            = typeof(TImplementation);
        return serviceCollection.AddSingleton<TImplementation>();
    }
}

public static class ServiceProviderExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string name)
    {
        if (provider == null)
            throw new ArgumentNullException(nameof(provider));
        if (string.IsNullOrEmpty(name))
            throw new ArgumentNullException(nameof(name));

        ServiceCollectionExtensions.nameToTypeMap.TryGetValue(
            new NamedServiceDescriptor(name, typeof(T)), out Type implementationType);
        return (T)provider.GetService(implementationType);
    }
}

Usage

public interface IMyReusableViewModel { }
public class MyReusableViewModel1 : IMyReusableViewModel { }
public class MyReusableViewModel2 : IMyReusableViewModel { }

IServiceCollection collection = new ServiceCollection();
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel1>("View1");
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel2>("View2");


public class MyService
{
    private readonly IServiceProvider provider;

    public MyService(IServiceProvider provider)
    {
        this.provider = provider;
    }

    public void DoSomething()
    {
        var view1 = provider.GetService<IMyReusableViewModel>("View1");
        var view2 = provider.GetService<IMyReusableViewModel>("View2");

        // ...
    }
}

NOTE: That said, I wouldn't recommend this approach. If you require such functionality, it is a sign that the application design is inadequate. A design pattern such as Abstract Factory or Strategy may be what is needed to fill the void without resorting to naming type registrations or abusing the container as a Service Locator.

Alternatively, you could use a 3rd party DI container that supports this functionality.