昨天我们介绍了怎麽在 .NET Web API 的专案里实现依赖注入,但是昨天我们注入的是一个指定的 Service。这样的情况下我们其实无法感受到 DI 的好处,用起来的感觉只是把 new 出 Service 实体的程序码集中到建构式,如果哪天这个 Service 有变动,依赖这个 Service 的地方还是都要改。比较好的作法是让我们的 Controller 依赖介面(interface)而不是依赖固定的 Service 类别,今天我们要来修改我们的程序,发挥 DI 的好处!
在开始之前,我们先介绍一下介面,以下节录微软官网对介面的解释
An interface contains definitions for a group of related functionalities that a non-abstract class or a struct must implement.
介面包含了一组相关的功能定义,非抽象的类别或结构必须实作这些定义。
(官网的繁体中文翻译真的很烂,所以我自己翻了一下 = =)
不过我想除非真的实际定过、实作过介面,不然还是无法明白这句话在说什麽。这里,笔者提供一个自己的理解给大家参考:「介面定义了一组相关的方法(method),一个类别如果实作这个介面,那麽这个类别就必须依照介面的定义完成所有的方法」
举个例子来说,我们定义了一个叫「猫咪」的介面,包含吃罐头()、睡觉()、呼噜噜()三个方法,然後我们让一个「橘猫」类别实作这个介面,那麽「橘猫」就一定至少要有吃罐头()、睡觉()、呼噜噜()三个公开的(public)方法,而且参数、回传的资料型态一定要与介面的定义相同
public interface ICat // 介面的命名惯例会在最开头加大写 I
{
void Eat(); // 吃罐头
void Hoolulu(); // 呼噜噜
int Sleep(int hour); // 睡觉
}
public class OrangeCat : ICat
{
public void Eat()
{
Console.WriteLine("橘猫开始吃罐头");
}
public void Hoolulu()
{
Console.WriteLine("橘猫开始呼噜噜");
}
public int Sleep(int hour)
{
Console.WriteLine("橘猫睡了" + hour + "小时");
return hour;
}
}
public class ScottishFold : ICat
{
public void Eat()
{
Console.WriteLine("摺耳猫开始吃罐头");
}
public void Hoolulu()
{
Console.WriteLine("摺耳猫开始呼噜噜");
}
public int Sleep(int hour)
{
Console.WriteLine("摺耳猫睡了" + hour + "小时");
return hour;
}
}
让我们的类别实作介面有几个直接的好处
var rand = new Random();
var cats = new List<ICat>();
for (int i = 0; i < 10; i++)
{
if (rand.Next() % 2 == 0)
{
cats.Add(new OrangeCat());
}
else
{
cats.Add(new ScottishFold());
}
}
foreach (ICat cat in cats)
{
cat.Hoolulu();
cat.Eat();
cat.Sleep(1);
}
public interface IUserCRUD
{
public List<User> GetAllUsers();
public User GetUserById(int id);
public void CreateUser(User model);
public void UpdateUser(int id, User model);
public void DeleteUser(int id);
}
然後,让我们的 Service 实作这个介面
public class UserService : IUserCRUD
{
// 内容不变
}
services.AddScoped<IUserCRUD, UserService>();
public class UserController : ControllerBase
{
private readonly IUserCRUD _user;
public UserController(IUserCRUD user)
{
_user = user;
}
// 其他的程序不变
}
这样一来,我们就成功的让 Controller 依赖介面而不是明确指定的类别了!执行程序可以发现,API 用起来完全一样。
现在,假设我们新增一个实作 IUserCRUD 介面的 Service,这个 Service 从本地端的档案读写 User 资料
public class UserServiceWithFile : IUserCRUD
{
private readonly string _fileName = "D:/TestUsers.csv";
private readonly List<User> _users;
private List<User> ReadUsersFromFile(string fileName)
{
if (!File.Exists(_fileName))
{
var file = File.Create(_fileName);
file.Close();
}
var users = new List<User>();
using (var reader = new StreamReader(fileName))
{
while (!reader.EndOfStream)
{
var values = reader.ReadLine().Split(",");
users.Add(new User()
{
UserId = Convert.ToInt32(values[0]),
UserName = values[1],
Email = values[2]
});
}
}
return users;
}
private void SaveUsersToFile(string fileName)
{
using (var writer = new StreamWriter(fileName))
{
foreach (var user in _users)
{
writer.WriteLine($"{user.UserId},{user.UserName},{user.Email}");
}
}
}
public UserServiceWithFile()
{
_users = ReadUsersFromFile(_fileName);
}
public List<User> GetAllUsers()
{
return _users;
}
public User GetUserById(int id)
{
return _users.FirstOrDefault(x => x.UserId == id);
}
public void CreateUser(User model)
{
if (_users.Count == 0)
{
model.UserId = 1;
}
else
{
model.UserId = _users.Max(x => x.UserId) + 1;
}
_users.Add(model);
SaveUsersToFile(_fileName);
}
public void UpdateUser(int id, User model)
{
var existingUser = _users.FirstOrDefault(x => x.UserId == id);
if (existingUser != null)
{
existingUser.UserName = model.UserName;
existingUser.Email = model.Email;
}
SaveUsersToFile(_fileName);
}
public void DeleteUser(int id)
{
var existingUser = _users.FirstOrDefault(x => x.UserId == id);
if (existingUser != null)
{
_users.Remove(existingUser);
}
SaveUsersToFile(_fileName);
}
}
接着,把注册 Service 的地方改成
services.AddScoped<IUserCRUD, UserServiceWithFile>();
执行程序一样会觉得跑起来一模一样!而且只要在注册 Service 的地方稍作修改,我们就能随时替换真正使用的 Service,依赖这个介面的其他程序完全不用改!我们在後面的文章会使用 MySQL 来管理资料,到时候只要再新增一个 UserServiceWithMySQL 实作 IUserCRUD 介面,然後注册的地方换一下,就能无痛把用档案管理使用者的 Service 替换掉!没骗你吧!学 DI 的 CP 值真的很高!
明天我们将继续使用 DI 的方法,把程序需要的组态设定注入给 Controller。
<<: .Net Core Web Api_笔记08_HTTP资源操作模式PATCH
>>: D7(9/7)-91App(6741) 帮商家做电商的电商专家
概述: 本教学提供了两种非常简单的Outlook修复方法,解决了Outlook PST文件找不到导致...
The Visitor design pattern represents an operation...
1.前言 看完上一篇的利用Esp32s的AP mode控制LED灯了吧,是不是觉得很神奇阿,那本篇会...
说明 继上篇建立 App Registration 後,本篇将继续介绍使用MSAL透过Activat...
函式建构式建立原型 前面几篇有提到,可以使用函示建构式、或是 ES 6 来建立原型,今天就来介绍使用...