Xamarin.Forms での依存関係の解決

サンプルのダウンロードサンプルのダウンロード

この記事では、アプリケーションの依存関係挿入コンテナーがカスタム レンダラー、効果、DependencyService 実装の作成と有効期間を制御できるように、依存関係解決メソッド Xamarin.Forms を に挿入する方法について説明します。 この記事のコード例は、コンテナーを使用した 依存関係の解決 のサンプルから取得しています。

Model-View-ViewModel (MVVM) パターンを使用するアプリケーションの Xamarin.Forms コンテキストでは、依存関係挿入コンテナーを使用して、ビュー モデルの登録と解決、およびサービスの登録とビュー モデルへの挿入を行うことができます。 ビュー モデルの作成時に、コンテナーは必要な依存関係を挿入します。 これらの依存関係が作成されていない場合、コンテナーは最初に依存関係を作成して解決します。 依存関係の挿入の詳細 (ビュー モデルへの依存関係の挿入の例など) については、「 依存関係の挿入」を参照してください。

プラットフォーム プロジェクトでの型の作成と有効期間の制御は、従来、 メソッドをActivator.CreateInstance使用してXamarin.Formsカスタム レンダラー、効果、実装DependencyServiceのインスタンスを作成することで実行されます。 残念ながら、これにより、開発者はこれらの型の作成と有効期間、およびそれらに依存関係を挿入する機能を制限します。 この動作を変更するには、型の作成方法を制御する依存関係解決メソッドを に Xamarin.Forms 挿入します(アプリケーションの依存関係挿入コンテナーまたは によって Xamarin.Forms)。 ただし、依存関係解決メソッドを に挿入する必要がないことに Xamarin.Forms注意してください。 Xamarin.Forms は、依存関係解決メソッドが挿入されていない場合、プラットフォーム プロジェクトで型の有効期間を作成および管理し続けます。

注意

この記事では、依存関係挿入コンテナーを使用して登録済み型を解決する に依存関係解決メソッド Xamarin.Forms を挿入することに重点を置いていますが、ファクトリ メソッドを使用して登録済み型を解決する依存関係解決メソッドを挿入することもできます。 詳細については、「 Factory メソッドを使用した依存関係の解決 」サンプルを参照してください。

依存関係解決メソッドの挿入

クラスは DependencyResolver 、 メソッドを使用して に依存関係解決メソッド Xamarin.Formsを挿入する機能を ResolveUsing 提供します。 その後、特定の型のインスタンスが必要な場合 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);
        ...
    }
    ...
}

この例では、依存関係解決メソッドは、Autofac 依存関係挿入コンテナーを使用して、コンテナーに登録されている型を解決するラムダ式に設定されています。 それ以外の場合は が null 返され、その結果 Xamarin.Forms 、型の解決が試みられます。

注意

依存関係挿入コンテナーによって使用される API は、コンテナーに固有です。 この記事のコード例では、 型と ContainerBuilder 型を提供する依存関係挿入コンテナーとして Autofac をIContainer使用します。 別の依存関係挿入コンテナーも同様に使用できますが、ここに示されている API とは異なる API を使用します。

アプリケーションの起動時に依存関係解決方法を設定する必要がないことに注意してください。 いつでも設定できます。 唯一の制約は、アプリケーションが Xamarin.Forms 依存関係挿入コンテナーに格納されている型の使用を試みるまでに、依存関係解決メソッドについて知る必要があることです。 したがって、起動時にアプリケーションが必要とする依存関係挿入コンテナーにサービスがある場合、依存関係解決方法は、アプリケーションのライフサイクルの早い段階で設定する必要があります。 同様に、依存関係挿入コンテナーが特定EffectXamarin.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 呼び出すことによって実現されます。 このメソッドは、 インスタンスで Autofac の Build メソッドを ContainerBuilder 呼び出します。これにより、作成された登録を含む新しい依存関係挿入コンテナーがビルドされます。

次のセクションでは、インターフェイスを Logger 実装 ILogger するクラスがクラス コンストラクターに挿入されます。 クラスは Logger 、 メソッドを使用して単純なログ機能を Debug.WriteLine 実装し、カスタム レンダラー、効果、実装 DependencyService にサービスを挿入する方法を示すために使用されます。

カスタム レンダラーの登録

サンプル アプリケーションには、次の例に示す XAML ソースを含む Web ビデオを再生するページが含まれています。

<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 プラットフォーム上のコンストラクターは、引数に加えてILogger引数をContext必要とするため、少し複雑になります。

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 、型はインターフェイス マッピングなしで直接登録されます。 アタッチされている インスタンスを含むBoxViewページにユーザーがTouchEffect移動すると、依存関係解決メソッドが呼び出され、依存関係挿入コンテナーからプラットフォーム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()) を読み込む前に、プラットフォームによって呼び出されます。 次の例は、UWP の RegisterTypes メソッドを示しています。

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

この例では、 Logger 具象型はインターフェイス型に対するマッピングを介して登録され PhotoPicker 、型もインターフェイス マッピングを介して登録されます。

PhotoPicker Android プラットフォーム上のコンストラクターは、引数に加えてILogger引数をContext必要とするため、少し複雑になります。

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必要があります。