02020302 .NET Core核心基础组件02-服务定位器、.NET依赖注入示例

02020302 .NET Core核心基础组件02-服务定位器、.NET依赖注入示例

1. 服务定位器(视频Part2-27)

1.1 IServiceProvider的服务定位器方法
T GetService<T>() → 如果获取不到对象,则返回null。
object GetService(Type serviceType)
T GetRequiredService<T>() → 如果获取不到对象,则抛异常。
object GetRequiredService(Type serviceType)
IEnumerable<T> GetServices<T>() → 适用于可能有很多满足条件的服务。
IEnumerable<object> GetService(Type serviceType)
1.2 其它注册方法
  • 服务类型和实现类型不一致的注册。
  • 简单看看其它Add方法。
1.3 GetService()注册方法
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Demo02
{
    public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }

    public class TestServiceImp1 : ITestService, IDisposable
    {
        public string Name { get; set; }

        public void Dispose()
        {
            Console.WriteLine("Dispose...");
        }

        public void SayHi()
        {
            Console.WriteLine($"Hi! I'm {Name}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImp1>(); // @1 
            // services.AddScoped<typeof(ITestService), typeof(TestServiceImp1)>(); // @2
            // services.AddSingleton<ITestService, TestServiceImp1>(); // @3
            // services.AddSingleton<typeof(ITestService), new TestServiceImp1()>; // @4 TestServiceImp1构造函数可以添加参数
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                ITestService ts1 = sp.GetService<ITestService>(); // @5 返回的是实现类的对象
                // TestServiceImp1 ts1 = sp.GetService<TestServiceImp1>(); // @6 返回的是实现类的对象
                ts1.Name = "Qinway";
                ts1.SayHi();
                Console.WriteLine(ts1);
            }

            Console.ReadLine();
        }
    }
}

控制台输出:
Hi! I'm Qinway
Demo02.TestServiceImp1
Dispose...

说明:
1. 在@1处两个泛型:ITestService表示服务类型(接口),实现类型TestServiceImp1(实现类)。此时不管实现类是什么,我只要接口的服务。
2. 随着项目变得复杂,写注册服务的人(ITestService)和写实现服务的人(TestServiceImp1),可能不是同一个人来做的。
3. 在@1处和@2处两种写法等价。
4. 在@4处,直接将实现类new好了,这样适用于对象需要传递某个参数的时候使用。可以在构造函数添加参数,由开发者自己控制创建对象的条件。
5. 如果采用@4处的注册形式,那么不用用@5处的获取形式,而是要采用@6处的获取形式。注册类型是实现类,那么拿的时候也是实现类。
6. 在@5和@6处,如果GetService找不到服务,就返回null。
7. GetService方法还有非泛型的方法重载,一般不用。
1.3 GetRequiredService()注册方法
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Demo02
{
    public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }

    public class TestServiceImp1 : ITestService, IDisposable
    {
        public string Name { get; set; }

        public void Dispose()
        {
            Console.WriteLine("Dispose...");
        }

        public void SayHi()
        {
            Console.WriteLine($"Hi! I'm {Name}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImp1>();
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                TestServiceImp1 ts1 = sp.GetRequiredService<TestServiceImp1>(); // 抛出异常
                ts1.Name = "Qinway";
                ts1.SayHi();
                Console.WriteLine(ts1);
            }

            Console.ReadLine();
        }
    }
}

异常信息:System.InvalidOperationException:“No service for type 'Demo02.TestServiceImp1' has been registered.”

说明:GetRequiredService方法不会返回null,这个方法本身就会抛出异常。

对比:GetService方法如果找不到,返回null;GetRequiredService如果找不到,抛出异常。这里和C#基础知识显式类型转换(如果不能转换抛异常)和as转换(如果不能转换返回null)类似。
1.4 GetServices()注册方法
  • 该方法适用于实现一个接口有多个服务的情况,如果实现一个接口有多个服务,那么将服务都返回。
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;

