zoukankan      html  css  js  c++  java
  • [ASP.NET Core开发实战]基础篇02 依赖注入

    ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法。

    什么是依赖注入

    我们看一下下面的例子:

    public class MyDependency
    {
        public MyDependency()
        {
        }
    
        public Task WriteMessage(string message)
        {
            Console.WriteLine(
                $"MyDependency.WriteMessage called. Message: {message}");
    
            return Task.FromResult(0);
        }
    }
    
    public class IndexModel : PageModel
    {
        MyDependency _dependency = new MyDependency();
    
        public async Task OnGetAsync()
        {
            await _dependency.WriteMessage(
                "IndexModel.OnGetAsync created this message.");
        }
    }
    

    IndexModel类直接在内部创建了一个MyDependency实例,直接依赖于MyDependency。在开发时避免使用这种方式,原因如下:

    • 要替换不同实现的MyDependency时,必须修改类。
    • 如果MyDependency也有依赖,必须由类对其进行配置。
    • 很难进行单元测试。

    可以通过以下方式解决这些问题:

    • 使用接口或基类。
    • 注册服务窗口中的依赖关注。
    • 通过构造函数注入。

    将上面的例子改造为依赖注入方式:

    public interface IMyDependency
    {
        Task WriteMessage(string message);
    }
    
    public class MyDependency : IMyDependency
    {
        private readonly ILogger<MyDependency> _logger;
    
        public MyDependency(ILogger<MyDependency> logger)
        {
            _logger = logger;
        }
    
        public Task WriteMessage(string message)
        {
            _logger.LogInformation(
                "MyDependency.WriteMessage called. Message: {MESSAGE}", 
                message);
    
            return Task.FromResult(0);
        }
    }
    
    public class IndexModel : PageModel
    {
        MyDependency _dependency;
        public IndexModel(MyDependency dependency){
            _dependency = dependency;
        }
    
        public async Task OnGetAsync()
        {
            await _dependency.WriteMessage(
                "IndexModel.OnGetAsync created this message.");
        }
    }
    
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
        
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
        }
    }
    

    在Startup里注册服务

    服务可以在Startup.Configure注册:

    public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
    {
        ...
    }
    

    使用扩展方法注册服务

    当使用服务集合的扩展方法来注册服务,约定使用Add{SERVICE_NAME}扩展方法来注册该服务所需的所有服务。

    以下例子是使用扩展方法AddDbContextAddIdentity向容器添加服务。

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
    
        ...
    }
    

    服务生命周期

    服务的生命周期有三种:Transient、Scoped、Singleton。

    Transient

    Transient,意为暂时的,是每次服务容器进行请求时创建的,即服务每次实例化时创建一次。这种生存期适合轻量级、无状态的服务。

    Scoped

    Scoped,意为范围内的,每个客户端请求(连接)时会创建一次。以Web为例,即一次Http请求内创建一次且请求内有效

    Singleton

    Singleton,意为单例的,是在第一次请求时创建的。后面每次请求时使用的是同一个实例。在应用关闭,释放ServiceProvider时,会释放。

    验证服务生命周期

    下面示例是演示服务生命周期的差异。

    public interface IOperation
    {
        Guid OperationId { get; }
    }
    
    public interface IOperationTransient : IOperation
    {
    }
    
    public interface IOperationScoped : IOperation
    {
    }
    
    public interface IOperationSingleton : IOperation
    {
    }
    
    public interface IOperationSingletonInstance : IOperation
    {
    }
    
    public class Operation : IOperationTransient, 
        IOperationScoped, 
        IOperationSingleton, 
        IOperationSingletonInstance
    {
        public Operation() : this(Guid.NewGuid())
        {
        }
    
        public Operation(Guid id)
        {
            OperationId = id;
        }
    
        public Guid OperationId { get; private set; }
    }
    
    public class OperationService
    {
        public OperationService(
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }
    }
    
    
    

    在Startup.ConfigureServices中,指定各服务的生命周期。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddScoped<IMyDependency, MyDependency>();
        services.AddTransient<IOperationTransient, Operation>();
        services.AddScoped<IOperationScoped, Operation>();
        services.AddSingleton<IOperationSingleton, Operation>();
        services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
    
        // OperationService depends on each of the other Operation types.
        services.AddTransient<OperationService, OperationService>();
    }
    

    示例IndexModel:

    public class IndexModel : PageModel
    {
        private readonly IMyDependency _myDependency;
    
        public IndexModel(
            IMyDependency myDependency, 
            OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _myDependency = myDependency;
            OperationService = operationService;
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = singletonInstanceOperation;
        }
    
        public OperationService OperationService { get; }
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }
    
        public async Task OnGetAsync()
        {
            await _myDependency.WriteMessage(
                "IndexModel.OnGetAsync created this message.");
        }
    }
    

    以下是两次访问IndexModel的结果:

    第一个请求:
    控制器操作:
    暂时性:d233e165-f417-469b-a866-1cf1935d2518
    作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
    单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
    实例:00000000-0000-0000-0000-000000000000
    OperationService 操作:
    暂时性:c6b049eb-1318-4e31-90f1-eb2dd849ff64
    作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
    单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
    实例:00000000-0000-0000-0000-000000000000
    
    第二个请求:
    控制器操作:
    暂时性:b63bd538-0a37-4ff1-90ba-081c5138dda0
    作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
    单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
    实例:00000000-0000-0000-0000-000000000000
    OperationService 操作:
    暂时性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf
    作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
    单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
    实例:00000000-0000-0000-0000-000000000000
    

    从上面的结果,观察OperationId的变化:

    • 暂时性,值始终不同。
    • 作用域,同一请求内相同,不同请求不同。
    • 单例,不管是同一请求还是不同请求,都一样。

    最佳实践

    最佳做法:

    • 设计服务以使用依赖关系注入来获取其依赖关系。
    • 避免有状态的、静态类和成员。将应用设计为改用单一实例服务,可避免创建全局状态。
    • 避免在服务中直接实例化依赖类。 直接实例化将代码耦合到特定实现。
    • 不在应用类中包含过多内容,确保设计规范,并易于测试。
  • 相关阅读:
    题解 CF171G 【Mysterious numbers
    题解 P1157 【组合的输出】
    题解 P3955 【图书管理员】
    题解 P2036 【Perket】
    题解 CF837A 【Text Volume】
    题解 CF791A 【Bear and Big Brother】
    题解 CF747A 【Display Size】
    题解 P1332 【血色先锋队】
    题解 P2660 【zzc 种田】
    题解 P4470 【[BJWC2018]售票】
  • 原文地址:https://www.cnblogs.com/liang24/p/13292726.html
Copyright © 2011-2022 走看看