.NET MAUI - 依赖注入 - 构造函数从未被调用

后端开发 2026-07-10

我想做一个通用的弹出框,以便在多种不同场景下重复使用。出于某种原因,尽管我已经正确注册并调用,但我的视图模型的构造函数从未被调用。

这是我在我的 MauiProgram.cs 中注册它的方式:

builder.Services.AddTransientPopup<PickerBottomSheet, BaitPickerViewModel>();

这是我的通用调用端:

public async Task<TItem?> DisplayBottomPickerAsync<TViewModel, TItem>(CancellationToken token = default)
    where TViewModel : PickerBottomSheetViewModel<TItem>
    where TItem : IPickableItem
{
    var result = await _popup.ShowPopupAsync<TViewModel, TItem>(Shell.Current, GetOptions(true), token);
    return result.WasDismissedByTappingOutsideOfPopup ? default : result.Result;
}

这是我用来调用它的代码:

var result = await _popup.DisplayBottomPickerAsync<BaitPickerViewModel, Bait>(token);

这是代码的关键部分,整组类放在一起会太冗长(按继承关系排序):

public abstract partial class PickerBottomSheetViewModel : ViewModelBase

public abstract partial class PickerBottomSheetViewModel<T> : PickerBottomSheetViewModel where T : IPickableItem

public sealed partial class BaitPickerViewModel : PickerBottomSheetViewModel<Bait>
{
    private readonly SqliteStorage _sql;
    private readonly ILocalizationResourceManager _lang;

    public BaitPickerViewModel(PopupHost popup, SqliteStorage sql, ILocalizationResourceManager lang) : base(popup)
    {
        _sql = sql; //Never called
        _lang = lang;

        Title = _lang["SelectBaitTitle"];
        AddNewText = _lang["AddNewBait"];
        CanEditOrDelete = true;
    }

    protected override IAsyncEnumerable<Bait> LoadTypedItemsAsync(CancellationToken token) => _sql.EnumerateBaitsAsync(token);
    protected override Task<Bait?> AddTypedItemAsync(CancellationToken token) => base.AddTypedItemAsync(token);
    protected override Task<Bait?> EditTypedItemAsync(Bait item, CancellationToken token) => base.EditTypedItemAsync(item, token);
    protected override Task DeleteTypedItemAsync(Bait item, CancellationToken token) => base.DeleteTypedItemAsync(item, token);
}

Popup 本身:

public sealed partial class PickerBottomSheet : Popup<IPickableItem>
{
    public PickerBottomSheet()
    {
        InitializeComponent();
    }
}
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
           xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
           xmlns:icon="http://www.aathifmahir.com/dotnet/2022/maui/icons"
           xmlns:lang="clr-namespace:LocalizationResourceManager.Maui;assembly=LocalizationResourceManager.Maui"
           xmlns:common="clr-namespace:CatchLog.Common"
           xmlns:models="clr-namespace:CatchLog.Models"
           xmlns:viewmodels="clr-namespace:CatchLog.ViewModels.Popups"
           x:Class="CatchLog.Popups.PickerBottomSheet"
           x:DataType="viewmodels:PickerBottomSheetViewModel"
           x:TypeArguments="models:IPickableItem"
           Background="{mct:AppThemeResource Surface}"
           HorizontalOptions="Fill"
           VerticalOptions="End">

我真的不知道为什么这不起作用。我没有抛出异常,弹出框也会显示。只是因为构造函数根本没有被调用,所以我所赋的值也就不存在。

编辑

如果我把 BaitPickerViewModel 直接传给 PickerBottomSheet,就能工作。但这不是一个可行的解决办法,因为那样我就不能使用其他的视图模型,这也是这套实现的核心意义所在。

另外传入 PickerBottomSheetViewModel 也不可能,因为它是抽象的。

解决方案

我找到了实现我想要的方法。 现在对于每个视图模型,我还会创建一个 PickerBottomSheet 的子类,并显式注册。

builder.Services.AddTransientPopup<BaitPickerBottomSheet, BaitPickerViewModel>();
builder.Services.AddTransientPopup<SpeciesPickerBottomSheet, SpeciesPickerViewModel>();
public partial class PickerBottomSheet : Popup<IPickableItem>
{
    public PickerBottomSheet(PickerBottomSheetViewModel vm)
    {
        BindingContext = vm;
        InitializeComponent();
    }
}
public sealed partial class BaitPickerBottomSheet : PickerBottomSheet
{
    public BaitPickerBottomSheet(BaitPickerViewModel vm) : base(vm) { }
}

我先把它标记为答案,如果有人有更好的办法,欢迎分享 :)

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章