我研究了xamarin形式的“依赖注入”,发现了一些使用诸如ContainerBuilder
之类的概念。在线找到的解决方案,例如this,讨论如何设置DI并将其注入到视图模型中。但是,就个人而言,由于以下几个原因,我没有发现视图模型和绑定的整个概念或整洁概念。我宁愿创建可以被业务逻辑重用的服务,这似乎使代码更简洁。我觉得实现IServiceProvider
可以使实现更加简洁。我正计划实施这样的服务提供商:
IServiceProvider Provider = new ServiceCollection()
.AddSingleton<OtherClass>()
.AddSingleton<MyClass>()
.BuildServiceProvider();
首先,我不确定为什么没有xamarin的例子。因此,我不确定朝这个方向做任何事情。我看过ServiceCollection
类。它来自的软件包Microsoft.Extensions.DependencyInjection
的名称中没有“ aspnetcore”。但是,它的所有者为“ aspnet”。我不确定ServiceCollection
是仅用于Web应用程序还是将其用于移动应用程序是否有意义。
只要我使用所有单例,将IServiceProvider
与ServiceCollection
一起使用是否安全?我是否缺少任何关注(在性能或内存方面)?
更新
在Nkosi发表评论后,我再次查看了链接,并注意到了几件事:
Microsoft.Extensions.DependencyInjection
仍处于beta版DependencyInjection
。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 :(得分: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
的简单类,并将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()
方法中,我们只是创建一个新的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
:
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);
}
...
}
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);
}
}
public partial class App : Application
{
public App(IServiceCollection services)
{
InitializeComponent();
var bootstrapper = new Bootstrapper(this, services);
bootstrapper.Start();
}
...
}
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();
}
}
仅此而已,您现在可以注册特定于平台的服务,并轻松地将界面注入页面/视图模型/类中。