DI コンテナ Unity の利用(その4)

前回に引き続き DI コンテナ Unity の利用についてです。

前回はコンテナの受け渡しと複数インスタンスの注入について記述したので、今回は WPF の MVVM パターンでの Unity DI コンテナの利用について記述します。

WPF の MVVM パターンでのプロジェクトになると、さすがにコーディング量が多くなるので、GitHub のリポジトリにプロジェクトを置いておいたので、必要な方はそちらをどうぞ。

全体の構成は次のとおりとします。

  • アプリケーションはメインウィンドウで「記録された本の情報の表示」、入力ウィンドウで「本の情報の入力」を行うものとする。
  • データ層のインスタンスの生存期間は DI コンテナと同一(DI コンテナの生存期間はアプリケーションの生存期間にする)、サービス層は DI コンテナの Resolve メソッド呼び出しごとに生成とする(異なる生存期間の例示のため)。
  • MVVM パターンで必要となる「プロパティ変更通知」「コマンド機能」「モーダル ウィンドウの表示機能」などは、拙作の MakViewModelBase を利用する
  • フォルダの構成は次のとおりとする(大したこともしていないのでサービス層を置くようなものではないのですが、依存性の注入の例示ということで)。
    • DAL: データアクセス層のものを置く。
    • DiContainer: DI Container の設定用のものを置く
    • Models: モデル層のものを置く。
    • Services: サービス層のものを置く。
    • ViewModels: ビューモデル層のものを置く。
  • DI コンテナは、DIContainer クラスの静的プロパティから取得する。
  • DI コンテナの取得とビューモデルのインスタンスの取得・DataContext への設定は Window のコードビハインドへ記述する。
  • ハンドリングできていない例外発生時の対応とアプリケーション終了時の対応を App.xaml と App.xaml.cs で行う。
  • アプリケーション終了時に DI コンテナの Dispose メソッドを呼び出す。

メインウィンドウと入力ウィンドウは次のようになります。

メインウィンドウ

入力ウィンドウ

Visual Studio を起動して WPF アプリケーションのプロジェクトを作成し、NuGet から MakViewModelBase と Unity を導入します。

最初にモデルから。
プロジェクトに「Models」フォルダを作成し、Models フォルダに Book クラスを作成します。

using MakCraft.ViewModels;

namespace WpfMvvmUnityDi.Models
{
    public class Book : NotifyObject
    {
        public Book() { }
        public Book(Book book) : this(book.Id, book.Title, book.Author) { }
        public Book(int id, string title, string author)
        {
            Id = id;
            Title = title;
            Author = author;
        }

        public int Id { get; set; }

        private string _title;
        public string Title
        {
            get { return _title; }
            set { base.SetProperty(ref _title, value); }
        }

        private string _author;
        public string Author
        {
            get { return _author; }
            set { base.SetProperty(ref _author, value); }
        }
    }
}

本の情報を保持するデータアクセス層の InMemoryBooks クラスが保持する Book クラスのインスタンスへの参照情報とサービス層側に受け渡しをする Book クラスのインスタンスへの参照情報を別のものにしたい(サービス層での Book の変更で InMemoryBooks クラスが保持する Book の内容が変わるのを避ける)ことから、コンストラクタにコピーコンストラクタを実装しています(データアクセス層の実装で利用します)。
SetProperty メソッドはプロパティへの値のセットとプロパティ変更通知を行います。

次にデータアクセス層です。
プロジェクトに「DAL」フォルダを作成し、DAL フォルダに IDataLayer インターフェイスと DataLayer クラスを作成します。
本の情報の保持は、シングルトンな InMemoryBooks クラスを実装して行っています。

using System.Collections.Generic;

using WpfMvvmUnityDi.Models;

namespace WpfMvvmUnityDi.DAL
{
    public interface IDataLayer
    {
        Book GetBook(int id);

        IEnumerable<Book> GetBooks();

        void AddBook(Book book);

        void UpdateBook(Book book);

        void DeleteBook(int id);

        void Save();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

using WpfMvvmUnityDi.Models;

namespace WpfMvvmUnityDi.DAL
{
    public class DataLayer : IDataLayer, IDisposable
    {
        public DataLayer()
        {
            System.Diagnostics.Trace.WriteLine("Datalayer instance: Create!");
        }

