0%

[DotnetCore](S)FTP-ServiceResolver

前情提要

由前兩篇的議題延續下去,參考[DotnetCore]FTP-下載上傳[DotnetCore]SFTP-下載上傳,因為同樣都是實作IFtpService,同一個專案皆有使用到Ftp及SFtp則會面臨到此篇要解決的問題,該如何分辨要採用哪種實作的下載、上傳,有哪些方法可以解決,由筆者我來細說吧,主要參考 國外文章 :佛心的整理了五種不同的解決方式。

內容

Collection方式

效能有憂慮,基本上是注入時直接產出多個實作實體,依照條件選取其中一個,因此會有不需要用到的實體產出,屬比較不建議的做法。這個方式需要有一個可以識別的屬性欄位,因此改造一下IFtpServiceFtpServiceSFtpService,多加一個Name屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IFtpService
{
string Name { get; }
// 以下省略
}

public class FtpService : IFtpService
{
public string Name { get; }
public FtpService(IFilePathService filePathService, IConfiguration config)
{
this.Name = "F";
}
// 以下省略
}

註冊Service

1
2
3
4
5
6
7
public void ConfigureServices(IServiceCollection services)
{
// 以上省略
services.AddTransient<IFtpService, FtpService>();
services.AddTransient<IFtpService, SFtpService>();
// 以下省略
}

注入Service

製作一個假的Service,主要參考注入方式即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IExchangeService
{
void DoWork();
}

public class ExchangeService : IExchangeService
{
private readonly IFtpService _ftpService;

public ExchangeService(IEnumerable<IFtpService> ftpServiceCollection)
{
// 使用不同的Name的值取得不一樣的實作FtpService
_ftpService = ftpServiceCollection.SingleOrDefault(x => x.Name == "F");
}

public void DoWork()
{
throw new System.NotImplementedException();
}
}

Resolver方式

撰寫IFtpServiceResolver

宣告IFtpServiceResolver並實作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IFtpServiceResolver
{
IFtpService Resolve(string serviceName);
}

public class FtpServiceResolver : IFtpServiceResolver
{
private readonly IServiceProvider _provider;

public FtpServiceResolver(IServiceProvider provider)
{
_provider = provider;
}

public IFtpService Resolve(string serviceName)
{
var type = Assembly.GetAssembly(typeof(FtpServiceResolver)).GetType(serviceName);
var instance = this._provider.GetService(type);
return instance as IFtpService;
}
}

主要是注入IServiceProvider,透過Resolve方法傳入的ServiceName,透過注入得到的IServiceProvider取得對應的實體並做事。

註冊Service

註冊方式跟第一種Collection不太一樣,此種方式是透過Resolver建立實體,因此直接註冊實作Service即可。

1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
// 以上省略
services.AddTransient<FtpService>();
services.AddTransient<SFtpService>();
services.AddTransient<IFtpServiceResolver, FtpServiceResolver>();
// 以下省略
}

注入Service

改寫ExchangeService,這次改注IFtpServiceResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExchangeService : IExchangeService
{
private readonly IFtpService _ftpService;

public ExchangeService(IFtpServiceResolver ftpServiceResolver)
{
_ftpService = ftpServiceResolver.Resolve("FtpService");
}

public void DoWork()
{
throw new System.NotImplementedException();
}
}

Resolver + Factory Pattern

筆者看了一下,基本上是跟上述Resolver是一樣的,只是將透過IServiceProvider取得實作Service改成使用Activator.CreateInstance的方式取得實作Service,基本上筆者不推薦這種,因為這樣就沒有使用到DI機制自動注入的好處,然而因為這邊負責建立實體,因此屬工廠模式

1
2
3
4
5
6
7
8
9
10
public class FtpServiceResolver : IFtpServiceResolver
{
public IFtpService Resolve(string serviceName)
{
var type = Assembly.GetAssembly(typeof(FtpServiceResolver)).GetType(serviceName);
// 這裡改用Activator.CreateInstance
var instance = Activator.CreateInstance(type);
return instance as IFtpService;
}
}

Resolver + Delegate

筆者最喜歡這個方式,由外部決定取得實作Service方式,而不是寫死於Resolver中,由外部決定做法,因為dotnet core的注入宣告於Startup中,因此將實際做法宣告於Startup中。

註冊Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public delegate IFtpService FtpServiceDelegate(string serviceName);

public void ConfigureServices(IServiceCollection services)
{
// 以上省略
services.AddTransient<FtpService>();
services.AddTransient<SFtpService>();
// 注入時才決定取得實作Service方式
services.AddTransient<FtpServiceDelegate>(provider => serviceName =>
{
var type = Assembly.GetAssembly(typeof(FtpServiceResolver)).GetType(serviceName);
var instance = provider.GetService(type);
return instance as IFtpService;
});
// 以下省略
}

注入Service

改造一下ExchangeService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using static {namespace}.Startup;

public class ExchangeService : IExchangeService
{
private readonly IFtpService _ftpService;

public ExchangeService(FtpServiceDelegate ftpServiceDelegate)
{
_ftpService = ftpServiceDelegate("FtpService");
}

public void DoWork()
{
throw new System.NotImplementedException();
}
}

Implicit Delegate + Lambda Function

第五種其實就是第四種的變種,直接注入Func,於註冊時宣告取得實作Service方式,直接注入相對應的Func取得實作Service。

註冊Service

1
2
3
4
5
6
7
8
9
10
11
12
13
public void ConfigureServices(IServiceCollection services)
{
// 以上省略
services.AddTransient<FtpService>();
services.AddTransient<SFtpService>();
// 注入時才決定取得實作Service方式
services.AddTransient<Func<string, IFtpService>>(provider => serviceName => {
var type = Assembly.GetAssembly(typeof(FtpServiceResolver)).GetType(serviceName);
var instance = provider.GetService(type);
return instance as IFtpService;
});
// 以下省略
}

注入Service

改造一下ExchangeService,不過筆者覺得這種直接注Func醜醜的,筆者本身不會選擇這種方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExchangeService : IExchangeService
{
private readonly IFtpService _ftpService;

public ExchangeService(Func<string, IFtpService> func)
{
_ftpService = func("FtpService");
}

public void DoWork()
{
throw new System.NotImplementedException();
}
}

結論

看完文章才知道有這麼多取得實作Service的方式,對筆者來說,開了眼界,筆者自己本身會選擇第四種作為主要使用方式,也實際應用於專案中,除了第一種Collection與第三種FactoryPattern之外,其他筆者都覺得還不錯,不過就是看各位讀者的使用情境去選擇最適合的一種即可。

參考

https://devkimchi.com/2020/07/01/5-ways-injecting-multiple-instances-of-same-interface-on-aspnet-core/