使用Template Studio for WinUI时,视图和视图模型之间存在奇怪的依赖注入行为。

huangapple go评论66阅读模式
英文:

Odd dependency injection behavior with Views & ViewModels using Template Studio for WinUI

问题

Using Template Studio for WinUI,我创建了一个包含两个页面(Main和Foo)的应用程序。如果将视图模型传递到页面的构造函数中,应用程序会生成异常,但如果使用 App.GetService<xViewModel>() 来获取视图模型,其中x是页面的视图模型名称,一切都正常工作。将相同的视图模型传递到其他非页面类的构造函数中也可以正常工作。

有人能解释为什么将视图模型传递到页面的构造函数会失败吗?

以下是相关的代码片段:

生成的 Foo.xaml.cs 文件(运行良好):

using Ioc.ViewModels;
using Microsoft.UI.Xaml.Controls;

namespace Ioc.Views;

public sealed partial class FooPage : Page
{
    public FooViewModel ViewModel
    {
        get;
    }

    public FooPage()
    {
        ViewModel = App.GetService<FooViewModel>();
        InitializeComponent();
    }
}

Foo.xaml.cs - 将视图模型传递到构造函数中(抛出异常):

public FooPage(FooViewModel viewModel)
{
    ViewModel = viewModel;
    InitializeComponent();
}

当尝试导航到 Foo 页面并将视图模型传递到构造函数时,会出现以下异常:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

at Ioc.Ioc_XamlTypeInfo.XamlUserType.ActivateInstance() in C:\PathToProject\Ioc\obj\x64\Debug\net7.0-windows10.0.19041.0\win10-x64\XamlTypeInfo.g.cs:line 2602


App.xaml.cs 构造函数中注册 FooViewModel 服务的部分:

