在IServiceProvider中使用xamarin表单

时间:2018-07-04 06:02:55

标签: dependency-injection xamarin.forms architecture service-provider

我研究了xamarin形式的“依赖注入”,发现了一些使用诸如ContainerBuilder之类的概念。在线找到的解决方案,例如this,讨论如何设置DI并将其注入到视图模型中。但是,就个人而言,由于以下几个原因,我没有发现视图模型和绑定的整个概念或整洁概念。我宁愿创建可以被业务逻辑重用的服务,这似乎使代码更简洁。我觉得实现IServiceProvider可以使实现更加简洁。我正计划实施这样的服务提供商:

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

首先,我不确定为什么没有xamarin的例子。因此,我不确定朝这个方向做任何事情。我看过ServiceCollection类。它来自的软件包Microsoft.Extensions.DependencyInjection的名称中没有“ aspnetcore”。但是,它的所有者为“ aspnet”。我不确定ServiceCollection是仅用于Web应用程序还是将其用于移动应用程序是否有意义。

只要我使用所有单例,将IServiceProviderServiceCollection一起使用是否安全?我是否缺少任何关注(在性能或内存方面)?

更新

在Nkosi发表评论后,我再次查看了链接,并注意到了几件事:

  1. 文档链接的日期大约是Microsoft.Extensions.DependencyInjection仍处于beta版
  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;
}

2 个答案:

答案 0 :(得分:4)

此实现使用 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 ....
    }

服务提供商的使用

// 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>

答案 1 :(得分:0)

我知道这个问题是在2年前提出的,但是我可能有一个可以满足您要求的解决方案。

在过去的几个月中,我一直在使用Xamarin和WPF开发应用程序,并且我使用了Microsoft.Extensions.DependencyInjection包向我的视图模型添加了构造函数依赖注入,就像ASP.NET Controller一样。这意味着我可能会有类似的内容:

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页面。对于其他任何类,该过程保持相同。 (ViewModel或其他类)

在您的项目中创建一个名为Bootstrapper的简单类,并将IServiceCollectionIServiceProvider私有字段初始化。

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()方法中,我们只是创建一个新的ServiceCollection,将在其中添加服务。 (请参见https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=dotnet-plat-ext-3.1) 添加我们的服务后,我们将建立服务提供商,使我们能够检索以前注册的服务。

然后在您的App类构造函数中,创建一个新的Bootstrapper实例并调用start方法来初始化应用程序。

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

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

使用这段代码,您已经设置了服务容器,但是我们仍然需要初始化应用程序的MainPage。返回引导程序的Start()方法,并创建所需主页的新实例。

public class Bootstrapper
{
    ...

    public void Start()
    {
        ConfigureServices();

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

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

在这里,我们使用ActivatorUtilities.CreateInstance<TInstance>()方法来创建新的MainPage实例。我们将_serviceProvider作为参数,因为ActivatorUtilities.CreateInstance()方法将负责创建您的实例并将所需的服务注入到您的对象中。

请注意,这是ASP.NET Core用于通过构造函数依赖项注入来实例化控制器的方法。

要对此进行测试,请创建一个简单的服务,然后尝试将其注入您的MainPage建设者中:

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

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

然后将其注册到ConfigureServices()类的Bootstrapper方法中:

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

    _services.AddSingleton<IMySimpleService, MySimpleService>();

    _serviceProvider = _services.BuildServiceProvider();
}

最后,转到您的MainPage.xaml.cs,注入IMySimpleService并调用WriteMessage()方法。

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

到此,您已经成功注册了一项服务并将其注入到页面中。

使用ActivatorUtilities.CreateInstance<T>()方法通过传递服务提供者确实可以实现构造函数注入。该方法实际上将检查构造函数的参数,并尝试通过尝试从您给他的IServiceProvider中获取依赖关系来解决依赖关系。

奖金:注册平台特定的服务

这很好吗?通过ActivatorUtilities.CreateInstance<T>()方法,您可以将服务注入到任何类中,但是有时您还需要注册一些特定于平台的服务(Android或iOS)。

使用先前的方法无法注册特定于平台的服务,因为IServiceCollection是在Bootstrapper类中初始化的。不用担心,解决方法非常简单。

您只需要将IServiceCollection初始化提取到特定于平台的代码。只需在Android项目的MainActivity.cs和iOS项目的AppDelegate上初始化服务集合,然后将其传递到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();
    }
}

仅此而已,您现在可以注册特定于平台的服务,并轻松地将界面注入页面/视图模型/类中。