ModelからViewModel へのプロパティを注入してくれるライブラリとして ValueInjecter を使っています。ValueInjecter は既定のInjectionは 名前と型が一致するプロパティのインジェクションを行い、特定のプロパティに対するインジェクションを無効にすることができません。なので、今回は、特定のプロパティに対するインジェクションを無視するInjectionクラス IgnoreSupportSameNameTypeInjection を自作してみました。ちなみに、AutoMapper には 特定のプロパティを無視する機能があります。

その他のマッパーツール AutoMapper  http://automapper.org/
たぶんAutoMapper のほうがメジャーかつそのままで高機能だと思います。ただ ValueInjecter はお気軽に使えることと 匿名型への対応も柔軟に行えるので使っています。複雑なマッピングの場合は無理してマッパー系のライブラリを使わなければいいと思っているので。

検証環境

  • Value Injeter 2.3
  • .NET 4

 IgnoreSupportSameNameTypeInjection は、コンストラクタに Injection を無効にする プロパティを文字列で指定するバージョンと Expression で指定するパターンを作成しています。それ以外型と名前が一致するプロパティに対して 値の注入を行います。

1.  IgnoreSupportSameNameTypeInjection の定義

ソース IgnoreSupportSameTypeInjection.cs は次のようになります。IgnoreSupportSameNameTypeInjection が Value Injecter の既定の Injection である SameNameType と同じように名前とプロパティが一致するプロパティに対して値の注入を行います。ただし、コンストラクタで指定されたプロパティ名と一致している場合、Injection を行いません。IgnoreSupportSameNameTypeInjection<T>は、無視するプロパティ名を Expression で指定するバージョンです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Omu.ValueInjecter;
using System.Linq.Expressions;

namespace IgnorePropertyTest
{
    /// <summary>
    /// マッピングを無視するプロパティを指定できる以外は、
    /// SameNameTypeと同じ動作を行うInjection.
    /// </summary>
    public class IgnoreSupportSameNameTypeInjection : ValueInjection
    {
        /// <summary>
        /// 値とキーが同じプロパティ名のクラス
        /// </summary>
        protected IDictionary<string, string> _ignoreProperties;
        public IgnoreSupportSameNameTypeInjection(params string[] propeties)
        {
            Initialize(propeties);
        }

        protected void Initialize(IEnumerable<string> properties)
        {
            _ignoreProperties = new Dictionary<string, string>();

            foreach (var item in properties)
            {
                if (!_ignoreProperties.ContainsKey(item))
                {
                    _ignoreProperties.Add(item, item);
                }
            }
        }
        /// <summary>
        /// ソースとターゲットのプロパティで型と名前が一致すれば値の注入を行う。
        /// ただし、指定された名前のプロパティはマッピングから除外する。
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        protected override void Inject(object source, object target)
        {
            var sourceProps = source.GetProps();
            for (var i = 0; i < sourceProps.Count; i++)
            {
                var s = sourceProps[i];

                // name and type is same then inject value.
                var t = target.GetProps().GetByName(s.Name);
                if (t == null) continue;

                if (s.PropertyType != t.PropertyType) continue;

                // if ignoring property then continue.
                if (_ignoreProperties.ContainsKey(s.Name)) continue;

                t.SetValue(target, s.GetValue(source));
            }
        }
    }

    /// <summary>
    /// IgnoreSupportSameNameTypeInjectionで、無視するプロパティの
    /// 名前をラムダ式で指定できるようにしたクラス
    /// 
    /// マッピング条件が同じ名前と型のプロパティなのでTにはソース、
    /// ターゲットどちらのクラスも指定可能。
    /// </summary>
    /// <typeparam name="TTarget">ターゲットオブジェクトの型</typeparam>
    public class IgnoreSupportSameNameTypeInjection<T> : IgnoreSupportSameNameTypeInjection
    {
        public IgnoreSupportSameNameTypeInjection(params Expression<Func<T, object>>[] nodes)
        {
            List<string> properties = new List<string>();

            ListPropertiesVisitor<T> visitor = new ListPropertiesVisitor<T>();
            visitor.Translate(nodes);
            Initialize(visitor.ListedProperties);
        }
    }
    /// <summary>
    /// プロパティアクセスされるプロパティの一覧を列挙するビジター
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ListPropertiesVisitor<T> : ExpressionVisitor
    {
        private List<string> _properties;

        public IEnumerable<string> ListedProperties
        {
            get { return _properties.AsReadOnly(); }
        }

        public ListPropertiesVisitor() { }

        public IEnumerable<string> Translate(params Expression<Func<T, object>>[] nodes)
        {
            _properties = new List<string>();
            foreach (var item in nodes)
            {
                Visit(item);
            }
            return _properties;
        }
        protected override Expression VisitMember(MemberExpression node)
        {
            _properties.Add(node.Member.Name);
            return node;
        }
    }

}