```csharp
public App()
{
    InitializeComponent();

    Host = Microsoft.Extensions.Hosting.Host
    .CreateDefaultBuilder()
    .UseContentRoot(AppContext.BaseDirectory)
    .ConfigureServices((context, services) =>
    {
        // 默认激活处理程序
        services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();

        // 其他激活处理程序

        // 服务
        services.AddTransient<INavigationViewService, NavigationViewService>();

        services.AddSingleton<IActivationService, ActivationService>();
        services.AddSingleton<IPageService, PageService>();
        services.AddSingleton<INavigationService, NavigationService>();

        // 核心服务
        services.AddSingleton<IFileService, FileService>();

        // 视图和视图模型
        services.AddTransient<FooViewModel>();
        services.AddTransient<FooPage>();
        services.AddTransient<MainViewModel>();
        services.AddTransient<MainPage>();
        services.AddTransient<ShellPage>();
        services.AddTransient<ShellViewModel>();

        // 配置
    })
    .Build();

    UnhandledException += App_UnhandledException;
}

编辑

使用帧并使用以下导航方法进行导航:

private Frame? _frame; // 类型为 Microsoft.UI.Xaml.Controls.Frame

public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
{
    var pageType = _pageService.GetPageType(pageKey);

    if (_frame != null && (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))))
    {
        _frame.Tag = clearNavigation;
        var vmBeforeNavigation = _frame.GetPageViewModel();
        var navigated = _frame.Navigate(pageType, parameter);
        if (navigated)
        {
            _logger.LogDebug("Navigated to {PageType}", pageType.Name);
            _lastParameterUsed = parameter;
            if (vmBeforeNavigation is INavigationAware navigationAware)
            {
                navigationAware.OnNavigatedFrom();
            }
        }

        return navigated;
    }

    return false;
}

代码在以下行引发异常:

var navigated = _frame.Navigate(pageType, parameter);

希望这些信息对你有所帮助。

英文:

Using Template Studio for WinUI, I created an app with 2 pages, Main and Foo. If the view model is passed into the page's constructor the application generates an exception, but if the view model is grabbed using App.GetService&lt;xViewModel&gt;(); where x is the name of the view model for the page, everything works great. Passing the same view model into other non-page class constructors works great as well.

Can someone explain why passing the view model into the page's constructor fails?

Code

Generated Foo.xaml.cs file (works great):

using Ioc.ViewModels;
using Microsoft.UI.Xaml.Controls;

namespace Ioc.Views;

public sealed partial class FooPage : Page
{
    public FooViewModel ViewModel
    {
        get;
    }

    public FooPage()
    {
        ViewModel = App.GetService&lt;FooViewModel&gt;();
        InitializeComponent();
    }
}

Foo.xaml.cs - passing view model into the constructor (throws exception):

    public FooPage(FooViewModel viewModel)
    {
        ViewModel = viewModel;
        InitializeComponent();
    }

the following exception occurs when trying to navigate to the Foo page when view model is passed into the constructor:
>System.NullReferenceException: 'Object reference not set to an instance of an object.'
>
>at Ioc.Ioc_XamlTypeInfo.XamlUserType.ActivateInstance() in C:\PathToProject\Ioc\obj\x64\Debug\net7.0-windows10.0.19041.0\win10-x64\XamlTypeInfo.g.cs:line 2602

App.xaml.cs constructor where the FooViewModel service is registered:

    public App()
    {
        InitializeComponent();

        Host = Microsoft.Extensions.Hosting.Host.
        CreateDefaultBuilder().
        UseContentRoot(AppContext.BaseDirectory).
        ConfigureServices((context, services) =&gt;
        {
            // Default Activation Handler
            services.AddTransient&lt;ActivationHandler&lt;LaunchActivatedEventArgs&gt;, DefaultActivationHandler&gt;();

            // Other Activation Handlers

            // Services
            services.AddTransient&lt;INavigationViewService, NavigationViewService&gt;();

            services.AddSingleton&lt;IActivationService, ActivationService&gt;();
            services.AddSingleton&lt;IPageService, PageService&gt;();
            services.AddSingleton&lt;INavigationService, NavigationService&gt;();

            // Core Services
            services.AddSingleton&lt;IFileService, FileService&gt;();

            // Views and ViewModels
            services.AddTransient&lt;FooViewModel&gt;();
            services.AddTransient&lt;FooPage&gt;();
            services.AddTransient&lt;MainViewModel&gt;();
            services.AddTransient&lt;MainPage&gt;();
            services.AddTransient&lt;ShellPage&gt;();
            services.AddTransient&lt;ShellViewModel&gt;();

            // Configuration
        }).
        Build();

        UnhandledException += App_UnhandledException;
    }

Edit

Navigation using a frame and this Navigation method:

private Frame? _frame; // Type Microsoft.UI.Xaml.Controls.Frame

    public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
    {
        var pageType = _pageService.GetPageType(pageKey);

        if (_frame != null &amp;&amp; (_frame.Content?.GetType() != pageType || (parameter != null &amp;&amp; !parameter.Equals(_lastParameterUsed))))
        {
            _frame.Tag = clearNavigation;
            var vmBeforeNavigation = _frame.GetPageViewModel();
            var navigated = _frame.Navigate(pageType, parameter);
            if (navigated)
            {
                _logger.LogDebug(&quot;Navigated to {PageType}&quot;, pageType.Name);
                _lastParameterUsed = parameter;
                if (vmBeforeNavigation is INavigationAware navigationAware)
                {
                    navigationAware.OnNavigatedFrom();
                }
            }

            return navigated;
        }

        return false;
    }

The code throws the exception on this line:

var navigated = _frame.Navigate(pageType, parameter);

答案1

得分: 1

By navigate I guess you mean navigation of a Frame control but the navigation feature of Frames doesn't resolve your type like App.GetService&lt;T&gt;() does.

What you can do is something like this:

In App.xaml.cs

public static bool TryGetService(Type serviceType, out object? service)
{
    service = (App.Current as App)?.Host?.Services.GetService(serviceType);
    return service is not null;
}

and in the navigation event handler:

private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
    if (args.SelectedItem is NavigationViewItem navigationViewItem &amp;&amp;
        navigationViewItem.Tag is string pageName &amp;&amp;
        Type.GetType(pageName) &amp;&amp;
        App.TryGetService(pageType, out object? page) is true)
    {
        this.ContentFrame.Content = page;
    }
}
英文:

By navigate I guess you mean navigation of a Frame control but the navigation feature of Frames doesn't resolve your type like App.GetService&lt;T&gt;() does.

What you can do is something like this:

In App.xaml.cs

public static bool TryGetService(Type serviceType, out object? service)
{
    service = (App.Current as App)?.Host?.Services.GetService(serviceType);
    return service is not null;
}

and in the navigation event handler:

private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
    if (args.SelectedItem is NavigationViewItem navigationViewItem &amp;&amp;
        navigationViewItem.Tag is string pageName &amp;&amp;
        Type.GetType(pageName) &amp;&amp;
        App.TryGetService(pageType, out object? page) is true)
    {
        this.ContentFrame.Content = page;
    }
}
``

</details>



huangapple
  • 本文由 发表于 2023年5月21日 08:01:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76297794.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定