共用方式為


中的相依性解析 Xamarin.Forms

本文說明如何將相依性解析方法 Xamarin.Forms 插入至 ,讓應用程式的相依性插入容器能夠控制自定義轉譯器、效果和 DependencyService 實作的建立和存留期。

在使用 Model-View-ViewModel (MVVM) 模式的應用程式內容 Xamarin.Forms 中,相依性插入容器可用於註冊和解析檢視模型,以及註冊服務,並將其插入檢視模型中。 在檢視模型建立期間,容器會插入所需的任何相依性。 如果尚未建立這些相依性,容器會先建立並解析相依性。 如需相依性插入的詳細資訊,包括將相依性插入至檢視模型的範例,請參閱 相依性插入

在平台專案中控制型別的建立和存留期,通常是由 Xamarin.Forms執行,它會使用 Activator.CreateInstance 方法來建立自定義轉譯器、效果和 DependencyService 實作的實例。 不幸的是,這會限制開發人員對這些類型的建立和存留期的控制,以及插入相依性的能力。 藉由將相依性解析方法Xamarin.Forms插入至 ,以變更此行為,以控制將如何建立類型 – 應用程式相依性插入容器或 。Xamarin.Forms 不過,請注意,不需要將相依性解析方法 Xamarin.Forms插入 。 Xamarin.Forms 如果未插入相依性解析方法,將繼續在平台專案中建立和管理類型的存留期。

注意

雖然本文著重於將相依性解析方法 Xamarin.Forms 插入至 ,以使用相依性插入容器解析已註冊的型別,但您也可以插入使用 Factory 方法來解析已註冊的型別的相依性解析方法。

插入相依性解析方法

類別DependencyResolver可讓您使用 ResolveUsing 方法將相依性解析方法插入 。Xamarin.Forms 然後,當需要特定類型的實例時 Xamarin.Forms ,相依性解析方法會有機會提供 實例。 如果要求的型別傳 null 回相依性解析方法, Xamarin.Forms 請回復為嘗試使用 Activator.CreateInstance 方法建立類型實例本身。

下列範例示範如何使用 方法設定相依性解析方法 ResolveUsing

using Autofac;
using Xamarin.Forms.Internals;
...

public partial class App : Application
{
    // IContainer and ContainerBuilder are provided by Autofac
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();

    public App()
    {
        ...
        DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) : null);
        ...
    }
    ...
}

在此範例中,相依性解析方法會設定為 Lambda 表達式,該運算式會使用 Autofac 相依性插入容器來解析已向容器註冊的任何類型。 否則會 null 傳回 ,這會導致 Xamarin.Forms 嘗試解析類型。

注意

相依性插入容器所使用的 API 專屬於容器。 本文中的程式代碼範例使用 Autofac 作為相依性插入容器,其提供 IContainerContainerBuilder 類型。 您也可以使用替代相依性插入容器,但會使用與這裡不同的 API。

請注意,在應用程式啟動期間不需要設定相依性解析方法。 您可以隨時設定它。 唯一的條件約束是 Xamarin.Forms ,在應用程式嘗試取用儲存在相依性插入容器中的類型時,需要知道相依性解析方法。 因此,如果應用程式在啟動期間所需的相依性插入容器中有服務,則相依性解析方法必須在應用程式的生命週期早期設定。 同樣地,如果相依性插入容器管理特定 Effect的建立和存留期, Xamarin.Forms 在嘗試建立使用該 Effect的檢視之前,必須先知道相依性解析方法。

警告

使用相依性插入容器註冊和解析類型具有效能成本,因為容器使用反映來建立每個類型,特別是針對應用程式中每個頁面導覽重建相依性時。 如果有許多或深度相依性,則建立的成本可能會大幅增加。

註冊類型

類型必須先向相依性插入容器註冊,才能透過相依性解析方法加以解析。 下列程式代碼範例顯示範例應用程式在 類別中針對 Autofac 容器公開的 App 註冊方法:

using Autofac;
using Autofac.Core;
...

public partial class App : Application
{
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();
    ...

    public static void RegisterType<T>() where T : class
    {
        builder.RegisterType<T>();
    }

    public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>().As<TInterface>();
    }

    public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where T : class
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        });
    }

    public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        }).As<TInterface>();
    }

    public static void BuildContainer()
    {
        container = builder.Build();
    }
    ...
}

當應用程式使用相依性解析方法來解析容器中的類型時,通常會從平臺專案執行類型註冊。 這可讓平台專案註冊自定義轉譯器、效果和 DependencyService 實作的類型。

從平臺項目進行型別註冊之後, IContainer 必須建置 物件,這可藉由呼叫 BuildContainer 方法來完成。 這個方法會在 實例上ContainerBuilder叫用 Autofac Build 的 方法,此實例會建置新的相依性插入容器,其中包含已進行的註冊。

