ASP.NET Core 一个接口多个实现的依赖注入与动态选择

2019-07-15 11:43:14 浏览数 (1)

点击蓝字

关注我

ASP.NET Core 自带的依赖注入(DI)非常实用,但是当一个接口有多个实现的时候怎么操作呢?运行时能否根据配置选择其中一种实现呢?能不能不用反射呢?很多小伙伴都有这样的疑问。今天我带大家看看如何在ASP.NET Core里不依赖反射,根据配置文件,在运行时动态选择一个接口的具体实现。

首先,这个需求其实来自我自己的博客系统。我的图片存储有两套:Azure Blob和文件系统,因此我写了一个接口,用了2套实现。想要做到能随时切换云存储或本地文件系统来保存博客文章的配图。因为这套代码比较复杂,因此我用一个最简明的例子来演示这个小技巧。

接口定义与两套实现

首先,接口长这样:

public interface IHelloer

{

string SayHello();

}

有两个实现:

public class HelloerA : IHelloer

{

public string SayHello()

{

return $"Hello from {nameof(HelloerA)}";

}

}

public class HelloerB : IHelloer

{

public string SayHello()

{

return $"Hello from {nameof(HelloerB)}";

}

}

注册依赖注入

和注册常规的ASP.NET Core DI完全一样,我们把HelloerAHelloerB都注册进去:

services.AddTransient<IHelloer, HelloerA>();

services.AddTransient<IHelloer, HelloerB>();

构造函数注入

刚才我们注册了同一接口的两套实现,那么ASP.NET Core运行时候会选择哪个实现呢?好奇可以试试:

public IHelloer Hello { get; set; }

public HomeController(IHelloer hello)

{

Hello = hello;

}

把结果输出到浏览器:

public IActionResult Index()

{

var message = Hello.SayHello();

return Content(message);

}

发现出来的是HelloB,就是我们注册DI时候顺序在最后的那个实现。

那么问题来了,我想要运行时选择HelloA怎么办?请往下看。

构造函数居然还能这样注入

其实,在ASP.NET Core中,当你对一个接口注册了多个实现的时候,构造函数是可以注入一个该接口集合的,这个集合里是所有注册过的实现。

private IEnumerable<IHelloer> Helloers { get; set; }

public HomeController(IEnumerable<IHelloer> helloers)

{

Helloers = helloers;

}

这样子出来的就是HelloA了:

public IActionResult Index()

{

var message = Helloers.First().SayHello();

return Content(message);

}

当然,在项目里,我们绝对是不能这样写死代码的,我们希望通过配置文件来选择具体实现。请往继续往下看。

配置文件

在appsettings.json里加入新配置:

"AppSettings": {

"CurrentHelloer": "HelloerA"

}

并且建立一个对应的class

public class AppSettings

{

public string CurrentHelloer { get; set; }

}

同样注册DI及构造器注入

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

private AppSettings AppSettings { get; set; }

private IEnumerable<IHelloer> Helloers { get; set; }

public HomeController(IEnumerable<IHelloer> helloers, IOptions<AppSettings> settings)

{

Helloers = helloers;

AppSettings = settings.Value;

}

这样一来,就可以用LINQ非常方便的根据配置文件,从接口集合中选择对应名字的实现:

public IActionResult Index()

{

var helloer = Helloers.FirstOrDefault(h => h.GetType().Name == AppSettings.CurrentHelloer);

var message = helloer?.SayHello();

return Content(message);

}

现在根据配置文件运行出来的就是HelloA了:

但是这个代码里竟然用了非政治正确的所谓影响性能的反射。为了避免代码被人鄙视,我们可以用一个workaround脱离反射。

不依赖反射

刚才我们用反射无非就是为了在运行时获得具体实现的class的名字。所以其实只要自己给每个class都加上一个名字属性就行了。并且我们可以利用nameof()来增加代码可维护性。

修改接口:

public interface IHelloer

{

string CurrentName { get; }

string SayHello();

}

修改实现:

public class HelloerA : IHelloer

{

public string CurrentName => nameof(HelloerA);

public string SayHello()

{

return $"Hello from {nameof(HelloerA)}";

}

}

public class HelloerB : IHelloer

{

public string CurrentName => nameof(HelloerB);

public string SayHello()

{

return $"Hello from {nameof(HelloerB)}";

}

}

修改LINQ:

public IActionResult Index()

{

var helloer = Helloers.FirstOrDefault(h => h.CurrentName == AppSettings.CurrentHelloer);

var message = helloer?.SayHello();

return Content(message);

}

完美运行:

0 人点赞