如何在Caliburn.Micro中使用导体的依赖注入

时间:2016-04-27 19:58:39

标签: c# dependency-injection caliburn.micro

我有时会使用Caliburn.Micro来创建应用程序。

使用最简单的BootStrapper,我可以像这样使用IoC容器(SimpleContainer):

private SimpleContainer _container = new SimpleContainer();

protected override object GetInstance(Type serviceType, string key) {
    return _container.GetInstance(serviceType, key);
}

protected override IEnumerable<object> GetAllInstances(Type serviceType) {
    return _container.GetAllInstances(serviceType);
}

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

所以在Configure方法中,我可以像这样添加和注册我的ViewModel:

container.PerRequest<MyMainViewModel>();

我的ViewModel构造函数可以在请求时由IoC容器提供注入的参数:

public MyMainViewModel(IWindowManager windowManager)
{
  //do the init
}

当我致电DisplayRootViewFor<MyMainViewModel>()

时,它按预期工作

但是,如果我打算创建更多逻辑并使用{​​{3}}?

,会发生什么

在这些例子中,作者使用一个简单的,无IoC的实现来实现&#34;方便&#34;:

  

为了让这个样本尽可能简单,我甚至都没有使用   带有Bootstrapper的IoC容器。我们来看看吧   首先是ShellViewModel。它继承自Conductor并实现为   如下:

public class ShellViewModel : Conductor<object> {
    public ShellViewModel() {
        ShowPageOne();
    }

    public void ShowPageOne() {
        ActivateItem(new PageOneViewModel());
    }

    public void ShowPageTwo() {
        ActivateItem(new PageTwoViewModel());
    }
}

所以他们实例化 ViewModels,而不是从IoC容器中请求实例

在这种情况下,正确使用依赖注入会是什么?

我有另一个ViewModel,它有一个像这样的构造函数:

public MySecondViewModel(MyParamClass input)
{
  //do the work
}

我应该像这样修改代码:

Configure方法中:

simpleContainer.PerRequest<MyParamClass>(); //How could it be different every time?

在指挥中:

public void ShowPageOne() 
{
   ActivateItem(IoC.Get<MySecondViewModel>());
}

此外,这是允许的还是违反了DI的规则:

protected override object GetInstance(Type serviceType, string key) 
{
  if(serviceType==typeof(MySecondViewModel))
    return new MySecondViewModel(new MyParamClass(2));
  return _container.GetInstance(serviceType, key);
}

我可以看到使用DI,ViewModel应由IoC容器提供,而不是手动创建(更不用说所需的参数 - 它位于容器内)。

那么你能否给出一些暗示如何用导体实现IoC模式?

3 个答案:

答案 0 :(得分:3)

最简单,最直接的方法是遵循The Explicit Dependency Principle

所以假设

public MySecondViewModel(MyParamClass input) {
  //do the work
}

并且它及其依赖项已在容器中注册

simpleContainer.PerRequest<MyParamClass>();
simpleContainer.PerRequest<MySecondViewModel>();

MainViewModel导体可以依赖于一个委托(工厂),该委托可以在需要时用于解决依赖关系。

public class MainViewModel : Conductor<object> {
    //...
    private readonly Func<MySecondViewModel> mySecondViewModelFactory;

    public MyMainViewModel(IWindowManager windowManager, Func<MySecondViewModel> mySecondViewModelFactory) {
        this.mySecondViewModelFactory = mySecondViewModelFactory;
        //...do the init
    }

    public void ShowPageOne() {
        var item = mySecondViewModelFactory(); //invoke factory
        ActivateItem(item);
    }
}

虽然没有正确记录,但是SimpleContainer允许以Func<TDependency>的形式注入工厂代表(Source Code),以便推迟解析/实例化注入的依赖关系。您可以利用该功能仅在实际需要时才解决依赖关系。

答案 1 :(得分:2)

我通常这样做的方法是引入一个Navigator并将其与一个Singleton ShellView(将成为我们的指挥)和IOC container实例耦合。一个简单的导航api可能看起来像

简单实施:

public interface INavigator
{
    void Navigate<T>();
}

public class Navigator : INavigator
{
    private ShellViewModel _shellview;

    public Navigator(ShellViewModel shellview) //where ShellViewModel:IConductor
    {
        _shellview = shellview;
    }
    public void Navigate<T>()
    {
       //you can inject the IOC container or a wrapper for the same from constructor
       //and use that to resolve the vm instead of this
        var screen = IoC.Get<T>(); 

        _shellview.ActivateItem(screen);
    }
}

