如何在WPF/MVVM应用程序中处理依赖注入

129

我正在开始一个新的桌面应用程序,并希望使用MVVM和WPF构建。

我还打算使用TDD。

问题是我不知道如何在生产代码中使用IoC容器来注入我的依赖项。

假设我有以下类和接口:

public interface IStorage
{
    bool SaveFile(string content);
}

public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

然后我有另一个类,它依赖于 IStorage ,假设这个类是 ViewModel 或业务类...

public class SomeViewModel
{
    private IStorage _storage;

    public SomeViewModel(IStorage storage){
        _storage = storage;
    }
}

有了这个,我可以轻松编写单元测试以确保它们正常工作,使用模拟等方法。

问题在于当它用于实际应用程序时。我知道我必须有一个IoC容器来链接IStorage接口的默认实现,但我该怎么做呢?

例如,如果我有以下XAML,会怎样呢:

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

在这种情况下,我应该如何正确地“告诉”WPF注入依赖项?

另外,假设我需要从我的C#代码中获取SomeViewModel的实例,我该怎么做?

我感到完全迷失了,我会很感激任何示例或指导,告诉我如何处理它的最佳方法。

我熟悉StructureMap,但并不是专家。如果有更好/更简单/开箱即用的框架,请告诉我。


1
使用 .NET Core 3.0 预览版,你可以通过一些微软的 NuGet 包完成此操作。 - Bailey Miller
10个回答

109

我一直在使用Ninject,并发现它很容易使用。所有设置都在代码中完成,语法相当简单,并且有良好的文档(以及在SO上的大量答案)。

所以基本上是这样的:

创建视图模型,并将IStorage接口作为构造函数参数:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

创建一个ViewModelLocator,其中包含一个用于获取视图模型的属性,该属性从Ninject中加载视图模型:
class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

ViewModelLocator 作为应用程序级别的资源在 App.xaml 中定义:

<Application ...>
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

UserControlDataContext与ViewModelLocator中对应的属性绑定。
<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

创建一个继承自NinjectModule的类,该类将设置必要的绑定(IStorage和viewmodel):
class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

在应用程序启动时使用必要的Ninject模块(目前为上面的模块)初始化IoC内核:

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

我使用一个静态的IocKernel类来保存IoC内核的应用程序范围实例,这样我需要时就可以轻松地访问它:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

    public static void Initialize(params INinjectModule[] modules)
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(modules);
        }
    }
}

这个解决方案确实使用了静态的ServiceLocator(即IocKernel),通常被认为是反模式,因为它隐藏了类的依赖关系。然而,对于UI类来说,很难避免一些手动的服务查找,因为它们必须有一个无参数的构造函数,并且您无法控制实例化,所以您无法注入VM。至少这种方式允许您在隔离的情况下测试VM,这也是所有业务逻辑所在的地方。
如果有更好的方法,请分享一下。
编辑: Lucky Likey提供了一种消除静态服务定位器的方法,通过让Ninject实例化UI类。答案的详细信息可以在这里看到。

18
我刚接触依赖注入,但你的解决方案本质上是将服务定位器反模式与Ninject相结合,因为你在使用静态的ViewModel Locator。有人可能会认为注入是在Xaml文件中完成的,这样就不太可能进行测试。我没有更好的解决方案,也可能会使用你的解决方案 - 但我认为在回答中提到这一点会很有帮助。 - user3141326
1
你的解决方案真是太好了,只有一点“问题”就是以下这行代码:DataContext="{Binding [...]}"。这会导致VS-Designer在ViewModel的构造函数中执行所有程序代码。在我的情况下,窗口被执行并模态地阻止任何与VS的交互。也许应该修改ViewModelLocator,不要在设计时定位“真正”的ViewModels。 - 另一个解决方案是“禁用项目代码”,这也将防止显示其他所有内容。也许你已经找到了一个好的解决方案。如果是这样,请向我们展示一下。 - LuckyLikey
1
@LuckyLikey 你可以尝试使用d:DataContext="{d:DesignInstance vm:UserControlViewModel, IsDesignTimeCreatable=True}",但我不确定它是否有所不同。但是为什么/如何VM构造函数会启动模态窗口?以及是什么类型的窗口? - sondergard
1
@LuckyLikey 从未遇到过这种情况。您也可以绕过ViewModelLocator绑定,在代码后台直接创建VM。这样,您可以首先检查设计模式。因此,在代码后台构造函数中:if (DesignerProperties.GetIsInDesignMode(this) == false) IocKernel.Get<UserControlViewModel>(); - sondergard
2
@sondergard,我已经发布了对你的答案的改进,避免了ServiceLocator反模式。随时欢迎查看。 - LuckyLikey
显示剩余3条评论

