Autofac 依賴注入小知識

Autofac 依賴注入小知識

控制反轉/依賴注入 IOC/DI

依賴介面而不依賴於實現,是面向對象的六大設計原則(SOLID)之一。即依賴倒置原則(Dependence Inversion Principle)

生命周期分為三種,具體如下

  • Singleton 單例(全局唯一實例)
  • Scoped 範圍 (在同一個生命周期內是同一個實例)
  • Transient 瞬時(每次請求都是一個新的實例)

使用說明

創建ASP.NET Core 3.0+的項目,並安裝Autofac

dotnet add package Autofac.Extensions.DependencyInjection

在Program 中Host主機指定 .UseServiceProviderFactory(new AutofacServiceProviderFactory()).

UseServiceProviderFactory調用Autofac提供程式,附加到通用宿主機制。

public class Program
{
    public static void Main(string[] args)
    {
        var host = Host.CreateDefaultBuilder(args)
+       .UseServiceProviderFactory(new AutofacServiceProviderFactory())
        .ConfigureWebHostDefaults(webHostBuilder => {
            webHostBuilder
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>();
        })
        .Build();
    
        host.Run();
    }
}

在StartUp中配置

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    this.Configuration = configuration;
  }

  public IConfiguration Configuration { get; private set; }

+  public ILifetimeScope AutofacContainer { get; private set; }

  public void ConfigureServices(IServiceCollection services)
  {
    services.AddOptions();
  }

  // ConfigureContainer is where you can register things directly
  // with Autofac. This runs after ConfigureServices so the things
  // here will override registrations made in ConfigureServices.
  // Don't build the container; that gets done for you by the factory.
  public void ConfigureContainer(ContainerBuilder builder)
  {
    // Register your own things directly with Autofac here. Don't
    // call builder.Populate(), that happens in AutofacServiceProviderFactory
    // for you.
+    builder.RegisterModule(new MyApplicationModule());
  }

  public void Configure(
    IApplicationBuilder app,
    ILoggerFactory loggerFactory)
  {
+   this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();

    loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
    app.UseMvc();
  }
}

定義注入實現

public class MyApplicationModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
      builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
    }
}
  • 註冊泛型倉儲
builder.RegisterGeneric(typeof(AuditBaseRepository<>)).As(typeof(IAuditBaseRepository<>)).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(AuditBaseRepository<,>)).As(typeof(IAuditBaseRepository<,>)).InstancePerLifetimeScope();
  • 一個介面多個實現,使用Named,區分、參數為字元串即可。

註冊服務

builder.RegisterType<IdentityServer4Service>().Named<ITokenService>(typeof(IdentityServer4Service).Name).InstancePerLifetimeScope();
builder.RegisterType<JwtTokenService>().Named<ITokenService>(typeof(JwtTokenService).Name).InstancePerLifetimeScope();

根據Name獲取哪個服務

private readonly ITokenService _tokenService;
public AccountController(IComponentContext componentContext, IConfiguration configuration)
{
    bool isIdentityServer4 = configuration.GetSection("Service:IdentityServer4").Value?.ToBoolean() ?? false;
    _tokenService = componentContext.ResolveNamed<ITokenService>(isIdentityServer4 ? typeof(IdentityServer4Service).Name : typeof(JwtTokenService).Name);
}

可通過appsettings.json中配置,可決定是哪個服務

  "Service": {
    "IdentityServer4": false
  }
  • 基於介面的注入

AsImplementedInterfaces Specifies that a type from a scanned assembly is registered as providing all of its implemented interfaces.
指定將掃描程式集中的類型註冊為提供其所有實現的介面。

根據介面ITransientDependency可以得到有哪些類繼承了此介面,並判斷是類,不是抽象類,不是泛型。