        public void AddBook(Book book)
        {
            var books = InMemoryBooks.Instance;
            var max = (books.Count != 0) ? books.Max(s => s.Id) : 0;
            book.Id = ++max;
            books.Add(new Book(book));
        }

        public void DeleteBook(int id)
        {
            throw new NotImplementedException();
        }

        public Book GetBook(int id)
        {
            var books = InMemoryBooks.Instance;
            return new Book(books.Find(m => m.Id == id));
        }

        public IEnumerable<Book> GetBooks()
        {
            var books = InMemoryBooks.Instance;
            return books.Select(s => new Book(s)).ToArray();
        }

        public void UpdateBook(Book book)
        {
            throw new NotImplementedException();
        }

        public void Save() { }

        public void Dispose()
        {
            System.Diagnostics.Trace.WriteLine("Datalayer instance: Dispose!");
        }
    }

    class InMemoryBooks : List<Book>
    {
        static InMemoryBooks() { } // コンパイラによる beforefieldinit フラグの付加を抑止する
        private InMemoryBooks() { } // 外部からのインスタンス生成を抑止

        // 初回アクセス時に InMemoryBooks クラスのインスタンスを生成する(スレッドセーフ)。
        private static Lazy<InMemoryBooks> _instance = new Lazy<InMemoryBooks>(() => {
            var instance = new InMemoryBooks();
            return instance;
        });

        // InMemoryBooks のインスタンスを取得する get のみのプロパティ
        public static InMemoryBooks Instance => _instance.Value;
    }
}

次にサービス層です。
プロジェクトに「Services」フォルダを作成し、Services フォルダに IBookService インターフェイスと BookService クラスを作成します。

using System.Collections.Generic;

using WpfMvvmUnityDi.Models;

namespace WpfMvvmUnityDi.Services
{
    public interface IBookService
    {
        Book GetBookById(int id);

        IEnumerable<Book> GetAllBooks();

        void AddBook(Book book);
    }
}
using System;
using System.Collections.Generic;

using WpfMvvmUnityDi.DAL;
using WpfMvvmUnityDi.Models;

namespace WpfMvvmUnityDi.Services
{
    public class BookService : IBookService, IDisposable
    {
        public BookService(IDataLayer dataLayer)
        {
            System.Diagnostics.Trace.WriteLine("BookService instance: Create!");

            _dataLayer = dataLayer;
        }

        private IDataLayer _dataLayer;

        public void AddBook(Book book)
        {
            _dataLayer.AddBook(book);
            _dataLayer.Save();
        }

        public Book GetBookById(int id)
        {
            return _dataLayer.GetBook(id);
        }

        public IEnumerable<Book> GetAllBooks()
        {
            return _dataLayer.GetBooks();
        }

        public void Dispose()
        {
            System.Diagnostics.Trace.WriteLine("BookService instance: Dispose!");
        }
    }
}

次に DI コンテナの設定です。
プロジェクトに「DiContainer」フォルダを作成し、DiContainer フォルダに DIContainer クラスを作成します。

using Microsoft.Practices.Unity;
using System;

using WpfMvvmUnityDi.DAL;
using WpfMvvmUnityDi.Services;

namespace WpfMvvmUnityDi.DiContainer
{
    sealed class DIContainer
    {
        private static Lazy<IUnityContainer> _container = new Lazy<IUnityContainer>(() =>
        {
            System.Diagnostics.Trace.WriteLine("DIContainer instance: Create!");

            var container = new UnityContainer();
            RegisterTypes(container);
            return container;
        });

        public static IUnityContainer Container => _container.Value;

        private static void RegisterTypes(IUnityContainer container)
        {
            container.RegisterType<IDataLayer, DataLayer>(new ContainerControlledLifetimeManager());
            container.RegisterType<IBookService, BookService>();
        }
    }
}

実装のやり方は、ASP.NET MVC の UnityContainer の設定と同様です。
冒頭の全体の構成で記述したように、DataLayer オブジェクトの生存期間はコンテナと同一、BookService オブジェクトは Resolve メソッド呼び出しごとに生成となるように設定しています。

長くなったので、次回に続けます。


2 thoughts on “DI コンテナ Unity の利用(その4)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です