81
在你的问题中,你在XAML中设置了视图的DataContext属性的值。这要求你的视图模型有一个默认构造函数。然而,正如你所指出的,这与依赖注入不太兼容,在那种情况下,你想要在构造函数中注入依赖项。
因此,你不能在XAML中设置DataContext属性,并进行依赖注入。 相反,你可以选择其他替代方案。
如果你的应用程序基于简单的分层视图模型,你可以在应用程序启动时构建整个视图模型层次结构(你需要从App.xaml文件中删除StartupUri属性):
public partial class App {

  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve<RootViewModel>();
    var window = new MainWindow { DataContext = viewModel };
    window.Show();
  }

}

这是基于一个视图模型对象图,以RootViewModel为根,但您可以将一些视图模型工厂注入到父视图模型中,使其能够创建新的子视图模型,因此对象图不必固定。这也有望回答您的问题:假设我需要在我的cs代码中实例化SomeViewModel,我该怎么做?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

  public void AddChild() {
    Children.Add(_childViewModelFactory.Create());
  }

  ObservableCollection<ChildViewModel> Children { get; private set; }

 }

class ChildViewModelFactory {

  public ChildViewModelFactory(/* ChildViewModel dependencies */) {
    // Store dependencies.
  }

  public ChildViewModel Create() {
    return new ChildViewModel(/* Use stored dependencies */);
  }

}

如果您的应用程序更具动态性,可能是基于导航的,您需要挂钩执行导航的代码。每次导航到新视图时,您需要创建一个视图模型(从 DI 容器中),视图本身并将视图的 DataContext 设置为视图模型。您可以采用 视图优先 的方式,在此情况下,您根据视图选择视图模型;或者您可以采用 视图模型优先 的方式,在此情况下,视图模型确定要使用哪个视图。MVVM 框架提供了这个关键功能,可以让您以某种方式将您的 DI 容器挂钩到视图模型的创建上,但您也可以自己实现它。我在这里有点模糊,因为根据您的需求,此功能可能会变得相当复杂。这是 MVVM 框架提供的核心功能之一,但在简单应用程序中自己编写将使您很好地理解 MVVM 框架提供的内容。

不能在 XAML 中声明 DataContext,这会导致失去一些设计时支持。如果您的视图模型包含一些数据,它将在设计时出现,这非常有用。幸运的是,在 WPF 中,您还可以使用设计时属性。一种方法是在 XAML 中向 <Window> 元素或 <UserControl> 添加以下属性:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True}"

视图模型类型应该有两个构造函数,一个是默认构造函数用于设计时间数据,另一个是用于依赖注入的构造函数:

class MyViewModel : INotifyPropertyChanged {

  public MyViewModel() {
    // Create some design-time data.
  }

  public MyViewModel(/* Dependencies */) {
    // Store dependencies.
  }

}

通过这样做,您可以使用依赖项注入并保留良好的设计时支持。


32
这正是我一直在寻找的。让我感到沮丧的是,我读到很多答案都说“只需使用 [yadde-ya] 框架”。那样当然很好,但我想首先确切地了解如何自己实现,然后我才能知道什么样的框架实际上对我有用。感谢您清晰地阐述。 - kmote
"在XAML中不能设置DataContext属性"这是完全错误的。 - Shenron
@神龙 请阅读前面的段落:这要求您的视图模型具有默认构造函数。为避免进一步的混淆,我进行了小的编辑以澄清在XAML中设置数据上下文和依赖注入的组合不起作用。 - Martin Liversage
@MartinLiversage 我仍然不同意,依赖注入并不能阻止在XAML中设置数据上下文。请参见 https://dev59.com/oFQI5IYBdhLWcg3w-Qli#69343170 - Shenron
3
@Shenron 我的观点是,如果视图模型没有默认构造函数,则无法以“通用WPF方式”在XAML中设置数据上下文。我知道你不喜欢我的措辞,但我仍然认为这是一个有效的观点。问题是如何解决这个问题。这里有多个答案,你提供了一种使用自定义标记扩展的替代解决方案。你仍然需要想出一个解决方案来解决这个问题,这正是Stack Overflow的目的:提供解决问题的解决方案。 - Martin Liversage
很棒的回答。关于设计时视图模型的观点加1 - 非常有用!您还可以添加一个关于在App.xaml中更改“StartupUri”的观点吗? - Kappacake

