在 Xamarin Forms 中使用 Prism 重新实例化单例
Re-instantiate a singleton with Prism in Xamarin Forms
如何在 Xamarin Forms 中使用 Prism/DryIoC 处理和重新实例化单例?
我正在使用 Azure 移动应用程序处理离线数据。偶尔需要删除本地的sqlite数据库,重新初始化。不幸的是,MobileServiceClient 偶尔会保持数据库连接打开,并且没有公开的方法来关闭它。建议的解决方案 (https://github.com/Azure/azure-mobile-apps-net-client/issues/379) 是处理 MobileServiceClient。唯一的问题是在 DryIoC 中注册为单例。
我对 DryIoC 或 Prism 和 Forms 并不太熟悉……但就我的生活而言,我看不出有什么办法可以做到这一点。
我确实制定了一个几乎可行的非常复杂的方案。
在我的 ViewModel 方法中,当我需要释放数据库时,我触发了一个事件 -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(false);
然后在 App.xaml.cs 中,我像这样连接了一个监听器和一个处理程序 -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Subscribe(OnRegisterDatabaseEventPublished);
private void OnRegisterDatabaseEventPublished()
{
Container.GetContainer().Unregister<IAppMobileClient>();
Container.GetContainer().Unregister<IMobileServiceClient>();
Container.GetContainer().Register<IMobileServiceClient, AppMobileClient>(new SingletonReuse());
Container.GetContainer().Register<IAppMobileClient, AppMobileClient>(new SingletonReuse());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
最后,回到 ViewModel 构造函数,我有一个最终侦听器来处理从 App.xaml 返回的事件并完成处理。
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Subscribe(OnRegisterDatabaseCompletedEventPublished);
所以令人惊奇的是这有效。数据库能够被删除,一切都很好。但后来我导航到另一个页面和 BOOM。 DryIoC 表示无法为该页面连接 ViewModel。我假设 unregister/register 为所有注入增加了 DryIoC...那么我怎样才能完成需要做的事情呢?
最终解决方案
非常感谢 dadhi 抽出时间提供帮助。你肯定是一个 class 演员,我现在正在考虑在其他地方使用 DryIoC。
对于遇到此问题的任何人,我将在下面发布最终解决方案。为了避免混淆,我会尽可能详细。
首先,在我的 App.xaml.cs 中,我添加了一个注册数据库的方法。
public void RegisterDatabase(IContainer container)
{
container.RegisterMany<AppMobileClient>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true),
ifAlreadyRegistered: IfAlreadyRegistered.Replace,
serviceTypeCondition: type =>
type == typeof(IMobileServiceClient) || type == typeof(IAppMobileClient));
}
我只是在 RegisterTypes 中添加对该方法的调用,而不是直接在其中注册类型。
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.GetContainer().Rules.WithoutEagerCachingSingletonForFasterAccess();
...
RegisterDatabase(containerRegistry.GetContainer());
...
}
另请注意根据 dadhi 添加的预缓存规则。
稍后当我需要在 ViewModel 中释放数据库时...我通过重置本地 db 变量并将事件发送到 App.xaml.cs
来开始工作
_client = null;
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(true);
在App.xaml.cs中,我已经订阅了该事件并将其绑定到以下方法。
private void OnRegisterDatabaseEventPublished()
{
RegisterDatabase(Container.GetContainer());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
这里我只是再次调用了RegisterMany,和应用启动时完全一样。无需注销任何东西。使用设置和 ifAlreadyRegistered 参数(谢谢,dadhi!),DryIoC 允许替换对象。然后我向 VM 返回一个事件,让它知道数据库已被释放。
最后,回到 ViewModel,我正在监听已完成的事件。该事件的处理程序像这样更新对象的本地副本。
_client = ((PrismApplication)App.Current).Container.Resolve<IAppMobileClient>();
然后我可以根据需要使用新对象。这是关键。没有将上面的 _client 设置为 null 并在此处再次解析它,实际上我最终得到了对象的 2 个副本并且对方法的调用被命中了 2 倍。
希望这对希望发布其 Azure 移动应用程序数据库的其他人有所帮助!
我不确定 XF 是如何处理这些事情的。
但在 DryIoc 中,为了完全删除或替换服务,需要在 setup: Setup.With(asResolutionCall: true)
中注册。阅读此处了解更多详情:https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache#markdown-header-unregister-and-resolution-cache
更新
这里有两个选项和注意事项在 pure DryIoc 中有效,但可能不适用于 XF。但它可能有助于解决问题。
public class Foo
{
public IBar Bar { get; private set; }
public Foo(IBar bar) { Bar = bar; }
}
public interface IBar {}
public class Bar : IBar {}
public class Bar2 : IBar { }
[Test]
public void Replace_singleton_dependency_with_asResolutionCall()
{
var c = new Container(rules => rules.WithoutEagerCachingSingletonForFasterAccess());
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.Register<IBar, Bar>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true)); // required
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.Register<IBar, Bar2>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true), // required
ifAlreadyRegistered: IfAlreadyRegistered.Replace); // required
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}
[Test]
public void Replace_singleton_dependency_with_UseInstance()
{
var c = new Container();
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.UseInstance<IBar>(new Bar());
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.UseInstance<IBar>(new Bar2());
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}
如何在 Xamarin Forms 中使用 Prism/DryIoC 处理和重新实例化单例?
我正在使用 Azure 移动应用程序处理离线数据。偶尔需要删除本地的sqlite数据库,重新初始化。不幸的是,MobileServiceClient 偶尔会保持数据库连接打开,并且没有公开的方法来关闭它。建议的解决方案 (https://github.com/Azure/azure-mobile-apps-net-client/issues/379) 是处理 MobileServiceClient。唯一的问题是在 DryIoC 中注册为单例。
我对 DryIoC 或 Prism 和 Forms 并不太熟悉……但就我的生活而言,我看不出有什么办法可以做到这一点。
我确实制定了一个几乎可行的非常复杂的方案。
在我的 ViewModel 方法中,当我需要释放数据库时,我触发了一个事件 -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(false);
然后在 App.xaml.cs 中,我像这样连接了一个监听器和一个处理程序 -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Subscribe(OnRegisterDatabaseEventPublished);
private void OnRegisterDatabaseEventPublished()
{
Container.GetContainer().Unregister<IAppMobileClient>();
Container.GetContainer().Unregister<IMobileServiceClient>();
Container.GetContainer().Register<IMobileServiceClient, AppMobileClient>(new SingletonReuse());
Container.GetContainer().Register<IAppMobileClient, AppMobileClient>(new SingletonReuse());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
最后,回到 ViewModel 构造函数,我有一个最终侦听器来处理从 App.xaml 返回的事件并完成处理。
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Subscribe(OnRegisterDatabaseCompletedEventPublished);
所以令人惊奇的是这有效。数据库能够被删除,一切都很好。但后来我导航到另一个页面和 BOOM。 DryIoC 表示无法为该页面连接 ViewModel。我假设 unregister/register 为所有注入增加了 DryIoC...那么我怎样才能完成需要做的事情呢?
最终解决方案
非常感谢 dadhi 抽出时间提供帮助。你肯定是一个 class 演员,我现在正在考虑在其他地方使用 DryIoC。
对于遇到此问题的任何人,我将在下面发布最终解决方案。为了避免混淆,我会尽可能详细。
首先,在我的 App.xaml.cs 中,我添加了一个注册数据库的方法。
public void RegisterDatabase(IContainer container)
{
container.RegisterMany<AppMobileClient>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true),
ifAlreadyRegistered: IfAlreadyRegistered.Replace,
serviceTypeCondition: type =>
type == typeof(IMobileServiceClient) || type == typeof(IAppMobileClient));
}
我只是在 RegisterTypes 中添加对该方法的调用,而不是直接在其中注册类型。
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.GetContainer().Rules.WithoutEagerCachingSingletonForFasterAccess();
...
RegisterDatabase(containerRegistry.GetContainer());
...
}
另请注意根据 dadhi 添加的预缓存规则。
稍后当我需要在 ViewModel 中释放数据库时...我通过重置本地 db 变量并将事件发送到 App.xaml.cs
来开始工作_client = null;
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(true);
在App.xaml.cs中,我已经订阅了该事件并将其绑定到以下方法。
private void OnRegisterDatabaseEventPublished()
{
RegisterDatabase(Container.GetContainer());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
这里我只是再次调用了RegisterMany,和应用启动时完全一样。无需注销任何东西。使用设置和 ifAlreadyRegistered 参数(谢谢,dadhi!),DryIoC 允许替换对象。然后我向 VM 返回一个事件,让它知道数据库已被释放。
最后,回到 ViewModel,我正在监听已完成的事件。该事件的处理程序像这样更新对象的本地副本。
_client = ((PrismApplication)App.Current).Container.Resolve<IAppMobileClient>();
然后我可以根据需要使用新对象。这是关键。没有将上面的 _client 设置为 null 并在此处再次解析它,实际上我最终得到了对象的 2 个副本并且对方法的调用被命中了 2 倍。
希望这对希望发布其 Azure 移动应用程序数据库的其他人有所帮助!
我不确定 XF 是如何处理这些事情的。
但在 DryIoc 中,为了完全删除或替换服务,需要在 setup: Setup.With(asResolutionCall: true)
中注册。阅读此处了解更多详情:https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache#markdown-header-unregister-and-resolution-cache
更新
这里有两个选项和注意事项在 pure DryIoc 中有效,但可能不适用于 XF。但它可能有助于解决问题。
public class Foo
{
public IBar Bar { get; private set; }
public Foo(IBar bar) { Bar = bar; }
}
public interface IBar {}
public class Bar : IBar {}
public class Bar2 : IBar { }
[Test]
public void Replace_singleton_dependency_with_asResolutionCall()
{
var c = new Container(rules => rules.WithoutEagerCachingSingletonForFasterAccess());
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.Register<IBar, Bar>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true)); // required
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.Register<IBar, Bar2>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true), // required
ifAlreadyRegistered: IfAlreadyRegistered.Replace); // required
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}
[Test]
public void Replace_singleton_dependency_with_UseInstance()
{
var c = new Container();
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.UseInstance<IBar>(new Bar());
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.UseInstance<IBar>(new Bar2());
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}