zl程序教程

您现在的位置是:首页 >  大数据

当前栏目

Caliburn.Micro Bootstrapper及IOC容器配置

配置容器 IOC Micro Bootstrapper
2023-06-13 09:12:18 时间

大家好,又见面了,我是你们的朋友全栈君。

如果想深入学习Caliburn.Micro,Bootstrapper和IOC容器配置是重中之重,一定要弄清楚,否则很难理解CM的工作方式。

配置Bootstrapper的意义

如果在Boostrapper中不进行任何配置的话,Bootstrapper会首先把Bootstrapper所在程序集加载到 AssemblySource.Instance中。而我们在Bootstrapper中只在DisplayRootViewFor()中给定了一个主ViewModel的类型,那么CM是如何找到找到ViewModel和View并创建实例的?

CM获得ViewModel实例的方式

CM查找ViewModel的方式只有一个,就是从IOC中提取。默认提取方法是IOC.GetInstance,这个方法是直接用ViewModel的类型创建ViewModel的实例,即Activator.CreateInstance()。默认的IOC.GetInstance方法,多次调用就相当于是多次创建新实例,实际上我们只需要第一次是创建新实例,再次调用,只需要返回已经有的实例就ok了。默认的方式是一个很简单的让CM能正常工作的方式,但不是很好,建议用户还是使用自定义的IOC容器。

并且,默认的方式有如下缺点:

  1. Bootstrapper需要依赖ViewModel所在的程序集,否则IOC无法创建ViewModel实例。
  2. 每次从IOC提取实例都是一个新建的实例,无法找到之前创建的实例。

这些问题都可以通过配置MEF等作为IOC容器后解决。

CM获得View实例的方式

在配置IOC容器之前,我们先看看,CM获取实例的方式。清楚的知道CM在内部是如何使用IOC的,才能更好的配置IOC。

CM在创建ViewModel实例后,会先根据ViewModel类型全名获取View的类型名(根据设定的名称映射规则),然后根据View的类型名查找View类型并创建实例。CM会在3个地方查找View,如下:

  1. ViewAware:仅仅只有ViewModel继承了ViewAware,并且View实例已经被创建之后才能用。如果一个ViewModel继承自ViewAware,那么在创建ViewModel对应的View时,会调用ViewAware的AttachView方法把View关联在ViewModel上,以后就可以通过ViewAware的GetView方法获得关联的View。也就是说从ViewAware只能获取已有的View实例,并不能创建View实例。ViewModel可以通过继承Screen的方式间接继承ViewAware(Screen继承了ViewAware),这样会有很多方便,比如在ViewModel中用GetView获得View进行某些操作。
  2. AssemblySource:用FindTypeByNames方法可以从AssemblySource.Instance中根据类型名称获取类型,然后CM用得到的类型创建View实例。AssemblySource.Instance中的类型都是Bootstrapper的SelectAssemblies方法提供的,在Bootstrapper中可以重载SelectAssemblies方法。
  3. IOC:默认情况下没有配置IOC容器,只是在IOC.GetInstance方法中提供了一个简单的创建实例的方法。

所以如果没有配置IOC容器的话,View所在程序集就必须满足以下之一:

  • 用SelectAssemblies方法加载到AssemblySource中。这样CM就可以从AssemblySource中获取View类型
  • View和Bootstrapper在同一个程序集。这样CM就可以用默认IOC.GetInstance静态方法创建一个View实例。

Bootstrapper配置内容

Bootstrapper中有2个必需用的方法即:

  • Initialize:初始化Bootstrapper所有设置,包括EventAggregator事件、AssemblySource、IOC、Application事件。
  • DisplayRootViewFor: 显示主界面。

Bootstrapper中可以通过重载来配置CM的方法主要有:

SelectAssemblies() :设置加载到AssemblySource中的程序集列表

PrepareApplication():从名字就可以看出是application的事件处理,事实上里边代码是这样的

  Application.Startup += OnStartup;
  Application.DispatcherUnhandledException += OnUnhandledException;
  Application.Exit += OnExit;

Configure(): 用于配置IOC容器

GetInstance(Type service, string key):IOC容器获取实例的方法

GetAllInstances(Type service):IOC容器获取实例的方法

BuildUp(object instance) :IOC容器注入实例的方法