namespace Demo02
{
    public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }

    public class TestServiceImp1 : ITestService, IDisposable
    {
        public string Name { get; set; }

        public void Dispose()
        {
            Console.WriteLine("Dispose...");
        }

        public void SayHi()
        {
            Console.WriteLine($"Hi! I'm {Name}");
        }
    }

    public class TestServiceImp2 : ITestService
    {
        public string Name { get; set; }

        public void SayHi()
        {
            Console.WriteLine($"你好,我是{Name}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImp1>(); // 注册第一个
            services.AddScoped<ITestService, TestServiceImp2>(); // 注册第二个
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                IEnumerable<ITestService> ts1 = sp.GetServices<ITestService>();
                foreach (ITestService it in ts1)
                {
                    Console.WriteLine(it);
                }
            }

            Console.ReadLine();
        }
    }
}

控制台输出:
Demo02.TestServiceImp1
Demo02.TestServiceImp2
Dispose...
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;

namespace Demo02
{
    public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }

    public class TestServiceImp1 : ITestService, IDisposable
    {
        public string Name { get; set; }

        public void Dispose()
        {
            Console.WriteLine("Dispose...");
        }

        public void SayHi()
        {
            Console.WriteLine($"Hi! I'm {Name}");
        }
    }

    public class TestServiceImp2 : ITestService
    {
        public string Name { get; set; }

        public void SayHi()
        {
            Console.WriteLine($"你好,我是{Name}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImp1>(); // 注册第一个
            services.AddScoped<ITestService, TestServiceImp2>(); // 注册第二个
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                var t1 = sp.GetService<ITestService>(); // 如果注册了多个,用Service方法那么获取最后一个。
                var t2 = sp.GetRequiredService<ITestService>(); // 如果注册了多个,用GetRequiredService方法那么获取的也是最后一个。
                Console.WriteLine(t1);
                Console.WriteLine(t2);
            }

            Console.ReadLine();
        }
    }
}

控制台输出:
Demo02.TestServiceImp2
Demo02.TestServiceImp2

2. .NET依赖注入(视频Part-2-28)

  • 在这节课之前,我们讲的GetService等方法都是服务定位器的用法。从这节课开始,是真正的依赖注入。
2.1 依赖注入魅力渐显
  • 依赖注入是有传染性的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值。
    • 但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数声明的服务类型参数就不会被自动赋值。
  • .NET的DI默认是构造函数注入。
    • 例如:编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类。
    • connstr见备注。
2.2 依赖注入示例
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;

namespace Demo02
{
    class Controller
    {
        private readonly ILog log;
        private readonly IStorage storage;
        public Controller(ILog log, IStorage storage)
        {
            this.log = log;
            this.storage = storage;
        }

        public void Test()
        {
            this.log.Log("开始上传");
            this.storage.Save("ABC", "1.txt");
            this.log.Log("上传完毕");
        }
    }

    interface ILog
    {
        public void Log(string msg);
    }

    class LogImpl : ILog
    {
        public void Log(string msg)
        {
            Console.WriteLine($"日志:{msg}");
        }
    }

    interface IConfig
    {
        public string GetValue(string name);
    }

    class ConfigImpl : IConfig
    {
        public string GetValue(string name)
        {
            return "Hello";
        }
    }

    interface IStorage
    {
        public void Save(string content, string name);
    }

    class StorageImpl : IStorage
    {
        private readonly IConfig config;
        public StorageImpl(IConfig config)
        {
            this.config = config;
        }
        public void Save(string content, string name)
        {
            string server = config.GetValue("server");
            Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<Controller>();
            services.AddScoped<ILog, LogImpl>();
            services.AddScoped<IStorage, StorageImpl>();
            services.AddScoped<IConfig, ConfigImpl>();

            using (var sp = services.BuildServiceProvider())
            {
                var c = sp.GetRequiredService<Controller>();
                c.Test();
            }

            Console.ReadLine();
        }
    }
}

控制台输出:
日志:开始上传
向服务器Hello的文件名为1.txt上传ABC
日志:上传完毕

说明:
1. 这样做的好处就是业务代码不用动,只需要配置新的服务即可。
2. DI可以降低模块之间的耦合。

结尾

书籍:ASP.NET Core技术内幕与项目实战

视频:https://www.bilibili.com/video/BV1pK41137He

著:杨中科

ISBN:978-7-115-58657-5

版次:第1版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-09-09 21:20  qinway  阅读(14)  评论(0)    收藏  举报