Autofac dependency injection small knowledge

Posted by lawnninja on Mon, 03 Jan 2022 17:48:56 +0100

Autofac dependency injection small knowledge

Control inversion / dependency injection IOC/DI

Relying on interface rather than implementation is one of the six object-oriented design principles (SOLID). That is, the dependency inversion principle

The life cycle is divided into three types, as follows:

  • Singleton singleton (globally unique instance)
  • Scoped scope (the same instance in the same lifecycle)
  • Transient (each request is a new instance)

instructions

Create ASP Net core 3.0 + and install the Autofac package

dotnet add package Autofac.Extensions.DependencyInjection

Specify Host host in Program UseServiceProviderFactory(new AutofacServiceProviderFactory()).

UseServiceProviderFactory calls the Autofac provider and attaches to the common hosting mechanism.

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();
    }
}

Configuring in 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();
  }
}

Define injection implementation

public class MyApplicationModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
      builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
    }
}
  • Register generic warehousing
builder.RegisterGeneric(typeof(AuditBaseRepository<>)).As(typeof(IAuditBaseRepository<>)).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(AuditBaseRepository<,>)).As(typeof(IAuditBaseRepository<,>)).InstancePerLifetimeScope();
  • Multiple implementations of one interface can be distinguished by using Named, and the parameters can be strings.

Registration service

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

Which service is obtained by 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);
}

Via Appsettings JSON, which service can be determined

  "Service": {
    "IdentityServer4": false
  }
  • Interface based injection

AsImplementedInterfaces Specifies that a type from a scanned assembly is registered as providing all of its implemented interfaces.
Specifies that the type in the scan assembly is registered as an interface that provides all its implementations.

According to the interface ITransientDependency, you can get which classes inherit this interface, and judge whether it is a class, not an abstract class or a generic class.

All classes that inherit the class interface will be automatically injected into the instance in the form of interface. You can use the interface directly.

  • InstancePerDependency (each request is a new instance)
  • InstancePerLifetimeScope scope (the same instance in the same lifecycle)
  • SingleInstance (globally unique instance)
    public class DependencyModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(r => r.FullName.Contains("LinCms.")).ToArray();

            //Each call will re instantiate the object; Each request creates a new object;
            Type transientDependency = typeof(ITransientDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => transientDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && !t.IsGenericType)
                .AsImplementedInterfaces().InstancePerDependency();

            //Objects generated by the same Lifetime are the same instance
            Type scopeDependency = typeof(IScopedDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => scopeDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && !t.IsGenericType)
                .AsImplementedInterfaces().InstancePerLifetimeScope();

            //In singleton mode, the same instantiated object will be used for each call; Use the same object every time;
            Type singletonDependency = typeof(ISingletonDependency);
            builder.RegisterAssemblyTypes(currentAssemblies)
                .Where(t => singletonDependency.GetTypeInfo().IsAssignableFrom(t) && t.IsClass && !t.IsAbstract &&!t.IsGenericType)
                .AsImplementedInterfaces().SingleInstance();

        }
    }

If you don't write inheritance, how to batch inject.
1. The class name has rules
2. Based on special labels
3. Inherit interface.

  • Class name has rules
    For example, the warehousing suffix is all Repository, where Assembly is the Assembly where the warehousing implementation is located. It will be automatically injected into all warehouses, and the warehouse must have an interface.
    Assembly assemblysRepository = Assembly.Load("LinCms.Infrastructure");
    builder.RegisterAssemblyTypes(assemblysRepository)
            .Where(a => a.Name.EndsWith("Repository"))
            .AsImplementedInterfaces()
            .InstancePerLifetimeScope();
  • After the service is injected, a piece of logic is executed
builder.RegisterType<MigrationStartupTask>().SingleInstance();
builder.RegisterBuildCallback(async (c) => await c.Resolve<MigrationStartupTask>().StartAsync());

Dynamic agent

dotnet add package Autofac.Extras.DynamicProxy
dotnet add package Castle.Core.AsyncInterceptor
  • Service registration

AOP + attribute injection + Service implementation with suffix Service, injection of Scope scope Scope life cycle + interceptor enabling interface.

  • Use EnableInterfaceInterceptors to create an interface proxy that performs interception,
  • Use EnableClassInterceptors() to dynamically override subclasses and intercept virtual methods
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()//Interface injection
    .InstancePerLifetimeScope()//Lifecycle: scope
    .PropertiesAutowired()// Attribute injection
    .InterceptedBy(interceptorServiceTypes.ToArray())//Declaration interceptor
    .EnableInterfaceInterceptors();//Enable interceptors for interfaces.

For these two classes, please refer to the following code

Autofac.Extras.DynamicProxy relies on castle Core, that is, it only supports the interception of synchronization methods.
The interception of asynchronous methods requires the installation package: Castle Core. AsyncInterceptor.

  • Asynchronous methods, including yes / no return values: async task runasync(), asyn task < result > runasync()
  • Synchronization method: void Run(),Result Run()

Synchronous interception

1. Define interceptors

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. Register interceptors.

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

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

Associate the interceptor with the type to intercept

[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
  }
}

link

Topics: .NET DI Autofac