Использование форм xamarin с IServiceProvider

Я изучал "Внедрение зависимостей" на формах ксамарина и нашел некоторые концепции, которые используют что-то вроде ContainerBuilder, Решения, найденные в Интернете, такие как это, расскажут о том, как вы можете настроить DI и внедрить их в свои модели представлений. Однако лично я не нашел ни эту, ни целую концепцию моделей представлений и связывания очень аккуратной по нескольким причинам. Я бы предпочел создавать сервисы, которые могут быть повторно использованы бизнес-логикой, которая, кажется, делает код намного чище. Я чувствовал, что реализация IServiceProvider приведет к гораздо более чистой реализации. Я планировал внедрить поставщика услуг примерно так:

IServiceProvider Provider = new ServiceCollection()
                            .AddSingleton<OtherClass>()
                            .AddSingleton<MyClass>()
                            .BuildServiceProvider();

Во-первых, я не уверен, почему нет таких примеров ксамарина. Поэтому я не уверен, что с этим направлением что-то не так. Я смотрел в ServiceCollection учебный класс. Пакет, из которого он Microsoft.Extensions.DependencyInjection, не имеет "aspnetcore" в своем названии. У этого, однако, есть его владелец как "aspnet". Я не совсем уверен, если ServiceCollection предназначен только для веб-приложений или имеет смысл использовать его для мобильных приложений.

Безопасно ли использовать IServiceProvider с ServiceCollection пока я использую все синглтоны? Есть ли какие-либо проблемы (с точки зрения производительности или оперативной памяти), я пропускаю?

Обновить

После комментариев от Nkosi я еще раз взглянул на ссылку и заметил несколько вещей:

  1. Ссылка на документацию датирована примерно в то же время Microsoft.Extensions.DependencyInjection был еще в бета-версии
  2. Все пункты в списке под "несколькими преимуществами использования контейнера внедрения зависимостей" в документации также относятся к DependencyInjection насколько я вижу.
  3. Autofac процесс, кажется, вращается вокруг ViewModels, которые я пытаюсь избежать.

Обновление 2

Мне удалось получить DI непосредственно в коде страниц с помощью функции навигации примерно так:

public static async Task<TPage> NavigateAsync<TPage>()
    where TPage : Page
{
    var scope = Provider.CreateScope();
    var scopeProvider = scope.ServiceProvider;
    var page = scopeProvider.GetService<TPage>();
    if (navigation != null) await navigation.PushAsync(page);
    return page;
}

0 ответов

Эта реализация использует Splat и некоторые вспомогательные классы / классы-оболочки для удобного доступа к контейнеру.

Способ регистрации служб немного подробен, но он может охватывать все случаи использования, с которыми я сталкивался до сих пор; Да и жизненный цикл можно легко изменить, например, переключившись на ленивое создание сервиса.

Просто используйте класс ServiceProvider для извлечения любых экземпляров из контейнера IoC в любом месте вашего кода.

Регистрация ваших услуг

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
        SetupBootstrapper(Locator.CurrentMutable);
        MainPage = new MainPage();
    }

    private void SetupBootstrapper(IMutableDependencyResolver resolver)
    {
        resolver.RegisterConstant(new Service(), typeof(IService));
        resolver.RegisterLazySingleton(() => new LazyService(), typeof(ILazyService));
        resolver.RegisterLazySingleton(() => new LazyServiceWithDI(
            ServiceProvider.Get<IService>()), typeof(ILazyServiceWithDI));
        // and so on ....
    }

Использование ServiceProvider

// get a new service instance with every call
var brandNewService = ServiceProvider.Get<IService>();

// get a deferred created singleton
var sameOldService = ServiceProvider.Get<ILazyService>();

// get a service which uses DI in its contructor
var another service = ServiceProvider.Get<ILazyServiceWithDI>();

Внедрение ServiceProvider

public static class ServiceProvider
{
    public static T Get<T>(string contract = null)
    {
        T service = Locator.Current.GetService<T>(contract);
        if (service == null) throw new Exception($"IoC returned null for type '{typeof(T).Name}'.");
        return service;
    }

    public static IEnumerable<T> GetAll<T>(string contract = null)
    {
        bool IsEmpty(IEnumerable<T> collection)
        {
            return collection is null || !collection.Any();
        }

        IEnumerable<T> services = Locator.Current.GetServices<T>(contract).ToList();
        if (IsEmpty(services)) throw new Exception($"IoC returned null or empty collection for type '{typeof(T).Name}'.");

        return services;
    }
}

Вот мой файл csproj. Ничего особенного, единственный пакет nuget, который я добавил, был Spat

Общий проект csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DebugType>portable</DebugType>
    <DebugSymbols>true</DebugSymbols>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Splat" Version="9.3.11" />
    <PackageReference Include="Xamarin.Forms" Version="4.3.0.908675" />
    <PackageReference Include="Xamarin.Essentials" Version="1.3.1" />
  </ItemGroup>
</Project>

Я знаю, что вопрос был задан 2 года назад, но у меня может быть решение, которое соответствует тому, о чем вы просите.

В последние несколько месяцев я работал над приложениями, использующими Xamarin и WPF, и я использовал Microsoft.Extensions.DependencyInjectionпакет для добавления инъекции зависимостей конструктора в мои модели представления, точно так же, как контроллер ASP.NET. Это означает, что у меня могло быть что-то вроде:

public class MainViewModel : ViewModelBase
{
    private readonly INavigationService _navigationService;
    private readonly ILocalDatabase _database;

    public MainViewModel(INavigationService navigationService, ILocalDatabase database)
    {
        _navigationService = navigationService;
        _database = database;
    }
}

