MAUI嵌套视图场景下,如何在XAML中使用依赖注入?
我正在尝试用MVVM模式嵌套视图,视图的ViewModel通过依赖注入注入。视图和ViewModel已注册为服务,依赖注入本身可以正常工作,OuterView的代码后台和InnerView的XAML已省略。
InnerView的构造函数如下:
public partial class InnerView { public InnerView(InnerViewModel viewModel) { BindingContext = viewModel; InitializeComponent(); } }
在OuterView的XAML中直接引用InnerView:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:pages="clr-namespace:Example.Views" x:Class="Example.Views.OuterView" x:DataType="viewModels:OuterViewModel"> <VerticalStackLayout x:Name="InnerViewWrapper"> <pages:InnerView/> </VerticalStackLayout> </ContentPage>
运行时报错:Type 'InnerView' is not usable as an object element because it [...] does not define a parameterless constructor [...],这符合预期。我现在是通过代码后台添加视图:
StockHistoryGraph? view = Handler?.MauiContext?.Services.GetServices<StockHistoryGraph>().FirstOrDefault(defaultValue: default); if (view is not null) InnerViewWrapper.Add(view);
但这种方式太繁琐,想问:是否可以在XAML文件中直接对视图使用依赖注入?
当然可以在XAML中实现依赖注入创建视图,MAUI提供了几种简便的方式:
1. 使用{x:FactoryMethod}调用DI容器获取实例
你可以通过{x:FactoryMethod}调用MauiContext.Services.GetRequiredService<T>()来获取视图实例,需要先给ContentPage指定x:Name以便访问Handler.MauiContext:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:pages="clr-namespace:Example.Views" xmlns:di="clr-namespace:Microsoft.Extensions.DependencyInjection;assembly=Microsoft.Extensions.DependencyInjection.Abstractions" x:Class="Example.Views.OuterView" x:DataType="viewModels:OuterViewModel" x:Name="RootPage"> <VerticalStackLayout> <pages:InnerView x:FactoryMethod="{x:Static di:ServiceProviderServiceExtensions.GetRequiredService}" x:Arguments="{x:Type pages:InnerView}" x:FactoryArguments="{Binding Source={x:Reference RootPage}, Path=Handler.MauiContext.Services}"/> </VerticalStackLayout> </ContentPage>
这种方式直接利用DI容器的GetRequiredService方法获取已注册的InnerView实例,无需手动写后台代码。
2. 自定义标记扩展(更简洁)
如果觉得上面的写法太冗长,可以自定义一个标记扩展来简化调用:
首先创建标记扩展类:
using Microsoft.Maui.Controls.Xaml; using Microsoft.Extensions.DependencyInjection; [ContentProperty(nameof(Type))] public class ResolveExtension : IMarkupExtension { public Type Type { get; set; } public object ProvideValue(IServiceProvider serviceProvider) { if (Type == null) throw new InvalidOperationException("必须指定Type属性"); return serviceProvider.GetRequiredService(Type); } }
然后在XAML中引用这个扩展:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:pages="clr-namespace:Example.Views" xmlns:extensions="clr-namespace:Example.Extensions" x:Class="Example.Views.OuterView" x:DataType="viewModels:OuterViewModel"> <VerticalStackLayout> <ContentView Content="{extensions:Resolve Type={x:Type pages:InnerView}}"/> </VerticalStackLayout> </ContentPage>
或者直接把标记扩展返回的视图作为子元素(需要实现IMarkupExtension<View>):
public class ResolveViewExtension : IMarkupExtension<View> { public Type ViewType { get; set; } public View ProvideValue(IServiceProvider serviceProvider) { if (ViewType == null) throw new InvalidOperationException("必须指定ViewType属性"); return (View)serviceProvider.GetRequiredService(ViewType); } object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider); }
XAML中使用:
<VerticalStackLayout> <extensions:ResolveViewExtension ViewType="{x:Type pages:InnerView}"/> </VerticalStackLayout>
注意事项
- 确保
InnerView和InnerViewModel已正确注册到DI容器,比如:
builder.Services.AddTransient<InnerViewModel>(); builder.Services.AddTransient<InnerView>();
- 自定义标记扩展的方式更适合频繁使用的场景,代码更简洁易读。
内容的提问来源于stack exchange,提问作者emilyisstewpid

