Я уже пробовал это но мне не помогло. GetService возвращается null.
Отказ от ответственности: это MRE (минимально воспроизводимый пример), поэтому, пожалуйста, не предлагайте способ, который не использует службу.
У меня есть ContentView, из которого я хочу получить доступ к своим синглтонам, добавленным в инжектор зависимостей.
Я хочу, чтобы LuckyNumberText содержал случайное число с помощью сервиса. Однако, поскольку это представление содержимого, конструктор должен быть без параметров и, следовательно, не может быть внедрен.
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();
}
}
Главная.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
}
}
@ Джейсон, нет звонка, чтобы получить услугу, потому что это не сработало. Поэтому я не воспроизвел это здесь.
GetService — способ сделать это. Если у вас возникли проблемы с его использованием, вам необходимо предоставить подробную информацию
1) "Я уже пробовал это": покажите точную строку с вашим типом и в каком методе какого класса вы его вызвали. 2) Есть ли у вашего конструктора App параметры? Если да, добавьте к вопросу эту строку объявления. 3) Если вы поставите точку останова в начале конструктора вашего ContentView, а другую в CreateMauiApp на return builder.Build();, вызовется ли Build до того, как будет построен ваш ContentView?





ОРИГИНАЛЬНЫЙ ОТВЕТ
App:public partial class App : Application
{
public static IServiceProvider Services; // Added
public App(IServiceProvider services)
{
Services = services; // Added
InitializeComponent();
MainPage = new AppShell();
}
}
Кредит: Ответ Джеральда Верслуиса на другой вопрос показывает внедрение IServiceProvider в конструктор.
[При таком подходе] Не вводите никакие другие параметры в конструктор App, если только внедренные классы не используют GetService. Они будут пытаться быть решены ДО того, как App.Services будет готов. Это означает, что любое использование App.Services.GetService сломается. Но нам нужно GetService, чтобы пользовательский компонент MyView ниже мог иметь конструктор без параметров.
[См. «ОБНОВЛЕНИЕ С ИСПОЛЬЗОВАНИЕМ подхода Марка Фабрегата» ниже, которое снимает это ограничение.]
ИСПОЛЬЗОВАНИЕ
Теперь можно использовать:App.Services.GetService<SomeType>(); везде.
В том числе в самом конструкторе приложений:
public App(IServiceProvider services)
{
Services = services; // Added
InitializeComponent();
// Assuming "MyPage", and any of its dependencies are registered in "CreateMauiApp()".
MainPage = App.Services.GetService<MyPage>();
}
ОБНОВЛЕНИЕ С ИСПОЛЬЗОВАНИЕМ подхода Марка Фабрегата
Ответ Марка Фабрегата позволяет избежать этого ограничения, напрямую используя путь к поставщику услуг.
Чтобы адаптировать этот ответ к коду, который я показываю здесь:
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, используемый в MyView (с любым определением App.Services выше)
{
public MyView()
{
InitializeComponent();
MyService myService = App.Services.GetService<MyService>();
var result = myService.RandomInt();
}
}
Я проверил, это работает.
Чтобы DI работал, внедряемый класс должен иметь публичный конструктор.
public class MyService
{
public MyService() // Must be PUBLIC, or DI will give an (unhelpful) exception.
{
}
...
}
У ОП был private MyService(){ ... }.
Это не работает для меня. Я попал в следующие контрольные точки: InitializeComponent в AppShell(), InitializeComponent() в MainPage(), MauiApplication.Current.Services в IServiceProvider Current. После этого я вылетаю с необработанным исключением в JNINativeWrapper. Трассировка стека не содержит ни одной из моих функций, но проходит через ShellContent.cs и ShellSectionRenderer.cs
Ага. Вот что бывает, если не делать все точно правильно. Удалите все, что можно, чтобы сделать его максимально простым, и обновите рассматриваемый код, чтобы он соответствовал тому, что вы сделали. В том числе и то, что не сработало. Убедитесь, что причина в том, что вы думаете: удаляйте код до тех пор, пока у вас не будет что-то, что работает (даже если оно не делает то, что вы хотите) - нужно точно знать, какая строка его прерывает. Несмотря на то, что я сталкивался с этими проблемами раньше, мне потребовалось несколько правок, прежде чем он заработал правильно. Может иметь больший успех, используя ответ Марка напрямую.
Я обновил все в ОП. Кажется, это линия Марка ломает его.
Ах. Я тестировал на Windows. Судя по вашему комментарию к коду Марка, вы тестируете на Android? Вы поставили точку останова на этой строке, перешагнули через нее? Он возвращает ноль? Или это исключение в вызывающем абоненте, когда он пытается это использовать? Чтобы увидеть, разделите вызывающую строку на две части: одну для получения Current, другую для вызова GetService. Какая линия дает сбой?
Я переключился на Windows, чтобы проверить, и он также не работает в том же фрагменте кода (очевидно, не в блоке Android). Сразу после прохождения этой точки останова код вылетает до достижения следующей точки останова.
GetService — единственный. Ток еще бежит. Я обновлю ОП.
@infinitezero сообщает, что конструктором MyService был private. Это привело к сбою DI.
Вы можете создать помощник следующим образом:
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
}
затем вызывайте его там, где вам нужно
public partial class MyContentView : ContentView
{
public MyContentView()
{
InitializeComponent();
BindingContext = AppServiceProvider.GetService<MyContentViewModel>();;
}
}
Надеюсь, поможет
Есть ли документ или сообщение в блоге, на которое вы можете сослаться, демонстрирующее этот подход?
конечно, я получил это из сообщения от Дэвида Ортинау. Вот репо от него, использующего его. github.com/davidortinau/WeatherTwentyOne/blob/main/src/…
Где ваш звонок в GetService?