WPF依赖注入方式(无第三方依赖库)
.NET依赖注入概述
- .NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。
- .NET 中的依赖关系注入是框架的内置部分,与配置、日志记录和选项模式一样。
- 使用接口或基类将依赖关系实现抽象化。
- 在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器。
- 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。
- 最白话的说明:使用依赖注入方式可以免于手动重复构造实体,即使构造器发生了变化
一、ASP.NET Core项目的依赖注入
在ASP.NET Core Web项目中,是默认实现了控制器的Ilogger注入的,但其他方式并没有,还需要自己手动注入,Transient、Scoped或Singleton,若是每个都注册,那岂不是很麻烦?对此,需要一个思维变换,一种解决方案出现,引用编译后dll文件,读取其中的所有类,将需要关联的(接口标记)注入都给注入了。代码如下,直接扩展IServiceCollection接口,非原创,一个旧项目找到的,几经转手,暂时不确定原作者是谁,我也由衷感谢这位同学的帮助
/// <summary>
/// 自动注入拥有ITransientDependency,IScopeDependency或ISingletonDependency的类
/// </summary>
/// <param name="services">服务集合</param>
/// <returns></returns>
public static IServiceCollection AddFxServices(this IServiceCollection services)
{
Dictionary<Type, ServiceLifetime> lifeTimeMap = new Dictionary<Type, ServiceLifetime>
{
{ typeof(ITransientDependency), ServiceLifetime.Transient},
{ typeof(IScopedDependency),ServiceLifetime.Scoped},
{ typeof(ISingletonDependency),ServiceLifetime.Singleton}
};
GlobalAssemblies.AllTypes.ForEach(aType =>
{
lifeTimeMap.ToList().ForEach(aMap =>
{
var theDependency = aMap.Key;
if (theDependency.IsAssignableFrom(aType) && theDependency != aType && !aType.IsAbstract && aType.IsClass)
{
//注入实现
services.Add(new ServiceDescriptor(aType, aType, aMap.Value));
var interfaces = GlobalAssemblies.AllTypes.Where(x => x.IsAssignableFrom(aType) && x.IsInterface && x != theDependency).ToList();
//有接口则注入接口
if (interfaces.Count > 0)
{
interfaces.ForEach(aInterface =>
{
//注入AOP
services.Add(new ServiceDescriptor(aInterface, serviceProvider =>
{
CastleInterceptor castleInterceptor = new CastleInterceptor(serviceProvider);
return _generator.CreateInterfaceProxyWithTarget(aInterface, serviceProvider.GetService(aType), castleInterceptor);
}, aMap.Value));
});
}
//无接口则注入自己
else
{
services.Add(new ServiceDescriptor(aType, aType, aMap.Value));
}
}
});
});
return services;
}
其中,GlobalAssemblies的数据格式为如下:
public static class GlobalAssembliesClient
{
/// <summary>
/// 解决方案所有程序集
/// </summary>
public static readonly Assembly[] AllAssemblies = new Assembly[]
{
Assembly.Load("FieldTools.Util"),
Assembly.Load("FieldTools.Client.Entity"),
Assembly.Load("FieldTools.Client.Business"),
Assembly.Load("FieldTools.Client"),
Assembly.Load("FieldTools.Client.MiniWebApi"),
};
/// <summary>
/// 解决方案所有自定义类
/// </summary>
public static readonly Type[] AllTypes = AllAssemblies.SelectMany(x => x.GetTypes()).ToArray();
}
使用方式
Program.cs/Startup.cs
//.NET6中的使用方式,该版本中,不使用IHost作为默认应用构建方式了,而是使用Microsoft.AspNetCore.Builder.WebApplication
//继承于IHost,扩展了不少,使用起来也更方便,旧版本大同小异
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFxServicesMini();
TestController.cs
//.NET6中的使用方式,该版本中,不使用IHost作为默认应用构建方式了,而是使用Microsoft.AspNetCore.Builder.WebApplication
//继承于IHost,扩展了不少,使用起来也更方便,旧版本类似
[ApiController]
[Route("/[controller]/[action]")]
public class TestController : ControllerBase
{
private readonly ILogger<TestController> _logger;
private readonly ITestBus _testBus;
public TestController(ILogger<TestController> logger, ITestBus testBus)
{
_logger = logger;
_testBus = testBus;
}
[HttpGet]
public string GetTest()
{
_logger.LogWarning("api中调用了get");
return "get调用成功";
}
[HttpPost]
public string PostTest()
{
_logger.LogWarning("api中调用了post");
return "post调用成功";
}
[HttpPost]
public string PostTest2()
{
return _testBus.Func1();
}
}
ITestBus.cs
public interface ITestBus
{
string Func1();
}
TestBus.cs
public class TestBus : ITestBus, ITransientDependency
{
public TestBus()
{
}
public string Func1()
{
return "调用了func1";
}
}
ITransientDependency.cs
/// <summary>
/// 注入标记,生命周期为Transient,仅作为标记,无需做任何实现
/// </summary>
public interface ITransientDependency
{
}
二、WPF中的注入
既然ASP.NET Core都能很容易实现注入,那在WPF中呢?同样很容易,关键就在于IServiceCollection
打开App.xaml文件,删除启动StartupUri项,在App.xaml.cs文件中,覆写OnStartup方法,进行IServiceCollection的配置。批量加入对对需要加入依赖注入的实体/接口进行接口标记即可
App.xaml.cs(手动构造窗口启动)
/// <summary>
/// ServiceProvider,用于某些情况下手动获取实体使用,比如窗口管理器
/// </summary>
public static ServiceProvider serviceProvider;
protected override void OnStartup(StartupEventArgs e)
{
var services = new ServiceCollection();
//一个一个的添加方式
//并非只能添加接口实现类,普通实体也能添加
//services.AddSingleton<LoginViewModel>();
//services.AddTransient<TTIntface, TTBus>();
//services.AddTransient<IDialogCoordinator, DialogCoordinator>();
//直接添加需要的所有
services.AddFxServicesMini();
serviceProvider = services.BuildServiceProvider();
//不通过构造器构造,而是通过BuildServiceProvider获取
LoginWindow login = serviceProvider.GetRequiredService<LoginWindow>();
login.Show();
}
LoginWindow.xaml.cs(使用ISingletonDependency对窗口进行注入标记,以实现LoginViewModel实体的自动注入)
/// <summary>
/// Login.xaml 的交互逻辑
/// </summary>
public partial class LoginWindow : MetroWindow, ISingletonDependency
{
/// <summary>
/// 构造
/// </summary>
public LoginWindow(LoginViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
WindowManager.Register<ToolsBox>("ToolsBox");
}
}
LoginViewModel.cs(其中TTIntface与TTBus类仅是为了测试才写在这个文件,实际开发中务必请分离解耦)
//测试接口
public interface TTIntface
{
void CC();
string GG();
}
//测试接口实现
public class TTBus : TTIntface, ITransientDependency
{
public void CC()
{
MessageBox.Show("调用CC");
}
public string GG()
{
throw new NotImplementedException();
}
}
//LoginViewModel
public class LoginViewModel : NotificationObject, ISingletonDependency
{
private TTIntface _tt;
/// <summary>
/// 初始构造
/// </summary>
public LoginViewModel(TTIntface tt)
{
obj.UserName = "Admin";
obj.Password = "123456";
GlobalParm.LoginType = 2;
_tt = tt;
}
private static string LocUserName = "Admin";
private static string LocPassword = "123456";
#region 实体
/// <summary>
/// Model对象
/// </summary>
private LoginModel obj = new LoginModel();
/// <summary>
/// 用户名
/// </summary>
public string? UserName
{
get
{
return obj.UserName;
}
set
{
obj.UserName = value;
this.RaisePropertyChanged(nameof(UserName));
}
}
/// <summary>
/// 密码
/// </summary>
public string? Password
{
get
{
return obj.Password;
}
set
{
obj.Password = value;
this.RaisePropertyChanged(nameof(Password));
}
}
/// <summary>
/// 登录类型
/// </summary>
public int LoginType
{
get
{
return GlobalParm.LoginType;
}
set
{
GlobalParm.LoginType = value;
this.RaisePropertyChanged(nameof(LoginType));
}
}
#endregion
#region 事件
private BaseCommand loginClick;
/// <summary>
/// 登录事件
/// </summary>
public BaseCommand LoginClick
{
get
{
if (loginClick == null)
{
loginClick = new BaseCommand(new Action<object>(o =>
{
//执行登录逻辑
//todo
}));
}
return loginClick;
}
}
private bool toClose = false;
/// <summary>
/// 是否要关闭窗口
/// </summary>
public bool ToClose
{
get
{
return toClose;
}
set
{
toClose = value;
if (toClose)
{
this.RaisePropertyChanged(nameof(ToClose));
}
}
}
#endregion
#region 方法
//todo
#endregion
}
以上,WPF中的依赖注入完成,效果还不错,构造起来方便很多,但也不是没有缺点的,就是在项目启动后的短暂时间,内存占用会猛涨,当然涨幅也不大,最多一两百MB,上去后就会下来
三、结语
不同于Java,C#出生得晚,生态主要是由微软公司(文档巨tm多和详细)以及众多开源开发者的构成,我,也想做这万千开源中的一份子,因为,.NET确实好用,C#也让程序员更省心,我想让更多人使用