.Net Core — 依賴注入

chaney1992發表於2021-01-03

 在.NET Core 中 依賴注入Dependency-Injection)作為基礎知識,在.Net Core中無處不在;這麼重要的知識接下來就瞭解和在.Net Core中使用。

一、依賴注入

 說到依賴注入(Dependency Injection,以下簡稱DI),就必須說IoC(Inverse of Control);這兩個概念很容易搞混。
 IoC:主要體現了這樣一種設計思想:通過將一組通用流程的控制從應用轉移到框架之中以實現對流程的複用,同時採用“好萊塢原則”是應用程式以被動的方式實現對流程的定製。

 DI:服務的消費者利用一個獨立的容器(Container)來獲取所需的服務物件,容器自身在提供服務物件的過程中會自動完成依賴的解析與注入

  核心功能:服務註冊和服務提供

二、DI在.Net Core中實現

  在.Net Core中主要 Microsoft.Extensions.DependencyInjection 中實現DI相關功能,可以在你的專案中單獨使用它

  核心分為兩個元件:IServiceCollection和 IServiceProvider

  

  IServiceCollection:負責服務的註冊;主要擴充套件方法如下:   

public static class ServiceCollectionServiceExtensions
{
    public static IServiceCollection AddScoped(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddTransient(this IServiceCollection services, Type serviceType, Type implementationType);
    ……  
}

   ServiceCollectionServiceExtensions擴充套件類中包含了這三個方法的多種過載方法,那麼這三個方法分別什麼含義呢?

  IServiceProvider:負責服務的獲取;主要方法:   

public interface IServiceProvider
{       
    //獲取服務
    object GetService(Type serviceType);
}

 在IServiceCollection中我們看到了主要到三個方法這些方法的意義是什麼呢?

 生命週期:

 .NET Core DI 為我們提供的例項生命周其包括三種:

  • Transient: 每一次GetService都會建立一個新的例項
  • Scoped:    在同一個Scope內只初始化一個例項 ,可以理解為( 每一個request級別只建立一個例項,同一個http request會在一個 scope內)
  • Singleton: 整個應用程式生命週期以內只建立一個例項(單例)   

三、Asp.Net Core 中應用

  以上一篇審計日誌原始碼作為示例:

  • 新增日誌倉儲介面和實現:
    //倉儲介面
    public interface IRepository<T>
    {
        T Save(T entity);
    }
    
    //審計日誌倉儲實現
    public class AuditLogRepository : IRepository<AuditInfo>
    {
        AuditLogDBContent _auditLogDB;
        public AuditLogRepository(AuditLogDBContent auditLogDB)
        {
            _auditLogDB = auditLogDB;
        }
    
        public AuditInfo Save(AuditInfo entity)
        {
            var retEntity = _auditLogDB.AuditInfos.Add(entity);
            _auditLogDB.SaveChanges();
            return retEntity.Entity;
        }
    }
  • 實現定義服務介面和實現
    //審計日誌服務介面
    public interface IAuditLogService
    {
        Task SaveAsync(AuditInfo auditInfo);
    }
    
    //審計日誌服務實現
    public class AuditLogService : IAuditLogService
    {
        IRepository<AuditInfo> _repository;
        //依賴注入:IRepository<AuditInfo>
        public AuditLogService(IRepository<AuditInfo> repository)
        {
            _repository = repository;
        }
    
        public async Task SaveAsync(AuditInfo auditInfo)
        {
            _repository.Save(auditInfo);
        }
    }    
  • 在Startup中注入定義的相關服務:
    public void ConfigureServices(IServiceCollection services)
        {
            //審計日誌儲存資料庫
            services.AddDbContext<AuditLogDBContent>(options =>
            {
                string conn = Configuration.GetConnectionString("LogDB");
                options.UseSqlite(conn, options =>
                {
                    options.MigrationsAssembly("AuditLogDemo");
                });
            });
    
            //依賴注入:審計日誌倉儲和審計日誌服務
            services.AddScoped<IRepository<AuditInfo>, AuditLogRepository>();
            services.AddScoped<IAuditLogService, AuditLogService>();
    
    
            services.AddControllers(options =>
            {
                options.Filters.Add(typeof(AuditLogActionFilter));
            });
        }

    可以看出當前注入服務不多,相對簡單還好,但專案中倉儲和服務數量肯定比示例數量多,不可能每個都像這樣手工注入。

  那麼有什麼好的解決辦法呢?接著往下看

四、批量注入實現方式

