• [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的变化:

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

    最佳实践

    最佳做法:

    • 设计服务以使用依赖关系注入来获取其依赖关系。
    • 避免有状态的、静态类和成员。将应用设计为改用单一实例服务,可避免创建全局状态。
    • 避免在服务中直接实例化依赖类。 直接实例化将代码耦合到特定实现。
    • 不在应用类中包含过多内容,确保设计规范,并易于测试。
  • 相关阅读:
    第十二周 张文小组学习情况总结
    第五章
    第四章
    第三章
    软工实训--学习回顾2
    软工实训-- 学习回顾1
    构建之法——读书笔记(9)
    构建之法——读书笔记(8)
    构建之法——读书笔记(7)
    第13周张文小组学习情况总结
  • 原文地址:https://www.cnblogs.com/liang24/p/13292726.html
Copyright © 2020-2023  润新知