38
我在此发布的是对sondergard答案的改进,因为我要讲的内容无法适应评论的长度限制 :)
实际上,我介绍了一个简洁的解决方案,避免了需要使用ServiceLocator和StandardKernel实例的包装器,这在sondergard的解决方案中称为IocContainer。为什么?如上所述,这些都是反模式。
使StandardKernel实例在任何地方都可用的关键是使用.Get()方法所需的StandardKernel实例。
与sondergard的IocContainer不同,您可以在App类中创建StandardKernel。
只需从App.xaml中删除StartUpUri即可。
<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

这是App.xaml.cs中的代码后台

public partial class App
{
    private IKernel _iocKernel;

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

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get<MainWindow>();
        Current.MainWindow.Show();
    }
}

从现在开始,Ninject已经可以使用并准备好进行编程方面的操作 :)

注入您的DataContext

由于Ninject已经可以使用,您可以执行各种注入方式,例如属性设置注入或最常见的构造函数注入

以下是将ViewModel注入到您的WindowDataContext中的方法:

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

当然,如果你进行正确的绑定,也可以注入 IViewModel,但这不是本答案的一部分。
直接访问内核
如果需要直接调用内核的方法(例如 .Get<T>() 方法),可以让内核注入自身。
    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

如果您需要一个本地的Kernel实例,您可以将其作为属性注入。
    [Inject]
    public IKernel Kernel { private get; set; }

虽然这样做可能非常有用,但我不建议您这样做。请注意,以这种方式注入的对象将无法在构造函数中使用,因为它稍后再被注入。
根据链接,应该使用工厂扩展而不是注入IKernel(DI容器)。

在软件系统中使用DI容器的推荐方法是,应用程序的组合根是直接触及容器的唯一位置。

Ninject.Extensions.Factory的使用方法也可以在此处阅读。

如果有人对如何使用Ninject.Extensions.Factory感兴趣,请在评论中说明,我会添加更多信息。 - LuckyLikey
1
@LuckyLikey:我该如何通过XAML将ViewModel添加到没有无参构造函数的窗口数据上下文中?使用Sondergard的ServiceLocator解决方案,可以实现这种情况。 - Thomas Geulen
请告诉我如何检索我在附加属性中所需的服务?它们始终是静态的,包括支持DependencyProperty字段及其Get和Set方法。 - springy76
将StandardKernel随处可用是一个非常糟糕的做法,我完全不喜欢在窗口构造函数中注入ViewModel。 - Shenron
有点晚了,但是@LuckyLikey,你还有什么其他的框架推荐吗?我一直认为Ninject是最好的选择,因为我的要求是手动注入我需要的服务,但我不应该在构造函数中注入它们(因为它们可能会被使用或者不被使用)。 - Melissa
显示剩余3条评论

13

我采用“视图优先”的方法,即将视图模型传递给视图的构造函数(在其代码后台中),然后将其分配给数据上下文,例如:

我采用“视图优先”的方法,即将视图模型传递给视图的构造函数(在其代码后台中),然后将其分配给数据上下文,例如:

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

这个方案用于替换您基于 XAML 的方法。

我使用 Prism 框架来处理导航 - 当某些代码请求显示特定视图(通过“导航”到它),Prism 将解析该视图(在应用程序的 DI 框架内部); DI 框架将反过来解决视图所具有的任何依赖项(例如我的示例中的视图模型),然后解决其依赖项,以此类推。

选择 DI 框架几乎无关紧要,因为它们都做基本相同的事情,即您注册一个接口(或类型)以及您希望框架在发现对该接口的依赖关系时实例化的具体类型。记录一下,我使用 Castle Windsor。

Prism 导航需要一些适应,但一旦您理解了它,就非常好用,可以使用不同的视图组合应用程序。例如,您可以在主窗口上创建一个 Prism“区域”,然后使用 Prism 导航在此区域内从一个视图切换到另一个视图,例如,当用户选择菜单项或其他内容时。

或者,您可以查看 MVVM 框架之一,例如 MVVM Light。我没有使用过这些框架,无法评论它们的使用感受。


3
如何向子视图传递构造函数参数?我尝试了这种方法,但在父视图中出现异常,告诉我子视图没有默认的无参构造函数。 - Doctor Jones

12

安装MVVM Light。

其中一部分安装过程是创建视图模型定位器。这是一个将您的视图模型公开为属性的类。这些属性的getter可以返回来自IOC引擎的实例。幸运的是,MVVM light还包括SimpleIOC框架,但如果您愿意,也可以连接其他框架。