作为更灵活的选择,您可以改进此模式以引入导航请求的概念,封装有关初始化屏幕和屏幕本身并根据需要激活它的所有详细信息。

一些扩展实施

对于这种模式,设计一个NavigationRequest,例如

public interface INavigationRequest<out T>
{
    T Screen { get; }
    void Go();
}

更新INavigator以返回此请求。

public interface INavigator
{
    INavigationRequest<T> To<T>();
}

为您的ShellViewModel提供类似于

的合同
public interface IShell : IConductActiveItem
{

}

实施INavigator

 public class MyApplicationNavigator : INavigator
    {
        private readonly IShell _shell;

        public MyApplicationNavigator(IShell shell)
        {
            _shell = shell;
        }
        public INavigationRequest<T> To<T>()
        {
            return new MyAppNavigationRequest<T>(() => IoC.Get<T>(), _shell);
        }

        /// <summary>
        /// <see cref="MyApplicationNavigator"/> specific implementation of <see cref="INavigationRequest{T}"/>
        /// </summary>
        /// <typeparam name="T">Type of view model</typeparam>
        private class MyAppNavigationRequest<T> : INavigationRequest<T>
        {
            private readonly Lazy<T> _viemodel;
            private readonly IShell _shell;

            public MyAppNavigationRequest(Func<T> viemodelFactory, IShell shell)
            {
                _viemodel = new Lazy<T>(viemodelFactory);
                _shell = shell;
            }

            public T Screen { get { return _viemodel.Value; } }
            public void Go()
            {
                _shell.ActivateItem(_viemodel.Value);
            }
        }
    }

此基础结构到位后,您可以根据需要通过将INavigator注入视图模型来使用它。

可以通过扩展方法来扩展此基本体系结构,以提供其他实用程序功能,例如您要在导航至视图模型时将其传递给视图模型。您可以按如下介绍其他服务,

/// <summary>
/// Defines a contract for View models that accept parameters
/// </summary>
/// <typeparam name="T">Type of argument expected</typeparam>
public interface IAcceptArguments<in T>
{
    void Accept(T args);
}

提供相同的实用方法,

public static class NavigationExtensions
{
    public static INavigationRequest<T> WithArguments<T, TArgs>(this INavigationRequest<T> request, TArgs args) where T : IAcceptArguments<TArgs>
    {
        return new NavigationRequestRequestWithArguments<T, TArgs>(request, args);
    }
}

internal class NavigationRequestRequestWithArguments<T, TArgs> : INavigationRequest<T> where T : IAcceptArguments<TArgs>
{
    private readonly INavigationRequest<T> _request;
    private readonly TArgs _args;

    public NavigationRequestRequestWithArguments(INavigationRequest<T> request, TArgs args)
    {
        _request = request;
        _args = args;
    }

    public T Screen { get { return _request.Screen; } }
    public void Go()
    {
        _request.Screen.Accept(_args);
        _request.Go();
    }
}

用法:

可以使用简洁的api来使用它:

public void GoToProfile()
{
   //Say, this.CurrentUser is UserProfile 
   //and UserDetailsViewModel implements IAcceptArguments<UserProfile>
   _navigator.To<UserDetailsViewModel>().WithArguments(this.CurrentUser).Go();
}

可以根据您的要求扩展所需的范围。这样的架构的主要优点是

  • 您正在将视图模型(屏幕)的解析,导航和初始化与请求者(其他视图模型或服务)分离。
  • 可测试的单元,您可以模拟掉所有与视图模型无关的内容,可以单独测试导航。
  • 可扩展。额外的导航要求,例如生命周期管理,在不同视图之间来回导航等,可以通过扩展Navigator轻松实现。
  • 适应性-可以通过不更改任何视图模型来适应不同的IoC甚至不使用它。

答案 2 :(得分:1)

解决方案

我认为最好的解决方案是通过一家知道如何创建我的子视图模型的工厂。父视图模型将调用工厂。


成就

  • 您仅在需要时实例化子视图模型(懒惰)
  • 您可以从父视图模型和/或注入中传递参数
  • 您可以使用模拟工厂为父视图模型编写单元测试。这样一来,您就可以测试父视图模型是否创建了子视图模型,而无需真正创建它们。

编辑:感谢@Nkosi的回答,有一种简单的方法可以用Caliburn.Micro注入惰性视图模型(像工厂一样):) 使用我的答案进行最佳注射。