MiniAPI（六）：Dependency Injection

OOPには依存性逆転の原則(The Dependency Inversion Principle)があります。これは、上層モジュールは下層モジュールに依存すべきではなく、両方とも抽象に依存すべきであるという意味です。言い換えれば、抽象に依存し、具体的な実装には依存しないようにします。
次の図のように、注文完了後に配送モジュールを呼び出す必要がある場合、具体的な配送モジュールではなく配送モジュールのインターフェースに依存します。
![alt 図](https://mmbiz.qpic.cn/mmbiz_jpg/4CAA63nvHRC7lzcaPn5JnCwP3ic04wr3uNdSQsFTDZ79tpkzepJ038atSBzCk30K0Yag7v91zsSDwZRib5iaeFlYQ/640?wx_fmt=jpeg)
依存関係注入（Dependency Injection、略称DI）は、依存関係の制御の反転を実現するためのソフトウェア設計パターンです。asp.net coreフレームワークはこの技術を生まれながらに内蔵しています。注入する箇所はコンテナ（dcokerとは無関係）あるいはサービスコンテナと呼ばれます。
注入には次の三つの形態があります：
Transient
Scoped
Singleton
文字通りの意味でも理解できるように、三つの形態はアクセスの範囲が異なります。Demoを通して見てみましょう。
DemoではTransientService、ScopedService、SingletonServieの三つのサービスとそれぞれのインターフェースを定義し、Service内で時刻を表示するCallメソッドをそれぞれ実装しています。
三つのServiceとそれらの実装
```C#

public interface ITransientService
{
    string Call();
}
public class TransientService : ITransientService
{
    public DateTime Time { get; init; } = DateTime.Now;
    public string Call()
    {
        return $"TransientService {Time.ToString("yyyy-MM-dd HH:mm:ss.fffffff")} test……";
    }
}

public interface IScopedService
{
    string Call();
}
public class ScopedService : IScopedService
{
    public DateTime Time { get; init; } = DateTime.Now;
    public string Call()
    {
        return $"ScopedService {Time.ToString("yyyy-MM-dd HH:mm:ss.fffffff")} test……";      
    }
}

public interface ISingletonService
{
    string Call();
}
public class SingletonService : ISingletonService
{
    public DateTime Time { get; init; } = DateTime.Now;
    public string Call()
    {
        return $"TSingletonService {Time.ToString("yyyy-MM-dd HH:mm:ss.fffffff")} test……";       
    }
}
```
これら三つのサービスとそれらのインターフェースをサービスコンテナに注入します。デモをより分かりやすくするために、app.Useメソッドを追加し、サービスのURLを呼び出し判断し、Callメソッドを呼び出し、最後にMapメソッドを呼び出します。つまり、各呼び出しでCallメソッドが二度呼び出されます。
```C#

var builder = WebApplication.CreateBuilder();

builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();

var app = builder.Build();

app.Use(async (context, next) =>
{
    if (context.Request.Path.HasValue)
    {
        switch (context.Request.Path.Value)
        {
            case string s when s.Contains("transient"):
                var transientService = context.RequestServices.GetService<ITransientService>();
                Console.WriteLine($"--------------{transientService?.Call()}");
                break;
            case string s when s.Contains("scoped"):
                var scopedService = context.RequestServices.GetService<IScopedService>();
                Console.WriteLine($"--------------{scopedService?.Call()}");
                break;
            case string s when s.Contains("singleton"):
                var singletonService = context.RequestServices.GetService<ISingletonService>();
                Console.WriteLine($"--------------{singletonService?.Call()}");
                break;
        }
    }
    await next.Invoke();
});

app.MapGet("/transient", (ITransientService transientService) => transientService.Call());
app.MapGet("/scoped", (IScopedService scopedService) => scopedService.Call());
app.MapGet("/singleton", (ISingletonService singletonService) => singletonService.Call());

app.Run();
```
結果を見てみましょう：
ITransientService結果：二度の呼び出しとも異なる
![alt 図](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRC7lzcaPn5JnCwP3ic04wr3u3dLEtPuicHSDVZJxSPHly2ETZLibCah1dia7Fxqmc1szMRpxIknrKbLSA/640?wx_fmt=png)

IScopedService結果：二度の呼び出しとも同じ（一つのhttp呼び出しの中で同じ）
![alt 図](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRC7lzcaPn5JnCwP3ic04wr3ujNAXCXGb9dagLKx6DaXMHSvyJ77aia6DF42F6NnW4eRspwibjpq4pVBQ/640?wx_fmt=png)
ISingletonService結果：各呼び出しとも同じ
![alt 図](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRC7lzcaPn5JnCwP3ic04wr3u2jKTV0RHROA825g65OaxmkA001ZmCx0uaU5K7wXfb07jmqOgp4lVEw/640?wx_fmt=png)
以下は簡単な説明図です：
![alt 図](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRC7lzcaPn5JnCwP3ic04wr3u56q2RLBkRsHZ2BjwJfpXNKTj00U7gTYg1KkuwvibkzWFmKApOYE4UqA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1)

複数のサブタイプインジェクション
FedExとUPSがIDeliveryを実装
```javascript
class Order{    public decimal Amount { get; set; }}
public interface IDelivery{    void Send();}
public class FedEx : IDelivery{    public void Send()    {        Console.WriteLine("FedEx API");    }}
public class UPS : IDelivery{    public void Send()    {        Console.WriteLine("UPS API");    }}
```
複数回の注入で、IEnumerableを使ってIDeliveryを取得し、タイプを判断して欲しいIDeliveryサブクラスを取得します。
```javascript
var builder = WebApplication.CreateBuilder();
builder.Services.AddScoped<IDelivery, FedEx>();builder.Services.AddScoped<IDelivery, UPS>();
var app = builder.Build();
app.MapPost("/order", (IEnumerable<IDelivery> deliveries, Order order) =>{    //ここには一連のロジックがあります    if (order.Amount > 1000)    {        var fedEx = deliveries.SingleOrDefault(s => s is FedEx);        fedEx?.Send();    }    else    {        var usp = deliveries.SingleOrDefault(s => s is UPS);        usp?.Send();    }});
app.Run();
```
(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484851&idx=1&sn=7b5d86d33c928092e307008debbe9463&chksm=9f005a99a877d38fc4643a58d7adc3f7ed3f3510d3af625ea4615641fdc9e18292d8781a0dfd&token=2050451386&lang=zh_CN#rd