Bootstrapper配置实例

MEF是一个.net的插件框架,也可以作为一个依赖注入容器(IOC)使用。我通常就用MEF作为CM的IOC容器。在MEF中所有export部件都会被作为插件导入到container中,通过container也可以访问每个export对象。我们在把MEF作为IOC容器的时候,通常只需要把类标记为export导入到container就可以了,当然不标记为export的类是无法导入到container的。也就是说我们把MEF作为IOC容器的时候,主要使用export部件相关的功能。不了解MEF的话,请了解一下MEF再看以下内容会比较容易理解。

AssemblySource配置

AssemblySource在CM中只有在查找View的时候会用到,(当然ViewModel-First的时候查找ViewModel也会用到)。所以如果你把View和ViewModel都注入到IOC容器中,应该是可以不需要AssemblySource的。事实上我们在用MEF作为IOC容器时一般只把ViewModel导入容器,View不作处理的。所以,一定要记得把View所在的程序集加入到AssemblySource。

代码示例如下:

protected override IEnumerable<Assembly> SelectAssemblies()
{ 
   
    return new[] { 
   
        Assembly.GetExecutingAssembly()
    };//返回目前正在執行程序集,当View在目前正在执行的程序集中时,可以这样写。
}

我个人习惯吧View单独放在一个文件夹的不同程序集中,所以我通常是这样做的:

        protected override IEnumerable<Assembly> SelectAssemblies()
        { 
   
            List<Assembly> lst = new List<Assembly>();
            lst.AddRange(base.SelectAssemblies());
            lst.AddRange(
                Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + @"\views")
                    .Where(file => file.EndsWith("dll", true, CultureInfo.CurrentCulture) || file.EndsWith("exe", true, CultureInfo.CurrentCulture))
                    .Select(Assembly.LoadFrom));
            //lst.AddRange(from file in Directory.GetFiles(Environment.CurrentDirectory + @"\views") where file.EndsWith("dll") || file.EndsWith("exe") select Assembly.LoadFrom(file));
            return lst;
        }

虽然并不需要把ViewModel部分也加载到AssemblySource中,但是建议还是放进去比较好,这样在使用的时候会比较方便,并且IOC容器也可以直接用AssemblySource里的内容填充。

如果动态加载了模块,也建议能够同时在AssemblySource和IOC中注册模块。

IOC配置

在这里我们用MEF作为IOC容器,所以需要先引用两个命名空间:

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

如果所有需要注入IOC的类都已经导入到了AssemblySource,就可以这样设置IOC。注:ViewModel-First时,ViewModel是必需要注入到IOC的。

    protected override void Configure()
    { 
   
        container = CompositionContainer(
            new AggregateCatalog(   AssemblySource.Instance.Select(x => new AssemblyCatalog(x))  )
            );

        var batch = new CompositionBatch();
        batch.AddExportedValue<IWindowManager>(new WindowManager());//新建一个窗口管理器添加到IOC中
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());//如果要使用弱事件就需要添加这个了
        batch.AddExportedValue(container);//只是个习惯,这样就可以在任何地方通过IOC使用container了
        container.Compose(batch);
    }

从IOC容器中获取对象的方法代码如下:

    protected override object GetInstance(Type serviceType, string key)
    { 
   
        string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
        var exports = container.GetExportedValues<object>(contract);

        var exportList = exports.ToList();//避免直接用exports时 调用2次IEnumerable操作
        if (exportList.Any())
            return exportList.First();
        throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
    }

从容器获取所有同类型对象和注入容器的方法可以用如下代码:

    protected override IEnumerable<object> GetAllInstances(Type serviceType)
    { 
   
        return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
    }

    protected override void BuildUp(object instance)
    { 
   
        container.SatisfyImportsOnce(instance);
    }

BuildUp用到的不多,可以不设置。

Application相关配置

PrepareApplication()中,我们可以添加新的事件处理程序,比如Activated等。OnStartup可以添加程序启动前需要处理的事情,比如命令行参数处理等,当然还有DisplayRootViewFor方法。OnUnhandledException中添加程序中未处理的异常的处理方法。OnExit处理程序退出事件。

另外,在其他平台(非PC)及winform中应用略有不同,请查看官方帮助。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/162197.html原文链接:https://javaforall.cn