Для реализации такого процесса я использую IServiceCollection добавить услуги и IServiceProvider для получения зарегистрированных услуг.

Важно помнить, что IServiceCollection- это контейнер, в котором вы будете регистрировать свои зависимости. Тогда при построении этого контейнера вы получитеIServiceProvider что позволит вам получить услугу.

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

Базовая реализация

В этом примере показано, как внедрить зависимости в страницу Xamarin. Процесс остается таким же для любого другого класса. (ViewModels или другие классы)

Создайте простой класс с именем Bootstrapper в вашем проекте и инициализировать IServiceCollection а также IServiceProvider частные поля.

public class Bootstrapper
{
    private readonly Application _app;
    private IServiceCollection _services;
    private IServiceProvider _serviceProvider;

    public Bootstrapper(Application app)
    {
        _app = app;
    }

    public void Start()
    {
        ConfigureServices();
    }

    private void ConfigureServices()
    {
        _services = new ServiceCollection();

        // TODO: add services here

        _serviceProvider = _services.BuildServiceProvider();
    }
}

Здесь в ConfigureServices() method we just create a new ServiceCollection where we are going to add our services. (See https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=dotnet-plat-ext-3.1) Once our services have been added, we build the service provider that will allow us to retrieve the previously registered services.

Then in your App class constructor, create a new Bootstrapper instance and call the start method to initialize the application.

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        var bootstrapper = new Bootstrapper(this);
        bootstrapper.Start();
    }
    ...
}

With this piece of code, you have setup your service container, but we still need to initialize the MainPage of the application. Go back to the bootstrapper's Start() method and create a new instance of the wanted main page.

public class Bootstrapper
{
    ...

    public void Start()
    {
        ConfigureServices();

        // Real magic happens here
        var mainPageInstance = ActivatorUtilities.CreateInstance<MainPage>(_serviceProvider);

        _app.MainPage = new NavigationPage(mainPageInstance);
    }
}

Here we use the ActivatorUtilities.CreateInstance<TInstance>() method to create a new MainPage instance. We give the _serviceProvider as parameter, because the ActivatorUtilities.CreateInstance() method will take care of creating your instance and inject the required services into your object.

Note that this is what ASP.NET Core using to instanciate the controllers with contructor dependency injection.

To test this, create a simple service and try to inject it into your MainPage contructor:

public interface IMySimpleService
{
    void WriteMessage(string message);
}

public class MySimpleService : IMySimpleService
{
    public void WriteMessage(string message)
    {
        Debug.WriteLine(message);
    }
}

Then register it inside the ConfigureServices() method of the Bootstrapper class:

private void ConfigureServices()
{
    _services = new ServiceCollection();

    _services.AddSingleton<IMySimpleService, MySimpleService>();

    _serviceProvider = _services.BuildServiceProvider();
}

And finally, go to your MainPage.xaml.cs, inject the IMySimpleService and call the WriteMessage() method.

public partial class MainPage : ContentPage
{
    public MainPage(IMySimpleService mySimpleService)
    {
        mySimpleService.WriteMessage("Hello world!");
    }
}

There you go, you have successfully registered a service and injected it into your page.

The real magic with constructor injection really occurs using the ActivatorUtilities.CreateInstance<T>() method by passing a service provider. The method will actually check the parameters of your constructor and try to resolve the dependencies by trying to get them from the IServiceProvider you gave him.

Bonus: Register platform specific services

Это хорошо, правда? Вы можете внедрять сервисы в любые классы благодаряActivatorUtilities.CreateInstance<T>() метод, но иногда вам также необходимо зарегистрировать некоторые службы для конкретной платформы (Android или iOS).

С помощью предыдущего метода невозможно зарегистрировать службы для конкретной платформы, потому что IServiceCollection инициализируется в Bootstrapperкласс. Не беспокойтесь, обходной путь действительно прост.

Вам просто нужно извлечь IServiceCollectionинициализация специфическим для платформы кодом. Просто инициализируйте коллекцию сервисов наMainActivity.cs вашего Android-проекта и в AppDelegate вашего проекта iOS и передайте его своему App класс, который направит его в Bootstrapper:

MainActivity.cs (Android)

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        ...

        var serviceCollection = new ServiceCollection();

        // TODO: add platform specific services here.

        var application = new App(serviceCollection);

        LoadApplication(application);
    }
    ...
}

AppDelegate.cs (iOS)

public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        global::Xamarin.Forms.Forms.Init();

        var serviceCollection = new ServiceCollection();

        // TODO: add platform specific services here.

        var application = new App(serviceCollection);

        LoadApplication(application);

        return base.FinishedLaunching(app, options);
    }
}

App.xaml.cs (общий)

public partial class App : Application
{
    public App(IServiceCollection services)
    {
        InitializeComponent();

        var bootstrapper = new Bootstrapper(this, services);
        bootstrapper.Start();
    }
    ...
}

Bootstrapper.cs (Обычный)

public class Bootstrapper
{
    private readonly Application _app;
    private readonly IServiceCollection _services;
    private IServiceProvider _serviceProvider;

    public Bootstrapper(Application app, IServiceCollection services)
    {
        _app = app;
        _services = services;
    }

    public void Start()
    {
        ConfigureServices();

        var mainPageInstance = ActivatorUtilities.CreateInstance<MainPage>(_serviceProvider);

        _app.MainPage = new NavigationPage(mainPageInstance);
    }

    private void ConfigureServices()
    {
        // TODO: add services here.
        _serviceCollection.AddSingleton<IMySimpleService, MySimpleService>();

        _serviceProvider = _services.BuildServiceProvider();
    }
}

И все, теперь вы можете регистрировать сервисы для конкретных платформ и легко внедрять интерфейс на свои страницы / просматривать модели / классы.

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