使用SimpleIOC,您可以根据类型注册实现...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);
在这个例子中,视图模型是按其构造函数创建并传递了一个服务提供者对象。然后,您创建一个属性,该属性从 IOC 返回一个实例。
public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}
视图模型定位器的巧妙之处在于它被创建为数据源,通常在app.xaml或其等效位置中。
<local:ViewModelLocator x:key="Vml" />

现在您可以绑定到其'MyViewModel'属性,以获取带有注入服务的视图模型。

希望这有所帮助。对于任何代码不准确之处表示歉意,该代码是在iPad上从记忆中编写的。


你不应该在应用程序的引导程序之外使用 GetInstanceresolve。这就是 DI 的目的! - Soleil
我同意您可以在启动期间设置属性值,但是暗示使用延迟实例化违反了依赖注入是错误的。 - kidshaw
@kishaw 我没有。 - Soleil

8

Canonic DryIoc案例

回答一篇旧帖子,使用 DryIoc 实现,并且我认为这是依赖注入和接口的最佳实践(最小化使用具体类)。

  1. The starting point of a WPF app is App.xaml, and there we tell what is the inital view to use; we do that with code behind instead of the default xaml:
  2. remove StartupUri="MainWindow.xaml" in App.xaml
  3. in codebehind (App.xaml.cs) add this override OnStartup:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DryContainer.Resolve<MainWindow>().Show();
    }
    

这是启动点;也是唯一应该调用resolve的地方。

  1. the configuration root (according to Mark Seeman's book Dependency injection in .NET; the only place where concrete classes should be mentionned) will be in the same codebehind, in the constructor:

    public Container DryContainer { get; private set; }
    
    public App()
    {
        DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
        DryContainer.Register<IDatabaseManager, DatabaseManager>();
        DryContainer.Register<IJConfigReader, JConfigReader>();
        DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
            Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
        DryContainer.Register<MainWindow>();
    }
    

备注和更多细节

  • 我仅在视图MainWindow 中使用具体类;
  • 我必须指定要使用的构造函数(我们需要使用DryIoc来做到这一点),因为默认构造函数需要存在于XAML设计器中,而带有注入的构造函数是实际用于应用程序的。

具有依赖注入的ViewModel构造函数:

public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
    _dbMgr = dbmgr;
    _jconfigReader = jconfigReader;
}

设计的ViewModel默认构造器:

public MainWindowViewModel()
{
}

视图的代码后台:
public partial class MainWindow
{
    public MainWindow(IMainWindowViewModel vm)
    {
        InitializeComponent();
        ViewModel = vm;
    }

    public IViewModel ViewModel
    {
        get { return (IViewModel)DataContext; }
        set { DataContext = value; }
    }
}

如果需要在视图(MainWindow.xaml)中得到一个带有ViewModel的设计实例,需要做以下几步:

d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"

结论

因此,我们使用DryIoc容器和DI获得了一个非常简洁和最小化的WPF应用程序实现,同时保持视图和视图模型的设计实例。


1
这个是最好的。 - Furkan Gözükara
@MonsterMMORPG 你应该点赞,这就是投票的目的 ;) - Soleil

2
另一个简单的解决方案是创建一个标记扩展,通过其类型解析您的视图模型:
public class DISource : MarkupExtension {
    public static Func<Type, object, string, object> Resolver { get; set; }

    public Type Type { get; set; }
    public object Key { get; set; }
    public string Name { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider) => Resolver?.Invoke(Type, Key, Name);
}

您可以按照以下方式将此扩展程序适应于任何 DI 容器:

protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    DISource.Resolver = Resolve;
}
object Resolve(Type type, object key, string name) {
    if(type == null)
        return null;
    if(key != null)
        return Container.ResolveKeyed(key, type);
    if(name != null)
        return Container.ResolveNamed(name, type);
    return Container.Resolve(type);
}

在XAML中使用它就像这样简单:

DataContext="{local:DISource Type=viewModels:MainViewModel}"

通过这种方式,您可以轻松地将DataContext分配给您的视图,并使用依赖注入容器直接自动注入所有必需的参数到您的视图模型中。使用此技术,您不必将DI容器或其他参数传递给View构造函数。
DISource不依赖于容器类型,因此您可以在任何依赖注入框架中使用它。只需将DISource.Resolver属性设置为一个知道如何使用您的DI容器的方法即可。
我在WPF MVVM应用程序中的依赖注入中更详细地描述了这种技术。

2

使用托管可扩展性框架(Managed Extensibility Framework)

