我应该在MVVM项目中使用其他容器吗?

时间:2016-02-02 22:00:08

标签: c# .net wpf xaml mvvm

示例项目的来源: https://github.com/AntwanReno/navi

我在WPF MVVM中做了一个项目。它有三个子项目:WPF app,ViewModels(PCL)和Domain(PCL)。 WPF只是一个WindowFrame和两个Pages。我将展示代码,但建议在repo中克隆/分叉我准备好的样本。

以下是WPF客户端的代码:

App.xaml.cs

namespace NaviWPFApp
{
    using System.Windows;
    using NaviWPFApp.Views;
    using NaviWPFApp.Views.Pages;   

    public partial class App : Application
    {
        public static NavigationService Navigation; 

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);  

            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();  

            Navigation = new NavigationService(mainWindow.MyFrame);
            Navigation.Navigate<FirstPage>();
        }
    }
}

App.xaml 它只是:

<Application x:Class="NaviWPFApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:NaviWPFApp"
             x:Name="Application">
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

我有一个带框架的主窗口和两个非常相似的页面(没有代码隐藏):

<Window x:Class="NaviWPFApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="NaviWPFApp" Height="300" Width="300">
    <Grid>
        <Frame x:Name="MyFrame" Margin="10" />
    </Grid>
</Window>   

<Page x:Class="NaviWPFApp.Views.FirstPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300" Title="FirstPage" 
      DataContext="{Binding FirstPageViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
        <Button Command="{Binding GoToSecondPageCommand}"  Height="30" Content="Go to second page" />
    </Grid>
</Page> 


<Page x:Class="NaviWPFApp.Views.SecondPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="SecondPage"
      DataContext="{Binding Path=SecondPageViewModel, Source={StaticResource ViewModelLocator}}">
    <StackPanel Margin="0, 100, 0, 0">
        <Button Command="{Binding CountSomethingCommand}" Content="Count something" Height="30" />
        <Button Command="{Binding BackToFirstPageCommand}" Content="Go back to page 1" Height="30" />
    </StackPanel>
</Page>

我的客户端我还有两个额外的类ViewModelLocator和NavigationService - 用于页面之间的导航:

namespace NaviWPFApp
{
    using NaviWPFApp.ViewModels.Pages;  

    public class ViewModelLocator
    {
        public FirstPageViewModel FirstPageViewModel => new FirstPageViewModel(App.Navigation);
        public SecondPageViewModel SecondPageViewModel => new SecondPageViewModel(App.Navigation);
    }
}   

namespace NaviWPFApp
{
    using System;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Controls;
    using NaviWPFApp.ViewModels.Common; 

    public class NavigationService : INavigationService
    {
        readonly Frame frame;   

        public NavigationService(Frame frame)
        {
            this.frame = frame;
        }   

        public void GoBack()
        {
            frame.GoBack();
        }   

        public void GoForward()
        {
            frame.GoForward();
        }   

        public bool Navigate(string page)
        {
            var type = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(a => a.Name.Equals(page));
            if (type == null) return false; 

            var src = Activator.CreateInstance(type);
            return frame.Navigate(src);
        }

        public bool Navigate<T>(object parameter = null)
        {
            var type = typeof(T);
            return Navigate(type, parameter);
        }

        public bool Navigate(Type source, object parameter = null)
        {
            var src = Activator.CreateInstance(source);
            return frame.Navigate(src, parameter);
        }
    }
}

这是我的 ViewModels(便携式)项目:

UI中每个页面只有两个ViewModel类,INavigationService(我不想知道有关NavigationService实现和UI客户端的任何信息),MyObservableObject和MyCommand。

MyObservableObjectMyCommandINotifyPropertyChangedICommand接口的典型实现。

所以这是一个接口和两个视图模型:

public interface INavigationService
{
    void GoForward();
    void GoBack();
    bool Navigate(string page);
}


namespace NaviWPFApp.ViewModels
{
    public class FirstPageViewModel : MyObservableObject
    {
        private readonly INavigationService navigationService;

        public FirstPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
        }

        public MyCommand GoToSecondPageCommand
        {
            get { return new MyCommand(x => navigationService.Navigate("SecondPage")); }
        }
    }
}   

namespace NaviWPFApp.ViewModels
{
    public class SecondPageViewModel : MyObservableObject
    {
        private readonly INavigationService navigationService;
        private readonly BusinessLogic businessLogic;   

        public SecondPageViewModel(INavigationService navigationService, BusinessLogic businessLogic = null)
        {
            this.navigationService = navigationService;
            this.businessLogic = businessLogic;
        }   

