WPF依赖注入方式(无第三方依赖库)

.NET依赖注入概述

  1. .NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。
  2. .NET 中的依赖关系注入是框架的内置部分,与配置、日志记录和选项模式一样。
  3. 使用接口或基类将依赖关系实现抽象化。
  4. 在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器。
  5. 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。
  6. 最白话的说明:使用依赖注入方式可以免于手动重复构造实体,即使构造器发生了变化

一、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#也让程序员更省心,我想让更多人使用

posted @ 2022-06-11 23:22  SoloShine  阅读(1029)  评论(0编辑  收藏  举报