[Export(typeof(IViewModel)]
public class SomeViewModel : IViewModel
{
    private IStorage _storage;

    [ImportingConstructor]
    public SomeViewModel(IStorage storage){
        _storage = storage;
    }

    public bool ProperlyInitialized { get { return _storage != null; } }
}

[Export(typeof(IStorage)]
public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

//Somewhere in your application bootstrapping...
public GetViewModel() {
     //Search all assemblies in the same directory where our dll/exe is
     string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
     var catalog = new DirectoryCatalog(currentPath);
     var container = new CompositionContainer(catalog);
     var viewModel = container.GetExport<IViewModel>();
     //Assert that MEF did as advertised
     Debug.Assert(viewModel is SomViewModel); 
     Debug.Assert(viewModel.ProperlyInitialized);
}

一般来说,你需要一个静态类,并使用工厂模式为你提供全局容器(缓存的,当然)。
至于如何注入视图模型,你可以像注入其他所有东西一样注入它们。在XAML文件的代码后台中创建一个导入构造函数(或将导入语句放在属性/字段上),并告诉它导入视图模型。然后将你的 WindowDataContext 绑定到该属性。你实际上从容器中提取的根对象通常是组成的Window对象。只需向窗口类添加接口并导出它们,然后从上面的目录中获取(在App.xaml.cs中...这是WPF引导文件)。

你忽略了 DI 的一个重要点,那就是避免使用 new 创建任何实例。 - Soleil

2
我建议使用ViewModel-First方法https://github.com/Caliburn-Micro/Caliburn.Micro

see: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions

使用Castle Windsor作为IOC容器。
关于约定的一切
Caliburn.Micro的主要特点之一在于它能够通过遵循一系列约定来消除样板代码的需要。有些人喜欢约定,有些人则不喜欢。这就是为什么CM的约定是完全可定制的,甚至可以完全关闭它们(如果不需要的话)。如果您要使用约定,并且因为它们默认是开启的,那么了解这些约定以及它们的工作原理是很重要的。这就是本文的主题。 视图解析(ViewModel-First)
基础知识
当使用CM时,您可能会遇到的第一个约定与视图解析有关。这个约定影响应用程序中任何ViewModel-First区域。在ViewModel-First中,我们有一个现有的ViewModel需要呈现到屏幕上。为了做到这一点,CM使用一个简单的命名模式来查找一个UserControl1,它应该绑定到ViewModel并显示。那么,这个模式是什么?让我们看一下ViewLocator.LocateForModelType:
public static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) =>{
    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if(context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
        viewTypeName = viewTypeName + "." + context;
    }

    var viewType = (from assmebly in AssemblySource.Instance
                    from type in assmebly.GetExportedTypes()
                    where type.FullName == viewTypeName
                    select type).FirstOrDefault();

    return viewType == null
        ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
        : GetOrCreateViewType(viewType);
};

首先,我们忽略“context”变量。为了得出视图,我们假设您在VM的命名中使用了文本“ViewModel”,因此我们只需通过删除“Model”一词来将其替换为“View”。这会更改类型名称和命名空间。因此,ViewModels.CustomerViewModel 将成为 Views.CustomerView。或者,如果您按功能组织应用程序:CustomerManagement.CustomerViewModel 变为 CustomerManagement.CustomerView。希望这很简单明了。一旦我们得到名称,我们就搜索具有该名称的类型。我们搜索您已公开为可通过 AssemblySource.Instance 搜索的任何程序集。如果我们找到该类型,则创建一个实例(或从IoC容器获取一个已注册的实例)并将其返回给调用者。如果我们没有找到该类型,则生成一个带有适当“未找到”的消息的视图。
现在,回到“context”值。这是CM支持在同一个ViewModel上有多个视图的方式。如果提供了上下文(通常是字符串或枚举),我们会根据该值进一步转换名称。该转换有效地假定您为不同的视图创建了一个文件夹(命名空间),方法是从末尾删除“View”一词并添加上下文。因此,给定上下文为“Master”,我们的ViewModels.CustomerViewModel将变为Views.Customer.Master。

2
你的整篇帖子都是观点。 - JWP

1

从你的app.xaml中移除启动URI。

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IoC.Configure(true);

        StartupUri = new Uri("Views/MainWindowView.xaml", UriKind.Relative);

        base.OnStartup(e);
    }
}

现在,您可以使用IoC类来构建实例。

MainWindowView.xaml.cs

public partial class MainWindowView
{
    public MainWindowView()
    {
        var mainWindowViewModel = IoC.GetInstance<IMainWindowViewModel>();

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

1
你不应该在app.xaml.cs之外拥有任何GetInstanceresolve的容器,否则你就失去了DI的意义。此外,在视图的代码后台中提及xaml视图有点复杂。只需使用纯C#调用视图,并使用容器完成此操作。 - Soleil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接