在netcore中如何注入同一个接口的多个实现

C#

浏览数:118

2019-5-12

AD:资源代下载服务

netcore中自带了Ioc框架,这也影响了我们的编码习惯,以前都是静态类或者直接new对象,现在有了Ioc框架的支持,我们也不必守旧,应当使用起来,接受这种对象管理方式。使用过java的同仁,都习惯了Spring,感觉离开了Spring就好像失去了灵魂一样。Spring经过多年的沉淀,非常的稳定和灵活,相比之下,netcore中自带的Ioc框架太过轻量,中规中矩的使用还算够用,但是其灵活性的确有待加强。比如属性注入、字段注入、方法参数注入等等,虽然说官方强烈建议使用构造函数注入,并且只提供了构造函数注入,但是我感觉后期肯定会支持更多都注入方法的,并且会结合Attribute提供更为灵活的功能,期望这一天能快点到来!

今天主要讨论其中一个问题,就是如何在netcore中注入同一个接口的多个实现,注册多个实现并不难,关键是如何优雅的取出来。下面我们用代码来说话:

    public interface IServiceA
    {
        string GetStr();
    }

    public class ImplA1 : IServiceA
    {
        public string GetStr()
        {
            return "ImplA1";
        }
    }

    public class ImplA2 : IServiceA
    {
        public string GetStr()
        {
            return "ImplA2";
        }
    }

上面的代码是有一个服务接口IServieA,和两个不同的实现类。如果我们这样注册:

services.AddSingleton<IServiceA>(new ImplA1());
services.AddSingleton<IServiceA>(new ImplA2());

第一种使用方式:

    public class HomeController : Controller
    {
        private readonly IServiceA serviceA;

        public HomeController(IServiceA serviceA)
        {
            this.serviceA = serviceA; //这里注入进来的是最后一个实现,也就是ImplA2
        }

        public IActionResult Index()
        {
            return Content(serviceA.GetStr());
        }
    }

这种方法肯定是不行的,因为只能获取注册的最后一个实现。

第二种使用方式:

    public class HomeController : Controller
    {
        private readonly IServiceA serviceA;

        public HomeController(IEnumerable<IServiceA> serviceAList)
        {
            this.serviceA = serviceAList.First(); //这里注入进来的是一个服务集合
        }

        public IActionResult Index()
        {
            return Content(serviceA.GetStr());
        }
    }

这种方法也不好,需要自己在集合中筛选要想的实现。

其实我们更希望是这样的,注册的时候可以使用一个标识,然后使用Attribute指定标识来获取具体的实现:

services.AddSingleton<IServiceA>("impla1",new ImplA1()); //impla1为该实现在该接口的唯一标识
services.AddSingleton<IServiceA>("impla2",new ImplA2());  //同上

可能会有这么一个Attribute类:

    public class IdentifierAttribute : Attribute
    {
        public IdentifierAttribute(string id)
        {
            this.Id = id;
        }
        public string Id { get; set; }
    }

然后使用的时候大概是这样:

        public HomeController([Identifier("impla1")]IServiceA serviceA)
        {
            this.serviceA = serviceA; //根据Identifier的指定,这里应该是ImplA1,
        }

如果能像Spring中@Autowired注解,注入字段就更好了:

        [Autowired]
        [Identifier("impla1")]
        private readonly IServiceA serviceA;

然而,我们该醒醒了,netcore中自带的Ioc没有提供类似这样的功能,我找了很久也没找到。

作为程序员,我们都要具备一种特质,那就是爱思考,经过思考,我感觉可以使用工厂来解决这个问题。这里只是提供一种思路和实现,有更好的方法大家可以留言讨论。
首先我们定义一个用于存储单例的集合类:

   public class SingletonFactory
    {

        Dictionary<Type, Dictionary<string, object>> serviceDict;
        public SingletonFactory()
        {
            serviceDict = new Dictionary<Type, Dictionary<string, object>>();
        }

        public TService GetService<TService>(string id) where TService : class
        {
            var serviceType = typeof(TService);
            return GetService<TService>(serviceType, id);
        }

        public TService GetService<TService>(Type serviceType, string id) where TService : class
        {
            if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
            {
                if (implDict.TryGetValue(id, out object service))
                {
                    return service as TService;
                }
            }
            return null;
        }

        public void AddService<TService>(TService service, string id) where TService : class
        {
            AddService(typeof(TService), service, id);
        }

        public void AddService(Type serviceType, object service, string id)
        {
            if (service != null)
            {
                if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
                {
                    implDict[id] = service;
                }
                else
                {
                    implDict = new Dictionary<string, object>();
                    implDict[id] = service;
                    serviceDict[serviceType] = implDict;
                }
            }
        }
    }

这个类中使用一个嵌套的字典来存储服务类的实现,提供添加服务和获取服务的方法。

有了这样一个类,在netcore自带的Ioc中我们就可以获取更多的控制权了。且看下面代码。

注册服务:

            SingletonFactory singletonFactory = new SingletonFactory();
            singletonFactory.AddService<IServiceA>(new ImplA1(), "impla1");
            singletonFactory.AddService<IServiceA>(new ImplA2(), "impla2");

            services.AddSingleton(singletonFactory);

我们把同一个接口的多个实现都加到SingletonFactory中,然后把SingletonFactory注册到Ioc。

使用服务:

        private readonly IServiceA serviceA;
        public HomeController(SingletonFactory singletonFactory)
        {
            this.serviceA = singletonFactory.GetService<IServiceA>("impla2"); //使用标识从SingletonFactory获取自己想要的服务实现
        }

使用服务的时候我们注入SingletonFactory,然后从SingletonFactory根据标识获取具体的服务实现。

这个方法还算优雅吗?😄,目前来看我是可以接受了,不过还是希望官方能早些提供更强大更灵活的功能,期待netcore会越来越好!

作者:loogn