所有繼承類介面的類,將以介面的方式自動注入實例。可直接使用介面即可。

  • InstancePerDependency 瞬時 (每次請求都是一個新的實例)
  • InstancePerLifetimeScope 範圍(在同一個生命周期內是同一個實例)
  • SingleInstance 單例(全局唯一實例)
    public class DependencyModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(r => r.FullName.Contains("LinCms.")).ToArray();

            //每次調用,都會重新實例化對象;每次請求都創建一個新的對象;
            Type transientDependency = typeof(ITransientDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => transientDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && !t.IsGenericType)
                .AsImplementedInterfaces().InstancePerDependency();

            //同一個Lifetime生成的對象是同一個實例
            Type scopeDependency = typeof(IScopedDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => scopeDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && !t.IsGenericType)
                .AsImplementedInterfaces().InstancePerLifetimeScope();

            //單例模式,每次調用,都會使用同一個實例化的對象;每次都用同一個對象;
            Type singletonDependency = typeof(ISingletonDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => singletonDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract &&!t.IsGenericType)
                .AsImplementedInterfaces().SingleInstance();

        }
    }

如果不寫繼承,如何批量注入呢。
1.類名有規則
2.基於特殊標籤
3.繼承介面。

  • 類名有規則
    比如倉儲後綴,全是Repository,其中Assembly為倉儲的實現所在程式集。將自動注入所有的倉儲,倉儲必須有介面。
    Assembly assemblysRepository = Assembly.Load("LinCms.Infrastructure");
    builder.RegisterAssemblyTypes(assemblysRepository)
            .Where(a => a.Name.EndsWith("Repository"))
            .AsImplementedInterfaces()
            .InstancePerLifetimeScope();
  • 注入服務後就執行一段邏輯
builder.RegisterType<MigrationStartupTask>().SingleInstance();
builder.RegisterBuildCallback(async (c) => await c.Resolve<MigrationStartupTask>().StartAsync());

動態代理

dotnet add package Autofac.Extras.DynamicProxy
dotnet add package Castle.Core.AsyncInterceptor
  • 服務註冊

AOP+屬性注入+以後綴為Service的服務實現,注入Scope 範圍的生命周期+啟用介面的攔截器。

  • 使用EnableInterfaceInterceptors創建執行攔截的介面代理,
  • 使用EnableClassInterceptors() 動態對子類進行重寫, 執行virtual方法的攔截
builder.RegisterType<UnitOfWorkInterceptor>();
builder.RegisterType<UnitOfWorkAsyncInterceptor>();


List<Type> interceptorServiceTypes = new List<Type>()
{
    typeof(UnitOfWorkInterceptor),
};

Assembly servicesDllFile = Assembly.Load("LinCms.Application");
builder.RegisterAssemblyTypes(servicesDllFile)
    .Where(a => a.Name.EndsWith("Service") && !a.IsAbstract && !a.IsInterface && a.IsPublic)
    .AsImplementedInterfaces()//介面注入
    .InstancePerLifetimeScope()//生命周期:範圍
    .PropertiesAutowired()// 屬性注入
    .InterceptedBy(interceptorServiceTypes.ToArray())//聲明攔截器
    .EnableInterfaceInterceptors();//啟用介面的攔截器。

這二個類,請參考如下程式碼

Autofac.Extras.DynamicProxy依賴Castle.Core,即只支援同步方法的攔截。
非同步方法的攔截需要安裝包:Castle.Core.AsyncInterceptor

  • 非同步方法,分為有/無返回值:async Task RunAsync(),asyn Task<Result> RunAsync()
  • 同步方法:void Run(),Result Run()

同步攔截

1.定義攔截器

public class CallLogger : IInterceptor
{
  TextWriter _output;

  public CallLogger(TextWriter output)
  {
    _output = output;
  }

  public void Intercept(IInvocation invocation)
  {
    _output.Write("Calling method {0} with parameters {1}... ",
      invocation.Method.Name,
      string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));

    invocation.Proceed();

    _output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
  }
}

2.註冊攔截器。

// Named registration
builder.Register(c => new CallLogger(Console.Out))
       .Named<IInterceptor>("log-calls");

// Typed registration
builder.Register(c => new CallLogger(Console.Out));

將攔截器與要攔截的類型 關聯

[Intercept(typeof(CallLogger))]
public class First
{
  public virtual int GetValue()
  {
    // Do some calculation and return a value
  }
}

// This attribute will look for a NAMED
// interceptor registration:
[Intercept("log-calls")]
public class Second
{
  public virtual int GetValue()
  {
    // Do some calculation and return a value
  }
}

鏈接