実装は簡単ですが以上です。

2. テストプログラム

作成したInjectionクラスの使ったサンプルプログラムを掲載します。下記プログラムで、TestPattern1, TestPattern2 メソッド内でそれぞれインジェクションを無視するプロパティを文字列とExpressionで指定するIgnoreSupportSameNameTypeInjection の使用例になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Omu.ValueInjecter;

namespace IgnorePropertyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            TestPattern1();
            TestPattern2();
            Console.WriteLine("Enterを入力して下さい。");
            Console.ReadLine();
        }
        /// <summary>
        /// 文字列で無視するプロパティを指定するテスト
        /// </summary>
        static void TestPattern1()
        {
            TestViewModel from = new TestViewModel
            {
                Name = "UpdatedName",
                Point = 10,
                AddedDate = DateTime.Parse("2012/01/01 10:10"),
                AddedBy = "Update UserName"
            };

            TestModel to = new TestModel
            {
                Name = "OriginalName",
                Point = 9,
                NewPoint = 400,
                AddedDate = DateTime.Parse("2011/12/10 15:15"),
                AddedBy = "Original UserName"
            };

            IgnoreSupportSameNameTypeInjection injection = new IgnoreSupportSameNameTypeInjection("AddedBy", "AddedDate");
            to.InjectFrom(injection, from);

            Console.WriteLine(to);

        }
        /// <summary>
        /// Expression Tree で無視するプロパティを指定するテスト
        /// </summary>
        static void TestPattern2()
        {
            TestViewModel from = new TestViewModel
            {
                Name = "UpdatedName",
                Point = 10,
                AddedDate = DateTime.Parse("2012/01/01 10:10"),
                AddedBy = "Update UserName"
            };

            TestModel to = new TestModel
            {
                Name = "OriginalName",
                Point = 9,
                NewPoint = 400,
                AddedDate = DateTime.Parse("2011/12/10 15:15"),
                AddedBy = "Original UserName"
            };
            IgnoreSupportSameNameTypeInjection<TestModel> injection = new IgnoreSupportSameNameTypeInjection<TestModel>(
                    x => x.AddedBy, x => x.AddedDate
                );
            to.InjectFrom(injection, from);

            Console.WriteLine(to);

        }

    }

    public class TestViewModel
    {
        public string Name { get; set; }
        public int Point { get; set; }
        public DateTime AddedDate { get; set; }
        public string AddedBy { get; set; }
    }

    public class TestModel
    {
        public string Name { get; set; }
        public int Point { get; set; }
        public int NewPoint { get; set; }
        public DateTime AddedDate { get; set; }
        public string AddedBy { get; set; }
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendFormat("Name:{0},", Name);
            builder.AppendFormat("Point:{0},", Point);
            builder.AppendFormat("NewPoint:{0},", NewPoint);
            builder.AppendFormat("AddedDate:{0},", AddedDate);
            builder.AppendFormat("AddedBy:{0}", AddedBy);

            return builder.ToString();
        }
    }

}

 

3.まとめ

説明は以上です。ValueInjecter はクラスライブラリ側の実装も自分で自作してもいいくらい非常にシンプルでお気軽に使えるので良いと思っています。そんなにないかもしれませんが、Injectionを行いたくないプロパティもある気がしたのでカスタム ValueInjection を自作してみました。指摘事項などありましたらご連絡ください。