  • 自己實現批量注入 
public static class DIHelper
{
    /// <summary>
    /// 注入服務
    /// </summary>
    /// <param name="services"></param>
    /// <param name="interfaceAssembly"></param>
    /// <param name="implementAssembly"></param>
    public static void AddScoped(this IServiceCollection services, Assembly interfaceAssembly, Assembly implementAssembly)
    {
        var interfaces = interfaceAssembly.GetTypes().Where(t => t.IsInterface);
        var implements = implementAssembly.GetTypes();
        foreach (var item in interfaces)
        {
            var type = implements.FirstOrDefault(x => item.IsAssignableFrom(x));
            if (type != null)
            {
                services.AddScoped(item, type);
            }
        }
    }

    /// <summary>
    /// 注入服務
    /// </summary>
    /// <param name="services"></param>
    /// <param name="interfaceAssembly"></param>
    /// <param name="implementAssembly"></param>
    public static void AddSingleton(this IServiceCollection services, Assembly interfaceAssembly, Assembly implementAssembly)
    {
        var interfaces = interfaceAssembly.GetTypes().Where(t => t.IsInterface);
        var implements = implementAssembly.GetTypes();
        foreach (var item in interfaces)
        {
            var type = implements.FirstOrDefault(x => item.IsAssignableFrom(x));
            if (type != null)
            {
                services.AddSingleton(item, type);
            }
        }
    }

    /// <summary>
    /// 注入服務
    /// </summary>
    /// <param name="services"></param>
    /// <param name="interfaceAssembly"></param>
    /// <param name="implementAssembly"></param>
    public static void AddTransient(this IServiceCollection services, Assembly interfaceAssembly, Assembly implementAssembly)
    {
        var interfaces = interfaceAssembly.GetTypes().Where(t => t.IsInterface);
        var implements = implementAssembly.GetTypes();
        foreach (var item in interfaces)
        {
            var type = implements.FirstOrDefault(x => item.IsAssignableFrom(x));
            if (type != null)
            {
                services.AddTransient(item, type);
            }
        }
    }
}

  使用方式:

services.AddScoped(Assembly.Load("xxxx.IService"), Assembly.Load("xxxx.Service"));
services.AddScoped(Assembly.Load("xxxx.IService"), Assembly.Load("xxxx.Service"));
  • Autofac實現批量注入

   新增Nuget包:

   

   1、新增Autofac模型實現:

/// <summary>
/// 註冊Autofac模組
/// </summary>
public class AutofacModuleRegister : Autofac.Module
{
    /// <summary>
    /// 重寫Autofac管道Load方法,在這裡註冊注入
    /// </summary>
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(GetAssemblyByName("AuditLog.EF"))
            .Where(a => a.Name.EndsWith("Repository"))
            .AsImplementedInterfaces();

        builder.RegisterAssemblyTypes(GetAssemblyByName("AuditLog.Application"))
            .Where(a => a.Name.EndsWith("Service"))
            .AsImplementedInterfaces();

        //註冊MVC控制器(註冊所有到控制器,控制器注入,就是需要在控制器的建構函式中接收物件)
        builder.RegisterAssemblyTypes(GetAssemblyByName("AuditLogDemo"))
            .Where(a => a.Name.EndsWith("Controller"))
            .AsImplementedInterfaces();
    }

    /// <summary>
    /// 根據程式集名稱獲取程式集
    /// </summary>
    /// <param name="assemblyName">程式集名稱</param>
    public static Assembly GetAssemblyByName(string assemblyName)
    {
        return Assembly.Load(assemblyName);
    }
}

  2、使用Autofac來實現依賴注入

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            //改用Autofac來實現依賴注入
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

  3、調整Statup檔案:    

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    /// <summary>
    /// autofac容器
    /// </summary>
    public ILifetimeScope AutofacContainer { get; private set; }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //審計日誌儲存
        services.AddDbContext<AuditLogDBContent>(options =>
        {
            string conn = Configuration.GetConnectionString("LogDB");
            options.UseSqlite(conn, options =>
            {
                options.MigrationsAssembly("AuditLogDemo");
            });
        });

        //依賴注入
        //Scoped:一個請求建立一個
        //services.AddScoped<IRepository<AuditInfo>, AuditLogRepository>();
        ////每次建立一個
        //services.AddTransient<IAuditLogService, AuditLogService>();

        services.AddControllers(options =>
        {
            options.Filters.Add(typeof(AuditLogActionFilter));
        });

    }

    /// <summary>
    /// 配置容器:在ConfigureServices後執行
    /// </summary>
    /// <param name="builder"></param>
    public void ConfigureContainer(ContainerBuilder builder)
    {
        // 直接用Autofac註冊我們自定義的 
        builder.RegisterModule(new AutofacModuleRegister());
    }

  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //autofac 新增 可選 this.AutofacContainer = app.ApplicationServices.GetAutofacRoot(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }

  到此Autofac批量實現注入服務完成。

 

原始碼地址:https://github.com/cwsheng/AuditLogDemo

參考:

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection

https://www.cnblogs.com/artech/p/di-4-asp-net-core.html

 

相關文章