shuhelohelo’s blog

Xamarin.Forms多めです.

PrismでFeatureフォルダを追加してアプリケーションを構成する

mookiefumi.com

印象としては,ASP.NET CoreでいうところのArea機能に似たような感じなのかな.

Prismで使いやすいようにConfigureViewModelLocatorに手を加えている.

具体的には以下のように機能ごとにフォルダを分ける方針.

│   App.xaml
│   App.xaml.cs
│
└───Features
│   │
│   └───Dashboard
│       │   Model
│       │   Services
│       │   ViewModels
│       │   Services
│       │   Views
│   └───Orders
│       │   Model
│       │   Services
│       │   ViewModels
│       │   Services
│       │   Views

f:id:shuhelohelo:20200112134333p:plain

なるほど.

このような構成にするメリットは,変更しようとするファイルがわかりやすい,新規メンバーにもわかりやすいというものがある.

かつ,Visual StudioにはソリューションエクスプローラScope to Thisという機能があり,これを使うと指定したフォルダ以下だけが表示されるが,この機能との相性がとても良い.

f:id:shuhelohelo:20200112134522p:plain

Dashboard」フォルダにScope to Thisした後のソリューションエクスプローラf:id:shuhelohelo:20200112134701p:plain

ViewとViewModelをどのように登録するか

Prismの基本的な動作としては命名規則によってViewとViewModelが自動的に結び付けられる.

ただし,これはPrismの想定するフォルダ構成の場合だ.

今回はフォルダ構成を変更しているので自動的には結び付けられない.

手動

一つの方法は,ViewとViewModelを明示的に結びつける方法だ.

例えばApp.xaml.csファイル内のRegisterTypesメソッドで以下のように記述します.

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<SalesView, SalesViewModel>();
        }

自動

Prismがどのように自動でViewとViewModelを結びつけているかというと,ViewModelLocaterによってこれを行っている.

デフォルトでは以下の規則に従ってViewとViewModelを結びつける.

  • ViewとViewModelは同じアセンブリ内にあること
  • ViewModelはViewModels名前空間にあること
  • ViewはViews名前空間にあること
  • ViewModelの名前は{View名}ViewModelであること

フォルダ構成が異なる場合は上記の規則からはずれるため,Prismによって結びつけることができない.

このような場合はViewModelLocaterの設定を変更してやればよい.

ViewModelLocaterの設定変更

PrismApplicationクラスのConfigureViewModelLocatorメソッドをオーバーライドすることで変更することができる.

App.xaml.cs内のAppクラス内の該当箇所を以下のようにする.

ViewとViewModelを結びつける規則は以下のとおりとする.

View

PrismFeatureFolder.Features.Dashboard.Views.MainPage

ViewModel

PrismFeatureFolder.Features.Dashboard.ViewModels.MainPageViewModel

こんな対応関係で同じ機能フォルダ(ここではDashboardフォルダ)内のViewとViewModelを結びつけます.

ViewとViewModelの名前の規則は,View名〇〇に対してViewModel名〇〇ViewModelとなるように設定します.

Viewの名前に対して,どういう規則でViewModelを対応付けるかを指定しているのがGetViewModelNameメソッドです.

このメソッドはViewの型(クラス)を受け取って,その名前を使って対応するViewModelを探して対応付けます.

protected override void ConfigureViewModelLocator()
{
    base.ConfigureViewModelLocator();

    //We set here the type resolver
    ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(GetViewModelName);
}

        private Type GetViewModelName(Type viewType)
        {
            //Viewに体操するViewModelの名前を生成
            //viewType.FullNameで取得されるのは名前空間も含めた完全名.
            //その名前空間の「Views」の部分を「ViewModels」に置換している.
            var viewModelName = viewType.FullName.Replace("Views", "ViewModels")+"ViewModel";
            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            return Type.GetType($"{viewModelName}, {viewAssemblyName}");
        }

実行

ViewとViewModelを追加し,AppクラスのRegisterTypesメソッド内でViewを登録し,OnInitializedメソッドで起動時に開くViewを指定して,実行します.

この時点でApp内はだいたい以下のようになっていると思います.

    public partial class App
    {
        public App(IPlatformInitializer initializer=null):base(initializer)
        {
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<MainPage>();//規則に従ってViewModelと結び付けられる
            //containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();//明示的に指定
        }

        protected override void OnInitialized()
        {
            InitializeComponent();

            NavigationService.NavigateAsync(nameof(MainPage));
        }

        protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();
            
            //We set here the type resolver
            ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(GetViewModelName);
        }

        private Type GetViewModelName(Type viewType)
        {
            var viewModelName = viewType.FullName.Replace("Views", "ViewModels")+"ViewModel";
            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            return Type.GetType($"{viewModelName}, {viewAssemblyName}");
        }
    }

データバインディングは機能したでしょうか.

PrismによってViewとViewModelの対応付が行われるため,開発者はViewにもコードビハインドにも両者を結びつける記述をする必要がなく,互いに疎な構造にすることができます.

今回のソースコードは以下にあります.

github.com

Prismを使用する場合は,Prismテンプレートを使えるようにする拡張機能をインストールするか,もしくは手動で適用するか,どちらでも好きな方を選択できます.

手動で適用する場合は以下に記事を書きました.

shuhelohelo.hatenablog.com