        public MyCommand BackToFirstPageCommand
        {
            get { return new MyCommand(x => navigationService.Navigate("FirstPage")); }
        }   

        public MyCommand CountSomethingCommand
        {
            get { return new MyCommand(x => businessLogic?.CountSomething()); }
        }
    }
}

我的业务逻辑,就是这样:

public class BusinessLogic
{
    private int counter = 0;

    public bool CountSomething()
    {
        return ++counter > 10;
    }
}

依赖关系很简单:除了自己的操作之外什么都不知道, ViewModel 知道域名,但没有关于View和查看的信息。嗯,这是我的问题 - 它知道ViewModel,但View应该知道Domain吗?我将解释我的担忧,但这就是我的意思:

enter image description here

首先关注:正如您所看到的,导航全部在ViewModel中完成,而Business逻辑仅在第二页面视图模型中使用。 SecondPage视图不需要知道逻辑。

第二个问题:因为我试图坚持依赖注入,我想在程序开始时创建我的域对象(我只需要一个)。所以在protected override void OnStartup(StartupEventArgs e)中,在VIEW中也是如此。我不知道如何将其传递给第二个视图模型,该模型是在ViewModelLocator中创建的。

所以我的问题是:如何转换此代码,以便更多ViewModel定向?我想将我的Domain对象注入ViewModel(它所属的位置),而不是注入View。

感谢您的任何建议!

2 个答案:

答案 0 :(得分:2)

您无法避免这种依赖,因为App.OnStartup是一个组合根,这意味着App.OnStartup知道所有内容。 但是,你可以避免的是你的应用程序中的这个全球道具:public static NavigationService Navigation;。您只需将其注入您需要它的对象即可。

  

首先关注:如您所见,导航全部完成   ViewModel和业务逻辑仅用于第二页面视图模型。   SecondPage视图不需要知道逻辑。

SecondPage不必了解业务对象。应用必须知道。因此,您可以将对象注入定位器,定位器可以在时间到来时将此对象注入特定的ViewModel。

  

第二个问题:因为我试图坚持依赖注入,   我想创建我的域对象(我只需要一个)   该计划的开始。所以在受保护的覆盖空虚   OnStartup(StartupEventArgs e),所以在VIEW中。我不知道,怎么样   将其传递给第二个视图模型,该模型在ViewModelLocator中创建。

依赖注入。

以下是我的表现:

public class ViewModelLocator
{
    private NavigationService navigationService;
    private BusinessLogic businessLogic;
    public void InjectNavigationService(NavigationService navigation)
    {
        navigationService = navigation;
    } 
    public void InjectBusinessLogic(BusinessLogic logic)
    {
        businessLogic = logic;
    } 

    public FirstPageViewModel FirstPageViewModel => new FirstPageViewModel(navigationService);
    public SecondPageViewModel SecondPageViewModel => new SecondPageViewModel(navigationService, businessLogic);
}

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        // Create/resolve all your objects in Comoposition Root:
        var businessLogic = new BusinessLogic();

        // Here you will have locator created already, but mainWindow has not been created yet
        // Retrive your locator
        ViewModelLocator locator = Resources.Values.OfType<ViewModelLocator>().FirstOrDefault();
        if (locator == null)
            throw new NoNullAllowedException("ViewModelLocator cannot be null.");

        MainWindow mainWindow = new MainWindow();
        var navigation = new NavigationService(mainWindow.MyFrame);

        // Inject your logic and navigation into locator
        locator.InjectBusinessLogic(businessLogic);
        locator.InjectNavigationService(navigation);

        // Set up first page
        navigation.Navigate<FirstPage>();

        // and show the window
        mainWindow.Show();
    }
}

答案 1 :(得分:0)

简短的回答是,您无法避免,尤其是如果您使用依赖注入/ IoC容器(建议将其解耦并提高可测试性)。

你无法避免它的原因是因为(当使用IoC时)你需要通过构造函数注入你的依赖项(或者不是最佳的,通过属性/方法)。大多数IoC容器要求这些都是公开的。

由于大多数以这种方式注入的类型不在您的ViewModel PCL中,而是在您的模型(域)中并且公开可见,因此即使您不手动初始化ViewModel,也需要引用该程序集。

你的困惑来自术语&#34; View&#34;。在这种情况下,您的WPF项目有多个角色。

  1. 它代表包含所有视图(XAML文件)的视图层
  2. 您的应用程序代码也在同一个项目/程序集中。应用程序代码类似NavigationServce实现(INavigationService接口但属于ViewModel程序集)
  3. 虽然您的观点不需要对您的域类进行任何引用,但您的应用程序确实如此(组合根,DI / IoC容器)。