かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Prism.FormsでAutofacを使ってみよう

Xamarin Studioベースで話を進めます。(Visual Studioでもだいたい同じになると思うけど)

まずForms Appを新規作成してPCLで作ります。

NuGetから以下のパッケージを追加します。

  • Prism.Autofac.Forms

Views名前空間を作って、そこにMainPage.xamlを作成します。XAMLはいつも通り、ViewModelLocatorを設定しておきます。 今回は、ViewModelにMessageプロパティがあることを前提に作って見ました。あとでViewModelも作ります。

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:mvvm="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
    mvvm:ViewModelLocator.AutowireViewModel="true"
    x:Class="PrismAutofac.Views.MainPage">
    <Label Text="{Binding Message}"
        HorizontalOptions="Center"
        VerticalOptions="Center" />
</ContentPage>

ViewModels名前空間を作って以下のようなViewModelも作ります。ここら辺は普通のPrismの世界ですね。

using System;
using Prism.Mvvm;

namespace PrismAutofac.ViewModels
{
    public class MainPageViewModel : BindableBase
    {
        public string Message => "Hello world powered by Prism.Autofac.Forms";
    }
}

そして、App.xaml.csを以下の内容に書き換えます。

AutofacはUnityと違って登録されてないクラスのインスタンスは作ってくれないので、Unityでは必要のなかったViewModelの登録を行っています。と言ってもAssemblyをスキャンしてViewModelを一括登録する感じなので楽チンですけどね。

using Prism.Autofac;
using Prism.Autofac.Forms;
using Autofac;
using Xamarin.Forms;
using PrismAutofac.Views;
using PrismAutofac.ViewModels;
using System.Reflection;

namespace PrismAutofac
{
    public partial class App : PrismApplication
    {
        protected override void OnInitialized()
        {
            InitializeComponent();

            this.NavigationService.NavigateAsync("MainPage");
        }

        protected override void RegisterTypes()
        {
            // ViewModelの登録
            var containerUpdater = new ContainerBuilder();
            containerUpdater
                .RegisterAssemblyTypes(typeof(App).GetTypeInfo().Assembly)
                .Where(x => x.IsInNamespace("PrismAutofac.ViewModels"))
                .Where(x => x.Name.EndsWith("ViewModel"))
                .AsSelf();
            containerUpdater.Update(this.Container);

            // Viewの登録
            this.Container.RegisterTypeForNavigation<MainPage>();
        }
    }
}

追記 2017/01/09

nuitsさんから指摘いただきました。

ということなので、こういう感じでOKな上に、将来的にはこの手間もいらなくなるとか。

using Prism.Autofac;
using Prism.Autofac.Forms;
using Autofac;
using Xamarin.Forms;
using PrismAutofac.Views;
using PrismAutofac.ViewModels;
using System.Reflection;

namespace PrismAutofac
{
    public partial class App : PrismApplication
    {
        protected override void OnInitialized()
        {
            InitializeComponent();

            this.NavigationService.NavigateAsync("MainPage");
        }

        protected override void RegisterTypes()
        {
            // Viewの登録
            this.Container.RegisterTypeForNavigation<MainPage>();

            var containerUpdater = new ContainerBuilder();
            // 登録されてない型もコンテナで作成する
            containerUpdater.RegisterSource(new Autofac.Features.ResolveAnything.AnyConcreteTypeNotAlreadyRegisteredSource());

            // Serviceの登録
            containerUpdater
                .RegisterAssemblyTypes(typeof(App).GetTypeInfo().Assembly)
                .Where(x => x.IsInNamespace("PrismAutofac.Services"))
                .Where(x => x.Name.EndsWith("Service"))
                .AsImplementedInterfaces()
                .SingleInstance();
            containerUpdater.Update(this.Container);
        }
    }
}

個人的に納得いかない挙動が1つあって、Viewの登録を最後に持ってくるとアプリが死ぬようになることですかね。

追記ここまで

あとは、PrismApplicationを継承するようにしてるのでApp.xamlの内容もそれに合わせて書き換えます。

<?xml version="1.0" encoding="utf-8"?>
<prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:prism="clr-namespace:Prism.Autofac;assembly=Prism.Autofac.Forms"
    x:Class="PrismAutofac.App">
    <Application.Resources>
        <!-- Application resource dictionary -->
    </Application.Resources>
</prism:PrismApplication>

実行すると、以下のようにいい感じに表示されます。

f:id:okazuki:20170108220649p:plain

サービスとかの登録もしてみよう

サービスの登録もついでにして見ましょう。以下のようなIMessageServiceというインターフェースを定義します。

using System;
namespace PrismAutofac.Services
{
    public interface IMessageService
    {
        string GetMessage();
    }
}

実装も適当にやります。

using System;
namespace PrismAutofac.Services
{
    public class MessageService : IMessageService
    {
        public string GetMessage() => "Xamarin.Forms and Prism.Autofac.Forms.";
    }
}

App.xaml.csでサービスの登録をシングルトンで行います。これもサービスが増えて来たときのことを考えてAssemblyをスキャンしてやってしまいましょう。

using Prism.Autofac;
using Prism.Autofac.Forms;
using Autofac;
using Xamarin.Forms;
using PrismAutofac.Views;
using PrismAutofac.ViewModels;
using System.Reflection;

namespace PrismAutofac
{
    public partial class App : PrismApplication
    {
        protected override void OnInitialized()
        {
            InitializeComponent();

            this.NavigationService.NavigateAsync("MainPage");
        }

        protected override void RegisterTypes()
        {
            // ViewModelの登録
            var containerUpdater = new ContainerBuilder();
            containerUpdater
                .RegisterAssemblyTypes(typeof(App).GetTypeInfo().Assembly)
                .Where(x => x.IsInNamespace("PrismAutofac.ViewModels"))
                .Where(x => x.Name.EndsWith("ViewModel"))
                .AsSelf();

            // Serviceの登録
            containerUpdater
                .RegisterAssemblyTypes(typeof(App).GetTypeInfo().Assembly)
                .Where(x => x.IsInNamespace("PrismAutofac.Services"))
                .Where(x => x.Name.EndsWith("Service"))
                .AsImplementedInterfaces()
                .SingleInstance();
            containerUpdater.Update(this.Container);

            // Viewの登録
            this.Container.RegisterTypeForNavigation<MainPage>();
        }
    }
}

MainPageViewModelを、IMessageServiceを使うように書き換えます。

using System;
using Prism.Mvvm;
using PrismAutofac.Services;

namespace PrismAutofac.ViewModels
{
    public class MainPageViewModel : BindableBase
    {
        private IMessageService MessageService { get; }

        public MainPageViewModel(IMessageService messageService)
        {
            this.MessageService = messageService;
        }

        public string Message => this.MessageService.GetMessage();
    }
}

実行すると、ちゃんとViewModelにMessageServiceがインジェクションされていることが確認できます。

f:id:okazuki:20170108221600p:plain

まとめ

Unityがいなくなっても平気ではありそうだなぁと思った今日この頃でした。(Unityには頑張って欲しい)