Reputation: 2077
I already tried this but it didn't work for me. GetService
returns null
.
Disclaimer: This is a MRE (minimal reproducable example), so please don't suggest a way that doesn't use the service.
I have a ContentView from which I want to access my singletons that have been added to the dependency injector.
I want LuckyNumberText
to contain a random number using the service. However, because it's a content view, the constructor has to be parameterless and thus cannot be injected.
App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiMRE"
x:Class="MauiMRE.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
App.xaml.cs
namespace MauiMRE;
public partial class App : Application
{
public static IServiceProvider Services => ServiceProvider.Current;
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
}
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MauiMRE.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiMRE"
Shell.FlyoutBehavior="Disabled">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
AppShell.xaml.cs
namespace MauiMRE;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
Mainpage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiMRE.MainPage"
xmlns:local="clr-namespace:MauiMRE"
>
<local:MyContentView></local:MyContentView>
</ContentPage>
MainPage.xaml.cs
namespace MauiMRE;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
MauiProgram.cs
using Microsoft.Extensions.Logging;
namespace MauiMRE;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
builder.Services.AddSingleton<MyContentView>();
builder.Services.AddSingleton<MyContentViewModel>();
builder.Services.AddSingleton<MyService>();
return builder.Build();
}
}
MyContentView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiMRE.MyContentView">
<VerticalStackLayout>
<Label
Text="{Binding LuckyNumberText}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentView>
MyContentView.xaml.cs
namespace MauiMRE;
public partial class MyContentView : ContentView
{
public MyContentView()
{
InitializeComponent();
BindingContext = new MyContentViewModel();
}
}
MyContentViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MauiMRE;
public class MyContentViewModel
{
public MyContentViewModel()
{
var service = ServiceProvider.GetService<MyService>();
var result = service.RandomInt(); // crashes before this line is reached
}
public string LuckyNumberText
{
get
{
return $"Lucky number: {1}";
}
}
}
MyService.cs
namespace MauiMRE
{
public static class ServiceProvider
{
public static T GetService<T>()
{
var current = Current; // runs
return current.GetService<T>(); // crashes
}
public static IServiceProvider Current
=>
#if WINDOWS10_0_17763_0_OR_GREATER
MauiWinUIApplication.Current.Services;
#elif ANDROID
MauiApplication.Current.Services; // pretty sure this line crashes
#else
null;
#endif
}
}
Upvotes: 2
Views: 2140
Reputation: 21243
ORIGINAL ANSWER
App
constructor:public partial class App : Application
{
public static IServiceProvider Services; // Added
public App(IServiceProvider services)
{
Services = services; // Added
InitializeComponent();
MainPage = new AppShell();
}
}
Credit: Gerald Versluis' answer to a different question shows injection of IServiceProvider into a constructor.
[With this approach] Do not inject any other parameters into App
constructor, unless injected classes don't use GetService
. Those would attempt to be resolved BEFORE App.Services is ready. This means any use of App.Services.GetService
would break. But we need GetService
so that custom component MyView
below can have a parameterless constructor.
[See "UPDATE USING Marc Fabregat's approach" below, which removes this limitation.]
USAGE
It is now possible to use:
App.Services.GetService<SomeType>();
everywhere.
Including in the App constructor itself:
public App(IServiceProvider services)
{
Services = services; // Added
InitializeComponent();
// Assuming "MyPage", and any of its dependencies are registered in "CreateMauiApp()".
MainPage = App.Services.GetService<MyPage>();
}
UPDATE USING Marc Fabregat's approach
Marc Fabregat's answer avoids this limitation, by directly using a path to the service provider.
To adapt that answer to the code I show here:
public partial class App : Application
{
// This definition works even for views injected into App constructor.
public static IServiceProvider Services => AppServiceProvider.Current;
public App(MyPage mp)
{
InitializeComponent();
MainPage = mp;
}
}
MyService used in MyView (with either App.Services definition above)
{
public MyView()
{
InitializeComponent();
MyService myService = App.Services.GetService<MyService>();
var result = myService.RandomInt();
}
}
I've verified this works.
For DI to work, the class being injected must have a public constructor
public class MyService
{
public MyService() // Must be PUBLIC, or DI will give an (unhelpful) exception.
{
}
...
}
OP had private MyService(){ ... }
.
Upvotes: 2
Reputation: 81
You can create a helper like this:
public static class AppServiceProvider
{
public static TService GetService<TService>()
=> Current.GetService<TService>();
public static IServiceProvider Current
=>
#if WINDOWS10_0_17763_0_OR_GREATER
MauiWinUIApplication.Current.Services;
#elif ANDROID
MauiApplication.Current.Services;
#elif IOS || MACCATALYST
MauiUIApplicationDelegate.Current.Services;
#else
null;
#endif
}
then call it wherever you need
public partial class MyContentView : ContentView
{
public MyContentView()
{
InitializeComponent();
BindingContext = AppServiceProvider.GetService<MyContentViewModel>();;
}
}
hope it helps
Upvotes: 1