Как разрешить зависимости в .net MAUI ContentView?

Я уже пробовал это но мне не помогло. 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?

Jason 25.06.2023 00:17

@ Джейсон, нет звонка, чтобы получить услугу, потому что это не сработало. Поэтому я не воспроизвел это здесь.

infinitezero 25.06.2023 00:17

GetService — способ сделать это. Если у вас возникли проблемы с его использованием, вам необходимо предоставить подробную информацию

Jason 25.06.2023 00:21

1) "Я уже пробовал это": покажите точную строку с вашим типом и в каком методе какого класса вы его вызвали. 2) Есть ли у вашего конструктора App параметры? Если да, добавьте к вопросу эту строку объявления. 3) Если вы поставите точку останова в начале конструктора вашего ContentView, а другую в CreateMauiApp на return builder.Build();, вызовется ли Build до того, как будет построен ваш ContentView?

ToolmakerSteve 25.06.2023 00:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

ОРИГИНАЛЬНЫЙ ОТВЕТ

  • Вставьте IServiceProvider в конструктор 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

infinitezero 25.06.2023 03:04

Ага. Вот что бывает, если не делать все точно правильно. Удалите все, что можно, чтобы сделать его максимально простым, и обновите рассматриваемый код, чтобы он соответствовал тому, что вы сделали. В том числе и то, что не сработало. Убедитесь, что причина в том, что вы думаете: удаляйте код до тех пор, пока у вас не будет что-то, что работает (даже если оно не делает то, что вы хотите) - нужно точно знать, какая строка его прерывает. Несмотря на то, что я сталкивался с этими проблемами раньше, мне потребовалось несколько правок, прежде чем он заработал правильно. Может иметь больший успех, используя ответ Марка напрямую.

ToolmakerSteve 25.06.2023 03:10

Я обновил все в ОП. Кажется, это линия Марка ломает его.

infinitezero 25.06.2023 03:26

Ах. Я тестировал на Windows. Судя по вашему комментарию к коду Марка, вы тестируете на Android? Вы поставили точку останова на этой строке, перешагнули через нее? Он возвращает ноль? Или это исключение в вызывающем абоненте, когда он пытается это использовать? Чтобы увидеть, разделите вызывающую строку на две части: одну для получения Current, другую для вызова GetService. Какая линия дает сбой?

ToolmakerSteve 25.06.2023 03:31

Я переключился на Windows, чтобы проверить, и он также не работает в том же фрагменте кода (очевидно, не в блоке Android). Сразу после прохождения этой точки останова код вылетает до достижения следующей точки останова.

infinitezero 25.06.2023 03:33

GetService — единственный. Ток еще бежит. Я обновлю ОП.

infinitezero 25.06.2023 03:38

@infinitezero сообщает, что конструктором MyService был private. Это привело к сбою DI.

ToolmakerSteve 25.06.2023 03:55

Вы можете создать помощник следующим образом:

    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>();;
    }
}

Надеюсь, поможет

Есть ли документ или сообщение в блоге, на которое вы можете сослаться, демонстрирующее этот подход?

ToolmakerSteve 25.06.2023 02:50

конечно, я получил это из сообщения от Дэвида Ортинау. Вот репо от него, использующего его. github.com/davidortinau/WeatherTwentyOne/blob/main/src/…

Marc Fabregat 26.06.2023 20:25

Другие вопросы по теме

Не удается получить строку подключения в файле приложения-функции .Net7.0 Azure program.cs
Как правильно избавиться от экземпляров, которые хранятся в зарегистрированной службе C#, которая не реализует IDisposable?
Унаследованное поле с автоматическим связыванием не инициализируется к моменту выполнения кода конструктора. Почему и как решить эту проблему?
Как сделать шаблон проектирования «строитель» и внедрение зависимостей Spring совместимыми друг с другом?
Является ли хорошей практикой добавление необязательных аргументов в лямбда-функцию AWS?
Объявите внедрение зависимостей, когда конструктор принимает параметр
C# Два одноэлементных экземпляра GraphServiceClient
Сервер Blazor — как долго длится временное время службы
Как решить ошибку внедрения зависимостей в DbContext?
Blazor Server.NET7 — Внедрение зависимостей — Entity Framework Core — Как получить новый экземпляр для определенного действия