在後續各節中,實 Logger 作 介面的類別 ILogger 會插入類別建構函式中。 類別 Logger 會使用 Debug.WriteLine 方法實作簡單的記錄功能,並用來示範如何將服務插入自定義轉譯器、效果和 DependencyService 實作中。

註冊自定義轉譯器

範例應用程式包含播放 Web 影片的頁面,其 XAML 來源會顯示在下列範例中:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:video="clr-namespace:FormsVideoLibrary"
             ...>
    <video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

檢視 VideoPlayer 是由 類別 VideoPlayerRenderer 在每個平台上實作,可提供播放影片的功能。 如需這些自定義轉譯器類別的詳細資訊,請參閱 實作視訊播放程式

在 iOS 和 通用 Windows 平台 (UWP),VideoPlayerRenderer類別具有下列建構函式,這需要自ILogger變數:

public VideoPlayerRenderer(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes iOS 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
    App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且直接 VideoPlayerRenderer 註冊類型時不會進行介面對應。 當使用者流覽至包含VideoPlayer檢視的頁面時,將會叫用相依性解析方法,以從相依性插入容器解析VideoPlayerRenderer類型,這也會解析類型並將類型VideoPlayerRenderer插入Logger建構函式中。

VideoPlayerRenderer Android 平臺上的建構函式稍微複雜一點,因為除了 自變數之外,還需要自Context變數ILogger

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

在這裡範例中 App.RegisterTypeWithParameters ,方法會 VideoPlayerRenderer 向相依性插入容器註冊 。 註冊方法可確保 MainActivity 實例會插入為 Context 自變數,並將 Logger 類型插入為 ILogger 自變數。

註冊效果

範例應用程式包含一個頁面,該頁面會使用觸控追蹤效果來拖曳 BoxView 頁面周圍的實例。 Effect使用下列程式代碼將 新增至 BoxView

var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

類別 TouchEffect 是由 RoutingEffect 類別在每個平台上 TouchEffect 實作的 PlatformEffect。 平台 TouchEffect 類別提供在頁面周圍拖曳 BoxView 的功能。 如需這些效果類別的詳細資訊,請參閱 從效果叫用事件。

在所有平臺上,類別 TouchEffect 都有下列建構函式,需要自 ILogger 變數:

public TouchEffect(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<TouchTracking.Droid.TouchEffect>();
    App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且直接 TouchEffect 註冊類型時不會進行介面對應。 當使用者流覽至包含BoxViewTouchEffect附加實例的頁面時,將會叫用相依性解析方法,以從相依性插入容器解析平臺TouchEffect類型,這也會解析類型,並將類型TouchEffect插入Logger建構函式中。

註冊 DependencyService 實作

範例應用程式包含一個頁面,使用 DependencyService 每個平臺上的實作,讓使用者從裝置的圖片庫挑選相片。 介面 IPhotoPicker 會定義實作所 DependencyService 實作的功能,如下列範例所示:

public interface IPhotoPicker
{
    Task<Stream> GetImageStreamAsync();
}

在每個平台專案中,類別會 PhotoPicker 使用平臺 API 實作 IPhotoPicker 介面。 如需這些相依性服務的詳細資訊,請參閱 從圖片庫挑選相片。

在 iOS 和 UWP 上 PhotoPicker ,類別具有下列建構函式,需要自 ILogger 變數:

public PhotoPicker(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes UWP 上的 方法:

void RegisterTypes()
{
    DIContainerDemo.App.RegisterType<ILogger, Logger>();
    DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
    DIContainerDemo.App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且類型 PhotoPicker 也會透過介面對應來註冊。

PhotoPicker Android 平臺上的建構函式稍微複雜一點,因為除了 自變數之外,還需要自Context變數ILogger

public PhotoPicker(Context context, ILogger logger)
{
    _context = context ?? throw new ArgumentNullException(nameof(context));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

在這裡範例中 App.RegisterTypeWithParameters ,方法會 PhotoPicker 向相依性插入容器註冊 。 註冊方法可確保 MainActivity 實例會插入為 Context 自變數,並將 Logger 類型插入為 ILogger 自變數。

當使用者瀏覽至相片挑選頁面並選擇選取相片時,會 OnSelectPhotoButtonClicked 執行處理程式:

async void OnSelectPhotoButtonClicked(object sender, EventArgs e)
{
    ...
    var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
    var stream = await photoPickerService.GetImageStreamAsync();
    if (stream != null)
    {
        image.Source = ImageSource.FromStream(() => stream);
    }
    ...
}

DependencyService.Resolve<T>叫用 方法時,將會叫用相依性解析方法,以從相依性插入容器解析PhotoPicker型別,這也會解析類型PhotoPicker並插入Logger建構函式中。

注意

Resolve<T>透過 解析應用程式相依性插入容器DependencyService中的型別時,必須使用 方法。