XamarinForms 系列課程

特別說明

2018/01/13

Prism for Xamarin.Forms 7.0 的相依性服務注入修正與使用說明

01/12 Prism for Xamarin.Forms 推出了7.0 正式版本,並且Prism Template Pack 也同步升級到2.0.8 版本。在此次的更新過程中,增加了許多相當好用的功能,不過,也同步中止了之前6.3 版本的好用功能。
在這裡有個相當重大的變更,那就是在以往我們若需要相依性注入服務,將原生專案內的實作介面類別,想要透過Prism的建構式註入到檢視模型ViewModel內,我們僅需要在實作介面類別中,使用Xamarin.Forms.Dependency屬性進行宣告,就可以直接使用,不過,在Prism for Xamarin.Forms 7.0這項福利已經無法使用了。
若我們想要繼續使用這樣的功能,那該如何處理呢?Prism現在將不同的相依性注入服務進行抽象化處理,包裝到Prism.IoC命名空間類,因此,當我們想要進行解析或者註入一個新的物件,可以使用IContainerProvider介面,而當我們要進行註冊一個要注入的介面與型別的時候,可以使用IContainerRegistry介面。
在這以,我們在Xamarin.Forms 的.NET Standard 類別庫內,建立了一個介面
public interface IOSInfo
{
    string Info();
}
接著,就如以往,我們在原生Android 專案內,來實作出這個介面,並且使用了Xamarin.Forms.Dependency 這個屬性來宣告這個類別是可以用於Xamarin.Forms 的相依性注入服務中。
[assembly: Xamarin.Forms.Dependency(typeof(BlankApp2.Droid.OSInfo))]
namespace BlankApp2.Droid
{
    public class OSInfo : IOSInfo
    {
        public string Info()
        {
            return "Android";
        }
    }
}
現在,我們在檢視頁面ViewModel 內的建構函式,想要自動注入IOSInfor 這個介面的實作物件,如下列程式碼。
private IOSInfo _osInfo;
public MainPageViewModel(INavigationService navigationService,
    IOSInfo osInfo)
{
    _navigationService = navigationService;
    _osInfo = osInfo;

    Title = _osInfo.Info();

}
很不幸的,您將會得到底下的例外異常錯誤訊息:
Unhandled Exception:

Unity.Exceptions.ResolutionFailedException: Resolution of the dependency failed, type = 'BlankApp2.ViewModels.MainPageViewModel', name = '(none)'.
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The current type, BlankApp2.IOSInfo, is an interface and cannot be constructed. Are you missing a type mapping?
-----------------------------------------------
At the time of the exception, the container was: 
  Resolving BlankApp2.ViewModels.MainPageViewModel,(none)
  Resolving parameter 'osInfo' of constructor BlankApp2.ViewModels.MainPageViewModel(Prism.Navigation.INavigationService navigationService, BlankApp2.IOSInfo osInfo)
    Resolving BlankApp2.IOSInfo,(none)
因此,想要能夠在Xamarin.Forms專案內,注入原生專案的實作類別物件,您需要自行透過IPlatformInitializer介面來實作出public void RegisterTypes(IContainerRegistry container)方法,在這個方法中有個參數,IContainerRegistry container,我們可以透過這個container來進行註冊行為,如同底下程式碼
public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainerRegistry container)
    {
        // Register any platform specific implementations
        container.Register<IOSInfo, OSInfo>();
    }
}
當然,每個原生專案內,您都要自己進行註冊動作,如此,您就可以在Xamarin.Forms 專案內,使用建構函式來注入這些原生專案內的實作類別物件。
若您想要在Xamarin.Forms專案內,自行註冊介面與實作類別,可以在App.xaml.cs檔案內,找到RegisterTypes這個方法,在這個方法內,使用containerRegistry參數就可以進行註冊的動作。
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<NavigationPage>();
    containerRegistry.RegisterForNavigation<MainPage>();
    containerRegistry.Register<IDITest, DITest>();
}
若想要直接操作Unity 這個相依性服務注入容器,透過containerRegistry 這個參數的GetContainer 方法,就可以得到IUnityContainer 的物件;在底下的程式碼中,我們將會顯示出所有已經註冊,且可以注入的介面與實作物件清單。
var foo = containerRegistry.GetContainer();
foreach (var item in foo.Registrations)
{
    System.Console.WriteLine($"{item.RegisteredType.Name} -> {item.MappedToType.Name}");
}
01-13 19:05:08.541 I/mono-stdout( 5976): IUnityContainer -> IUnityContainer
01-13 19:05:08.542 I/mono-stdout( 5976): IContainerExtension -> IContainerExtension
01-13 19:05:08.542 I/mono-stdout( 5976): ILoggerFacade -> EmptyLogger
01-13 19:05:08.542 I/mono-stdout( 5976): IApplicationProvider -> ApplicationProvider
01-13 19:05:08.542 I/mono-stdout( 5976): IApplicationStore -> ApplicationStore
01-13 19:05:08.542 I/mono-stdout( 5976): IEventAggregator -> EventAggregator
01-13 19:05:08.542 I/mono-stdout( 5976): IDependencyService -> DependencyService
01-13 19:05:08.542 I/mono-stdout( 5976): IPageDialogService -> PageDialogService
01-13 19:05:08.542 I/mono-stdout( 5976): IDeviceService -> DeviceService
01-13 19:05:08.542 I/mono-stdout( 5976): IPageBehaviorFactory -> PageBehaviorFactory
01-13 19:05:08.542 I/mono-stdout( 5976): IModuleCatalog -> ModuleCatalog
01-13 19:05:08.542 I/mono-stdout( 5976): IModuleManager -> ModuleManager
01-13 19:05:08.542 I/mono-stdout( 5976): IModuleInitializer -> ModuleInitializer
01-13 19:05:08.543 I/mono-stdout( 5976): INavigationService -> PageNavigationService
01-13 19:05:08.543 I/mono-stdout( 5976): IOSInfo -> OSInfo
01-13 19:05:08.543 I/mono-stdout( 5976): Object -> NavigationPage
01-13 19:05:08.543 I/mono-stdout( 5976): Object -> MainPage
01-13 19:05:08.543 I/mono-stdout( 5976): IDITest -> DITest
最後,若想要在檢視模型類別或者任何地方,使用容器來取得實作物件,可以使用底下的程式碼來手動注入。
IContainerProvider fooContainer = (App.Current as Prism.Unity.PrismApplication).Container;
var fooOSInfo = fooContainer.Resolve<IDITest>();

沒有留言:

張貼留言