WCF + EntityFramework で POCO プロキシ のシリアル化

Entity Framework では 変更追跡と遅延読み込みを可能とするために Model クラスに対して自動的にプロキシクラスを作るのが既定の動きです。ですが、WCF ではこのプロキシオブジェクト をシリアライズすることができません。いや、正確にいうと、 DataContractSeirializer ではシリアライズできません。

MSDN にあるチュートリアルでは IOperationBehavior と Attribute を実装し、 WCF のメソッドに属性で適用する例が説明されています。ですが、該当するメソッドにいちいち属性を付けるのは実際のアーキテクチャでは採用されないでしょう。面倒だし、属性を忘れたら訳のわからないエラー(基礎になる接続が閉じられました、とかなんとか)で苦しむことになります。

データアクセスに EntityFramework を使用する、と決めたのならば WCF からの戻り値は POCO またはそのコレクション、またはそれらを含むクラスになるでしょう。ならば、あらかじめどこかで設定しておけば新しくメソッドを追加しても、戻り値がちゃんとシリアライズされるようになるのがアーキテクチャとして求められていることになるかと思います。

ではそのようにしてみたいと思います。Contract を用意します。


using System.ServiceModel;

using MyWcf.Models;

namespace MyWcf
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Customer GetData();
    }
}

戻り値の Customer は EntityFramework で取得した結果、プロキシオブジェクトとなります。次のように定義しました。


using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace MyWcf.Models
{
    [DataContract]
    public class Customer
    {
        public Customer()
        {
            this.CustomerAddresses = new List<CustomerAddress>();
            this.SalesOrderHeaders = new List<SalesOrderHeader>();
        }

        [DataMember]
        public int CustomerID { get; set; }
        [DataMember]
        public bool NameStyle { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string FirstName { get; set; }
        [DataMember]
        public string MiddleName { get; set; }
        [DataMember]
        public string LastName { get; set; }
        [DataMember]
        public string Suffix { get; set; }
        [DataMember]
        public string CompanyName { get; set; }
        [DataMember]
        public string SalesPerson { get; set; }
        [DataMember]
        public string EmailAddress { get; set; }
        [DataMember]
        public string Phone { get; set; }
        [DataMember]
        public string PasswordHash { get; set; }
        [DataMember]
        public string PasswordSalt { get; set; }
        [DataMember]
        public System.Guid rowguid { get; set; }
        [DataMember]
        public System.DateTime ModifiedDate { get; set; }
        [DataMember]
        public virtual ICollection<CustomerAddress> CustomerAddresses { get; set; }
        [DataMember]
        public virtual ICollection<SalesOrderHeader> SalesOrderHeaders { get; set; }
    }
}

この Customer クラス、AdventureWorksというサンプルデータベース内のクラスです。DataConract, DataMember 属性は自分で付けました。

次に Service です。


using System.Linq;

using MyWcf.Models;

namespace MyWcf
{
    public class Service1 : IService1
    {
        public Customer GetData()
        {
            using (var context = new AdventureWorksLTContext())
            {
                context.Configuration.LazyLoadingEnabled = false;
                var data = context.Customers.FirstOrDefault();

                return data;
            }
        }
    }
}

EntityFramework のコードファーストによる例です。さりげなく重要なのが、LazyLoadingEnabled を false にしている点です。遅延読み込みが有効になっていると、他 Model との参照関係を virtual プロパティで実装している部分が原因でうまくシリアライズできないようです。

さて、このままではエラーになります。戻り値である Customer オブジェクトがプロキシオブジェクトだからです。
ではいよいよ実装していきましょう。目標はメソッドを追加しても問題なくシリアライズできるようになる、ということなので、拡張は Contract、または Service のどちらかのレベルで実装することになります。

どっちでやっても大差ないのですが、今回は両方やってみたいと思います。まずは IContractBehavior の実装です。


using System;

using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Data.Objects;

namespace MyWcf
{
    public class ApplyDataContractResolverAttribute : Attribute, IContractBehavior
    {
        public ApplyDataContractResolverAttribute()
        {
        }

        #region IContractBehavior
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        { }

        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        { }

        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
        {
            foreach (var operation in contractDescription.Operations)
            {
                var dataContractSerializerOperationBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
            }
        }

        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        { }
        #endregion
    }

}

ApplyDispatchBehavior メソッドに注目です。OperationBehavior の中に、DataContractSerializerOeprationBehavior といういかにもな ビヘイビアが既定で登録されているので、それを探し出しているんですね。こいつの DataContractResolver プロパティに、これまた用意されている ProxyDataContractResolver を設定するだけです。
ただ、IContractBehavior の実装=Interface に対する振る舞いの実装なわけですから、 Interface に複数実装されているであろう Operation(メソッド) 全てに対して ProxyDataContractResolver を設定しなければならないので、 foreach でぐるぐる回しています。
インターフェースの明示的な実装をしているのはわざとです。IServiceBehavior の実装もこのクラスにする予定だからです。実際のアーキテクチャでは、 IContractBehavior, IServiceBehabior のどちらかしか実装しないでしょうから、明示的に実装する必要はありません。

さて、Contract = Interface なわけですから、Interface に対して属性を適用します。次の[ApplyDataContractResolver] という箇所です。


using System.ServiceModel;

using MyWcf.Models;

namespace MyWcf
{
    [ApplyDataContractResolver]
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Customer GetData();
    }
}

ContractBehavior は構成ファイルで設定することができません。これで実装終了です。動かせば、ちゃんとシリアライズされるはずです。
次は IServiceBehavior の実装です。さて、宣言どおり先ほどと同じクラスに実装しちゃいます。

using System;

using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Data.Objects;

namespace MyWcf
{
    public class ApplyDataContractResolverAttribute : Attribute, IContractBehavior, IServiceBehavior
    {
        public ApplyDataContractResolverAttribute()
        {
        }

        #region IServiceBehavior
        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase host)
        {
            foreach (var endpoint in description.Endpoints)
            {
                foreach (var operation in endpoint.Contract.Operations)
                {
                    var dataContractSerializerOperationBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
                    dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
                }
            }
        }


        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        { }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        { }
        #endregion

        #region IContractBehavior
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        { }

        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        { }

        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
        {
            foreach (var operation in contractDescription.Operations)
            {
                var dataContractSerializerOperationBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
            }
        }

        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        { }
        #endregion
    }
}

今度は Service レベルですから、 Service → Endpoint → Contract → Operation と下がるだけですね。

Contract = Interface ならば、Service とは Interface をImplements(実装・実現)したクラスのことですから、今回の例でいうと Service1 クラスのことです。属性を適用します。さきほど Interface に設定した属性は削除しておきましょう。


using System.Linq;

using MyWcf.Models;

namespace MyWcf
{
    [ApplyDataContractResolver]
    public class Service1 : IService1
    {
        public Customer GetData()
        {
            using (var context = new AdventureWorksLTContext())
            {
                context.Configuration.LazyLoadingEnabled = false;
                var data = context.Customers.FirstOrDefault();

                return data;
            }
        }
    }
}

動かしてみると、シリアライズされることがわかるはずです。

構成ファイルでこの IServiceBehavior を設定できるようにするためには、 BehaviorExtensionElement を実装し、構成ファイル内で登録する必要があります。


using System;

using System.ServiceModel.Configuration;

namespace MyWcf
{
    public class ApplyDataContractResolverElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(ApplyDataContractResolverAttribute); }
        }

        protected override object CreateBehavior()
        {
            return new ApplyDataContractResolverAttribute();
        }
    }
}

構成ファイルでの登録です。extension 要素内で ApplyDataContractResolverElement を追加することで拡張機能を登録しています。
Service に対しては、MyServiceBehavior という名前のビヘイビア内に新しく ApplyDataContractResolver という要素を追加することで登録しています。

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="MyServiceBehavior"
               name="MyWcf.Service1">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="MyWcf.IService1" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
            <ApplyDataContractResolver />
        </behavior>
      </serviceBehaviors>
    </behaviors>
      <extensions>
          <behaviorExtensions>
              <add name="ApplyDataContractResolver" type="MyWcf.ApplyDataContractResolverElement, MyWcf"/>
          </behaviorExtensions>
      </extensions>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

さきほど、Service1 に付けた属性を削除して動かしてみると、シリアライズされるはずです。

・・・さて、余談ですが、上記の config ファイルで設定した ApplyDataContractResolver 要素って、エラー扱いになりませんか?

20120505_01

これについて言及している人がほとんどいない、というかいない!なぜでしょうか・・・。
このエラーは非常に気持ちが悪いですね。ちゃんと動くのにエラー扱いなんですから。やり方に賛否両論あるでしょうが、消す方法はあります。

config ファイルを開いた状態で XML → スキーマの作成 とすると、現在の config を XSD スキーマにしてくれます。このスキーマをプロジェクト内に
保存してしまいます。

20120505_02

すると、 Web.config のスキーマとして作成した xsd ファイルが自動的に含まれます。 Web.config 上で右クリック → プロパティでプロパティウィンドウを開き、スキーマの部分を見てみると、一番最後に 追加されているのが確認できるでしょう。これでエラーが表示されなくなります。

カテゴリー: EntityFramework, WCF | コメントをどうぞ

WCF を 拡張する

MSDN マガジンのこの記事がよくできているのです。WCF を拡張する時に知りたいことが全て書いてあります。
でも、元記事の訳し方に問題があるのか、元記事が悪いのか、今ひとつわかりにくいです。自分なりの理解を残しておきます。

  • クライアント側のプロキシに相当するサーバー側のコンポーネントは、Dispatcher(ディスパッチャ)と呼ぶ。
  • 拡張は、プロキシとディスパッチャに対して行う。
    ※この記事は、プロキシとディスパッチャに対しての拡張についてのみ扱う、という意味。WCF はメッセージ、トランスポート、エンコーダ、などその気になったらなんでも作ることができる
  • プロキシはメソッドごとに ClientOperation オブジェクトを持つ。またプロキシ全体で1つ、 ClientRuntime オブジェクトを持つ。
  • ディスパッチャはメソッドごとに DispatchOperation オブジェクトを持つ。またディスパッチャ全体で1つ、DispatchRuntime オブジェクトを持つ。
  • プロキシ(クライアント)内の拡張ポイント
    • メソッドごとに持つ、ClientOperation オブジェクトが行う次の2つの動作
      1. パラメータの検査(IParameterInspector)
      2. メッセージの書式設定(シリアライズ、IClientFormatter)
    • プロキシ全体で1つ持つ、ClientRuntime オブジェクトが行う次の1つの動作
      1. メッセージの検査(IClientMessageInspector)
  • ディスパッチャ(サーバー)内の拡張ポイント
    • ディスパッチャ全体で1つ持つ、DispatchRuntime オブジェクトが行う次の2つの動作
      1. メッセージの検査(IDispatchMessageInspector)
      2. 操作の選択(IDispatchOperationSelector)
    • メソッドごとに持つ、DispatchOperation オブジェクトが行う次の3つの動作
      1. メッセージの書式設定(デシリアライズ、IDispatchMessageFormatter)
      2. パラメータの検査(IParameterInspector)
      3. 操作呼び出し元(IOperationInvoker)
  • プロキシとディスパッチャの拡張ポイント(Interface)の実装クラスは、独自に作成する Behavior に登録する。
  • 独自に作成する Behavior は、次の4つ。
    1. サービススコープの Behavior ・・・ IServiceBehavior
    2. エンドポイントスコープの Behavior ・・・ IEndpointBehavior
    3. コントラクトスコープの Behavior ・・・ IContractBehavior
    4. メソッドスコープの Behavior ・・・ IOperationBehavior
  • 独自に作成した Behavior は、属性または構成ファイル(config ファイル)を使って WCF へ登録する。
  • 属性、構成ファイルのそれぞれで、設定できる Behavior のスコープが異なる。
    1. 属性の場合
      • 1.サービス、3.コントラクト、4.メソッドスコープの Behavior は設定可能
      • 2.エンドポイントスコープの Behavior は設定できない
    2. 構成ファイルの場合
      • 1.サービス、2.エンドポイントスコープの Behavior は設定可能
      • 3.コントラクト、4.メソッドスコープの Behavior は設定できない
カテゴリー: WCF | コメントをどうぞ

WCF の用語について自分なりにまとめる

WCF は色んなところが関連付いていて、用語の定義が難しくなっている気がします。全体が見渡せるようになってからやっと用語の意味が理解できるようになる、というかなり高いハードルが WCF の普及を妨げているのかもしれませんね。自分なりのまとめをしておきます。

  • Service(サービス) とは

    1. ServiceContract 属性の付いた Interface を実装するクラス。次の例でいう MyWcf.Service1 のこと。
      
      namespace MyWcf
      {
          [ServiceContract]
          public interface IService1
          {
      
              [OperationContract]
              string GetData(int value);
      
          }
      
      }
      
      namespace MyWcf
      {
          public class Service1 : IService1
          {
              public string GetData(int value)
              {
                  return string.Format("You entered: {0}", value);
              }
          }
      }
      
      
    2. IIS でホストする場合の svc ファイル マークアップ内、Service 属性の値。次の例でいう MyWcf.Service1 のこと。
      
      <%@ ServiceHost Language="C#" Debug="true" Service="MyWcf.Service1" CodeBehind="Service1.svc.cs" %>
    3. config ファイル、<system.serviceModel><serivces> 内の <service> 要素の name 属性値。次の例でいう MyWcf.Service1 のこと。
        
      <system.serviceModel>
          <services>
            <service name="MyWcf.Service1">
              <endpoint
                address=""
                binding="basicHttpBinding"
                contract="MyWcf.IService1" />
            </service>
          </services>
      
      
    4. IServiceBehavior インターフェースの実装を適用することで拡張される、動作の単位。 例えば config ファイルで拡張設定をする場合、
      次のように拡張設定を行います。 service 要素内の behaviorConfiguration で、どの serviceBehavior を使用するかを指定しています。(ここでは MyServiceBehavior を指定しています)
        
      <system.serviceModel>
          <services>
            <service name="MyWcf.Service1" behaviorConfiguration="MyServiceBehavior">
              <endpoint
                address=""
                binding="basicHttpBinding"
                contract="MyWcf.IService1" />
            </service>
          </services>
          <behaviors>
            <serviceBehaviors>
              <behavior name="MyServiceBehavior">
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="false"/>
              </behavior>
            </serviceBehaviors>
          </behaviors>
      
      
    5. Endpoint を複数持つことができる。同一の Contract に対してアドレス違いや、Binding 違いなど。次の例はアドレス違いを示す。
      もちろん contract 違いの Endpoint を作ることもできるが、その場合 service が複数の Interface を実装しているのが大前提。
      というのも、name 属性の値は クラス名そのものなので、 contract 違い、ということは Interface 違い、ということになるから。
      そして、それぞれの Interface (要するに contact )ごとに Endpointを作る、ということである。
      でも、そんな Endpoint を必要とする要件はまずないんじゃないかなぁ・・・
        
      <system.serviceModel>
          <services>
            <service behaviorConfiguration="MyServiceBehavior" name="MyWcf.Service1">
              <endpoint address="" 
                        binding="basicHttpBinding" 
                        bindingConfiguration=""
                        contract="MyWcf.IService1" />
              <endpoint name="anotherEP"
                        address="another" 
                        binding="basicHttpBinding" 
                        bindingConfiguration=""
                        contract="MyWcf.IService1" />
              <host>
                <baseAddresses>
                  <add baseAddress="http://localhost:2424/" />
                </baseAddresses>
              </host>
            </service>
          </services>
      
      

      ちなみにこの例のようにアドレス違いを設定する場合で 各 endpoint の address 属性の値を相対アドレスで設定する場合は <host> 要素内の <baseAddress> に相対アドレスの元となるベースアドレスを設定します。

  • Endpoint(エンドポイント)とは

    1. Service が外部に公開するもの。Address (アドレス)、Binding (バインディング)、Contract (コントラクト)をひとつにまとめたもの。
    2. IEndpointBehavior インターフェースの実装を適用することで拡張される、動作の単位。例えば config ファイルで拡張設定をする場合、
      次のように拡張設定を行います。 endpoint 要素内の behaviorConfiguration で、どの endpointBehavior を使用するかを指定しています。(ここでは MyEndpointBehavior を指定しています)
        <system.serviceModel>
          <services>
            <service behaviorConfiguration="MyServiceBehavior" name="MyWcf.Service1">
              <endpoint address="" 
                        binding="basicHttpBinding" 
                        behaviorConfiguration="MyEndpointBehavior"
                        contract="MyWcf.IService1" />
            </service>
          </services>
          <behaviors>
            <serviceBehaviors>
              <behavior name="MyServiceBehavior">
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="false"/>
              </behavior>
            </serviceBehaviors>
            <endpointBehaviors>
              <behavior name="MyEndpointBehavior">
                <dataContractSerializer ignoreExtensionDataObject="true" />
              </behavior>
            </endpointBehaviors>
          </behaviors>
      
      
  • Address(アドレス)とは

    1. 外部からのアクセスできる、Endpoint の場所。
    2. 相対アドレスまたは絶対アドレスで指定。
    3. 細かく、そして正確には Address とは論理アドレスであり、物理アドレスとは異なる。WCF で指定しているのは 論理アドレス。でも物理アドレスが指定されていない場合は、論理アドレスを物理アドレスとして扱う。物理アドレスを別途指定する場合は endpoint 要素の ListernUri 属性を使用する。詳しくはWCF アドレス指定の詳細物理アドレスと論理アドレスを参照。

  • Binding(バインディング)とは

    1. クライアントとの通信方法。プロトコル、エンコーディングなど。
    2. トランザクション、信頼性、セキュリティ、エンコード、トランスポートの5つの層から成る。 (必須はエンコードとトランスポートだけ)
    3. システム提供の既定 Binding と、自ら5つの層を構築して作成する CustomBinding がある。
      多少は、MSDN も参考になる。

  • Contract(コントラクト)とは

    1. ~ Contract と付くものは意外にも多くて、ServiceContract, OperationConract, MessageContract, DataContract ほかにもあったかも。
    2. でも、Address, Binding, Contract (いわゆるABC)での Contract は、Interface を指すと思えばほぼ正解。 次の例でいう IService1 のこと。
      
      namespace MyWcf
      {
          [ServiceContract]
          public interface IService1
          {
      
              [OperationContract]
              string GetData(int value);
      
          }
      
      }
      
      namespace MyWcf
      {
          public class Service1 : IService1
          {
              public string GetData(int value)
              {
                  return string.Format("You entered: {0}", value);
              }
          }
      }
      
      
    3. 以下は MSDN からの引用。
      • クライアントから呼び出すことができる操作。
      • メッセージの形式。
      • 操作を呼び出すために必要な入力パラメーターまたはデータの型。
      • クライアントが予期できる処理メッセージまたは応答メッセージの種類
カテゴリー: WCF | コメントをどうぞ

ASP.NET MVC3 Dependency Indection (with Unity )

ASP.NET MVC 3 で DIする方法について調べたことをまとめておきます。

ここでは主にControllerに対するDIについてのまとめを残しておきます。Googleで色々検索していると、ASP.NET MVC 3 では、DependencyInjection のための拡張ポイントが3つあるように見えます。
ひとつは IDependencyResolver というインターフェース。これを実装すればよい、という人もいれば、他にも IControllerActivator も一緒に実装するサンプルを実装してみせる人もいれば、さらには IControllerFactory というインターフェースがどうのこうの言う人までいて、情報が錯綜しているように見えます。

結論から言えば、Unity やMEF, Seasar.NET などなんらかの DIContainer を使用して DI する場合に限ると、IDependencyResolver だけを実装すれば良いようです。

詳しくは Do not implement IControllerActivator in ASP.NET MVC 3 に書いてある通りなのですが、ICotrollerActivator の実装は、ほとんど意味がないのです。もちろん要件にもよりますが、 IControllerActivator が必要になるような要件は存在しないでしょう。

IDependencyResolver の実装が登録されていると、ASP.NET MVC3 はリクエスト時のパイプラインに従って拡張ポイントの実装が登録されているかどうかをひとつひとつ聞いてきます。

  1. IControllerFactory の実装は登録されていますか?されているならそれをください。
  2. IControllerActivator の実装は登録されていますか?されているならそれをください。
  3. ~~Controllerは登録されていますか?されているならそれをください。
  4. ・・・続く(Filter、ViewEngine、cshtml など)

IDepndencyResolver の実装が IControllerActivator の実装を ASP.NET MVC 3 へちゃんと渡してあげれば、ASP.NET MVC 3 は Controller の生成時にちゃんと IControllerActivator の実装を使ってくれます。ちなみに各インターフェースの実装を ASP.NET MVC 3 へ渡さないと、(具体的には null を返却すると) ASP.NET MVC 3 はデフォルトの実装を使用します。Unity による IDependencyResolver の実装例は次のようになります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Web.Mvc;
using Microsoft.Practices.Unity;

namespace MvcWithUnity.DI
{
    public class UnityDependencyResolver : IDependencyResolver {
        IUnityContainer container;
        public UnityDependencyResolver(IUnityContainer container)
        {
            this.container = container;
        }

        public object GetService(Type serviceType)
        {
            try {
                return this.container.Resolve(serviceType);
            }
            catch {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try {
                return this.container.ResolveAll(serviceType);
            }
            catch {
                return new List<object>();
            }
        }
    }
}

メソッド GetService の 仮引数である serviceType が IControllerActivator である場合、つまり先ほどのパイプラインの 2 の時、この時に Unity Container が Resolve した結果、IControllerActivator の実装を返却できるように Unity Container に登録しておけばよいわけです。そして ASP.NET MVC 3 にこの IDependencyResolver の登録をするには Global.asax へ次のように実装します。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    var container = new UnityContainer().LoadConfiguration();
    DependencyResolver.SetResolver(new MvcWithUnity.DI.UnityDependencyResolver(container));
}

Unity へのオブジェクトの登録は、config ファイルによる登録にしています。 LoadConfiguration メソッドを使っている部分がそれにあたります。

(余談ですが、 LoadConfiguration メソッドを使うには Microsoft.Practices.Unity.Configuration 名前空間への using が必要です)

では config ファイルへの記述例を・・・と、その前に IControllerActivator の実装をします。 DependencyResolver へ Contoller の生成を委譲する実装になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcWithUnity.DI
{
    public class UnityControllerActivator : IControllerActivator {
        public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            // 現在使用中の DependencyResolver を使用して、 contoller を生成して返却しています。
return DependencyResolver.Current.GetService(controllerType) as IController; } } }

段々ややこしくなってきましたが、要するに次のような流れです。

IControllerActivator の存在確認

ASP.NET MVC 3 → IDependencyResolver

この存在確認は、アプリケーションの初回起動時にしか行われません。 IDependencyResolver の戻り値は今回の場合 UnityControllerActivator です。

Controller 生成の委譲 ( with IControllerActivator )

ASP.NET MVC 3 → IControllerActivator → IDependencyResolver → Unity Container

ここが IControllerActivator の意味を無くしている点です。実はASP.NET MVC 3 はIControllerActivator の登録がない場合は、IDependencyResolver に対して Controller の生成を委譲するのです。

Contoller 生成の委譲 ( without IControllerActivator )

ASP.NET MVC 3 → IDependencyResolver → Unity Container

つまり、IControllerActivator の実装があってもなくても結果は同じなのです。だったら、余計な実装は少ないほうがよい、ということで IControllerActivator の実装はしないほうがよいのでは、という結論になります。

最後に Unity Container にオブジェクトの登録を Config ファイルに実装する例は次のとおりです。

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<
namespace name="MvcWithUnity.DI" />
<namespace name="System.Web.Mvc" />
<
assembly name="MvcWithUnity" />
<
assembly name="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<
container>
<!--
<register type="HomeController" />
コントローラを登録する必要はありません。登録していない型をResolveすると、Unityはその型を生成してくれます。 もしコントローラが仮引数ありのコンストラクタを持つ場合、その仮引数のオブジェクトがUnity に登録されていれば Unity はそのオブジェクトを 仮引数に Injection しつつコントローラを生成してくれるからです。 ––>
   <register type="IControllerActivator" mapTo="UnityControllerActivator" />
</container>
</unity >

ではなぜ IControllerActivator という拡張ポイントが提供されているのでしょうか。本来、IDependencyResolver では拡張ポイントに対する自前の実装だけを 登録するだけ、という使い方が正しいのかもしれません。今回の場合は IControllerActivator の実装だけを登録する、ということです。たとえば Controller を生成する DIContainer と、Filter や View を生成する DIContainer は別のものを使いたい、という要件にも応えることがことができます。 つまり、 Controller を生成する クラス は、このクラスなんだよ、と明確にする必要がある場合は IControllerActivator を実装する意味はあるのです。

ではまず、 IControllerActivator だけを登録する IDependencyResolver の実装例です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Web.Mvc;

namespace MvcWithUnity.DI
{
    public class UnityDependencyResolver : IDependencyResolver {
         public object GetService(Type serviceType)
        {
            try {
                if (serviceType.Equals(typeof(IControllerActivator)))
                {
                    return new UnityControllerActivator();
                }
                else return null;

            }
            catch {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return new List<object>();
        }
    }
}

仮引数 serviceType が IControllerActivator のときだけ、IControllerActivator の実装である UnityControllerActivator を返却しています。次はUnityControllerActivator の実装例を。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace MvcWithUnity.DI
{
    public class UnityControllerActivator : IControllerActivator {
        IUnityContainer container;

        public UnityControllerActivator()
        {
            this.container = new UnityContainer().LoadConfiguration();
        }

        public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            return this.container.Resolve(controllerType) as IController;
        }
    }
}

コンストラクタで Unity Container を生成しているところが変更点です。 Global.asax への UnityDependencyResolver の登録は次のようになります。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    DependencyResolver.SetResolver(new MvcWithUnity.DI.UnityDependencyResolver());
}

Unity Container を生成していた部分がなくなりました。純粋に UnityDependencyResolver を登録しているだけです。 config ファイルによる Unity Container へのオブジェクト登録は次のようになります。

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
</container>
</
unity>

空っぽになりました。

というわけで、Controller に対する DI だけにフォーカスして実装すると、IControllerActivator の実装が必要ではありますが、とてもシンプルでわかりやすくなります。

では、なぜ IControllerActivator を使わないのでしょうか。恐らく、 IDependencyResolver の実装内で Controller を生成 & DI することが要件として難しい、という場面が実際のプロジェクトではほとんど出てこないのしょう。実際、 何も不都合がないと思いませんか?

というわけで、結論として IDpendencyResolver の実装だけでOK!ということです。

カテゴリー: ASP.NET MVC, Unity | コメントをどうぞ

Entity Framework Code First を ORMapperとして使う

いいツールがリリースされています。
その名も Entity Framework Power Tools です。まだβだけど、これを使うことで、データベースからPOCOクラスをリバースエンジニアリングしてくれるし、PKの設定や列長の制限、FKの設定までもFluent APIで自動的に記述してくれます。なにより、Microsoft謹製!

つまり、これを使ってリバースエンジニアリングした結果を見れば、Web上にはあまり解説がなくて苦労する Fluent API の書き方も理解できちゃうわけです。これは便利!

惜しいのは、テーブルやビューなどを指定してリバースエンジニアリングができないことです。データベース単位でまとめてドーン!。まだ β ということで、正式リリースの時にはもっと便利になっているといいですね。

インストールはVisual Studio 2010 のツール → 拡張機能マネージャーから簡単にインストールできます。

さて、このEF Power Toolsは、βのくせになかなかに便利なんだけども、ちょっとだけカスタマイズしておいた方がより幸せになれます。
というわけでその方法です。

プロジェクト上で右クリックすると Entity Frameworkなんてメニューが追加されているはずです。その中の Customize Reverse Engineer Templates をクリック。

20120414_01

なんかセキュリティがどうのこうの言うけど、無視してOKクリック。・・・嫌ならキャンセルクリックでもいいです。結果は同じです。

20120414_02

あれ、同じダイアログがまたあがってきた?ってなるけど、あと2回OK(またはキャンセル)クリックしてればダイアログはあがらなくなります。
これは、テンプレートファイルが3つ作成されるから、3回ダイアログがみたいです。

で、プロジェクトをよく見ると、テンプレートファイルであるttファイルが3つ生成されているはずです。

20120414_03

そしたらまずはEntity.ttを開きます。そして、13行目にある、public class を、public partial class に変えます。

20120414_04

こんどは、Mapping.ttを開きます。同じように、33行目の public class を、public partial class に変え、さらに35行目に

partial void AddReferences();

を追加します。39行目に

this.AddReferences();

を追加します。次のようになればOKです。

20120414_05

これで準備はOKです。これをしておくと何が素敵かっていうと、リーバスエンジニアリングで自動的に生成された結果に何かを手を加えてしまうと、リバースエンジニアリングによる再生成をやりにくくなる・・・だって、再生成しちゃうと、せっかくカスタマイズした部分が消えちゃうから、一度どこかに退避しておいて、再生成後に手でマージして・・・なんて作業、いちいちやってられるか!・・・ってことが防げるってことです。

あれ、でも Entity Framework Power Tools を使えば自動的に全部設定をFluent APIで作ってくれるわけだから、カスタマイズなんかしなくてもいいんじゃないの?って思うかもしれません。
でもテーブル間のリレーションって、設計時にはもちろん考えるけど、物理的に外部参照制約は貼らないって場合多くないですか?だって、開発時には邪魔にしかならないし、運用の妨げになることも多いからって考えの人が結構いるのが現実。個人的にはFKは貼れ!って思ってますが。
んで、Entity Framework Power Tools のリバースエンジニアリングは、FK貼ってなかったら当たり前だけどクラス間の参照設定は作ってくれません。
だから自分で作るしかないのです。これがカスタマイズ部分、ってわけ。

ツール+人間で1つのクラスを作るっていうパターンのために partial クラスは作られたようなもんだから、すごく真っ当な使い方だと思うけどどうです?

さて、じゃあ次はそのカスタマイズいってみましょう。リバースエンジニアリングして次の2つのPOCOが生成された、とします。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Customer {
        public int CustomerId { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string PasswordHash { get; set; }
    }
}
using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Reservation {
        public int ReservationId { get; set; }
        public int CustomerId { get; set; }
        public System.DateTime ReservationStartDateTime { get; set; }
        public System.DateTime ReservationEndDateTime { get; set; }
    }
}

Customer(顧客)クラスは、Reservation(予約)クラスを複数持つ、つまり1:多の関係にあります。
ただ、データベース上ではFKを作っていなかったので、クラス間に参照はない。これを今から作っていきます。

まずはカスタマイズした設定を保持するためにModelsフォルダ内にフォルダを作る。カスタマイズ内容はクラス間の参照しかないはずなので、フォルダ名はReferencesにする。ここへカスタマイズクラスを作成する。

まずはCustomerクラスです。こいつはReservationクラスを複数持つので、ICollection<T>でプロパティを作る必要があります。
まずはCustomerクラスのファイルを ReferencesフォルダへそのままCopyしちまいましょう。名前空間がまったく同じpartialクラスを作るわけなので、こうやってあらかじめコピーしておけばミスがないです。

20120414_06

では、プロパティを書きます。次のようになるはず。virtual を忘れないように。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Customer {
        public virtual ICollection<Reservation> Reservations { get; set; }
    }
}

今度はReservationクラス。ReservationクラスからはCustomerクラスを1つだけ参照する(保持する)わけなので、プロパティは次のようになります。
あぁ、あらかじめReferencesフォルダへコピーしてから作業しましょう。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Reservation {
        public virtual Customer Customer { get; set; }
    }
}

さて、最後にオブジェクト間の参照の設定をしてやらなくてはならない。実は今回はこの設定をしなくても、規約に則ったプロパティ名なのでオブジェクト間の参照設定は必要ありません。(具体的に言うと、ReservationクラスのCustomerIdが規約に則った命名になっている)

でも規約に則っていない列名の方が世の中多いよねーということで、その場合はFluent APIでさらっと書けばよいでしょう。
今度はMappingフォルダ内にReferencesフォルダを作ることにしよう。そして、1:多の関連を書くわけだけども、この場合、「多」のクラスから見た場合の関連を書くっていうのがFluent API のお作法のようです。つまり今回の場合でいうと、Reservationクラスの設定ファイルである、ReservationMapクラスに書くわけだ。

では早速、Mapping\ReferencesフォルダにReservationMapクラスをコピー。次のように書きます。

using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;

namespace ConsoleAppEF.Models.Mapping
{
    public partial class ReservationMap : EntityTypeConfiguration<Reservation>
    {
         partial void AddReferences()
        {
            this.HasRequired(t => t.Customer)
                .WithMany(f => f.Reservations)
                .HasForeignKey(k => k.CustomerId);
        }
    }
}

設定の記述は、partial void AddReferencesメソッド内に書きます。これは部分メソッドで、コピー元のReservationMapのコンストラクタの先頭で呼ぶようにさっきカスタマイズしたところです。

部部メソッドは、中身を実装しなくても問題ないから、参照設定が必要あるクラスだけ書けばいい、という仕掛けです。

ではCustomerオブジェクトとReservationオブジェクトの参照を維持したままデータを取得してみましょう。オブジェクト参照を維持してSelectする方法はIncludeメソッドを使いますが、使い方は2種類あります。一つは、プロパティ名を文字列で指定する方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ConsoleAppEF.Models;

namespace ConsoleAppEF
{
    class Program {
        static void Main(string[] args)
        {
            using (var context = new MyDbContext())
            {
                var customerList = (from a in context.Customers.AsNoTracking().Include("Reservations")
                                   select a).ToList();

                Console.WriteLine("count : {0}", customerList.Count);

                Console.ReadLine();
            }
        }
    }
}

でも、これだとプロパティ名を文字列で指定って、非常に気に入らないですね。
そこで、System.Data.Entityをusing で参照しましょう。ラムダ式で指定が可能になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ConsoleAppEF.Models;
using System.Data.Entity;

namespace ConsoleAppEF
{
    class Program {
        static void Main(string[] args)
        {
            using (var context = new MyDbContext())
            {
                var customerList = (from a in context.Customers.AsNoTracking().Include(m => m.Reservations)
                                   select a).ToList();

                Console.WriteLine("count : {0}", customerList.Count);

                Console.ReadLine();
            }
        }
    }
}

これなら、ばっちり。

というわけで、Entity Framework Code FirstをORMapperとして使用できる環境が本格的に整った!と思いませんか?

カテゴリー: EntityFramework | コメントをどうぞ

既存の型およびオブジェクトを登録する(Registering Existing Types and Object Instances)

Unity exposes a method named RegisterInstance that you can use to register existing instances with the container. When you call the Resolve method, the container returns the existing instance during that lifetime. If you do not specify a value for the lifetime, the instance has a container-controlled lifetime, which means that it effectively becomes a singleton instance. For more information see Creating Instance Registrations

Unityは、コンテナに既存のインスタンスを登録するためのRegisterInstanceという名前のメソッドを公開します。Resolveメソッドを呼び出すと、コンテナはその期間中に存在しているインスタンスを返します。存続期間の値を指定しない場合、インスタンスはコンテナに制御された存続期間となります。それはつまりシングルトンインスタンスになることを意味しています。詳細については、「インスタンスの登録を作成」参照してください。

まとめると、

  • インスタンスをコンテナに登録できる。
  • インスタンス登録時にLifeTimeオブジェクトを指定しないと、シングルトンになる。

では、実験します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace ConsoleAppUnity
{
    class Program2 {
        static void Main(string[] args)
        {
            var container = new UnityContainer();

            // オブジェクトを生成し、登録する。 // 存続期間を何も指定していないので、登録オブジェクトはシングルトンのはず var objNoLifeTime = new InstantiateAndRegister();
            container.RegisterInstance<InstantiateAndRegister>(objNoLifeTime);

            var objNoLifeTime1 = container.Resolve<InstantiateAndRegister>();

            // デフォルト値 999 と表示される Console.WriteLine(objNoLifeTime1.Count);

            // 値を888に変更 objNoLifeTime1.Count = 888;

            // 2回目の取得 var objNoLifeTime2 = container.Resolve<InstantiateAndRegister>();

            // 888 が表示される。確かにシングルトン。 Console.WriteLine(objNoLifeTime2.Count);

            // コンテナに登録したオブジェクトを破棄 container.Teardown(objNoLifeTime);
            container.Teardown(objNoLifeTime1);
            container.Teardown(objNoLifeTime2);

            // オブジェクトを生成し、登録する。 // TransientLifetimeManagerは、Resolveの度にObjectを生成する。 var objTransient = new InstantiateAndRegister();
            container.RegisterInstance<InstantiateAndRegister>(objTransient, new TransientLifetimeManager());

            // 1回目の取得 var objTransient1 = container.Resolve<InstantiateAndRegister>();

            // デフォルト値 999 と表示される Console.WriteLine(objTransient1.Count);

            // 値を888に変更 objTransient1.Count = 888;

            // 2回目の取得 var objTransient2 = container.Resolve<InstantiateAndRegister>();

            // 999 と表示される。確かに毎回生成されている。 Console.WriteLine(objTransient2.Count);

            Console.ReadLine();
        }
    }

    // デフォルトコンストラクタがあるクラス class InstantiateAndRegister {
        public InstantiateAndRegister()
        {
            this.Count = 999;
        }

        public int Count { get; set; }
    }
}

 

カテゴリー: Unity | コメントをどうぞ

Unity で作成可能な オブジェクトの種類(The Types of Objects Unity Can Create)

Help File を適当に訳してみます。(Google翻訳を読みやすくするだけですが)

You can use the Unity container to generate instances of any object that has a public constructor (in other words, objects that you can create using the new operator), without registering a mapping for that type with the container. When you call the Resolve method and specify the default instance of a type that is not registered, the container simply calls the constructor for that type and returns the result.

public コンストラクタを持つ任意のオブジェクト(つまり、new演算子を使用して作成できるオブジェクト)であれば、オブジェクトの型のマッピングを登録せずにインスタンスを生成するために Unity コンテナを使用することができます。登録されていない型の既定のインスタンスを指定してResolveメソッドを呼び出すと、Unity コンテナは単にその型のコンストラクタを呼び出し、結果を返します。

Unity exposes a method named RegisterType that you can use to register types and mappings with the container. It also provides a Resolve method that causes the container to build an instance of the type you specify. The lifetime of the object it builds corresponds to the lifetime you specify in the parameters of the method. If you do not specify a value for the lifetime, the container creates a new instance on each call to Resolve. For more information see Registering Types and Type Mappings.

Unityは、型とマッピングを Unity コンテナに登録するためのRegisterTypeという名前のメソッドを公開します。また、Unity コンテナは、指定した型のインスタンスを生成するResolveメソッドを提供します。その生成されたオブジェクトの存続期間は、RegisterTypeメソッドのパラメータで指定した存続期間に対応しています。存続期間の値を指定しない場合、コンテナは、Resolve メソッドが呼ばれる度に新しいインスタンスを作成します。詳細については、「型と型のマッピングの登録」を参照してください。

簡単にまとめてみると、

  • Unity コンテナに登録していなくても、デフォルトコンストラクタ(パラメータなしのコンストラクタ)を持つクラスであれば、Resolveメソッドを使ってUnity コンテナでオブジェクトの生成ができ、そのオブジェクトを受け取れる。
  • Unity コンテナに型と型のマッピングは、RegisterTypeメソッドで行う。
  • Unity コンテナにオブジェクトの生成を指示し、その結果を受け取るのはResolveメソッド。
  • Unity コンテナ内で生成されたオブジェクトの存続期間はRegisterTypeメソッドの引数で指定。
  • 存続期間を指定しないと、Resolveメソッドが呼ばれるたびにオブジェクトが生成し直される。

大体こんな感じ?実験してみます。

「Unity コンテナに登録がなくても、オブジェクトの生成ができる」

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace ConsoleAppUnity
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();

            // RegsterType メソッドを使用して登録しないで、いきなりResolveする
            var obj = container.Resolve<NotRegister>();

            // 999 と表示される
            Console.WriteLine(obj.Count);

            Console.ReadLine();
        }
    }

    // デフォルトコンストラクタがあるクラス
    class NotRegister
    {
        public NotRegister()
        {
            this.Count = 999;
        }

        public int Count { get; set; }
    }
}

「型を登録。そのときに存続期間を指定しないと、毎回オブジェクトは生成される」

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace ConsoleAppUnity
{
    class Program1
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();

            // クラスを登録する。
            // 存続期間を何も指定していないので、Resolveメソッドが呼ばれるたびに、オブジェクトが生成されるはず
            container.RegisterType<Registered>();

            var objNoLifeTime1 = container.Resolve<Registered>();

            // デフォルト値 999 と表示される
            Console.WriteLine(objNoLifeTime1.Count);

            // 値を888に変更
            objNoLifeTime1.Count = 888;

            // 2回目の取得
            var objNoLifeTime2 = container.Resolve<Registered>();

            // 888 ではなく、デフォルト値 999 が表示される。毎回生成している。
            Console.WriteLine(objNoLifeTime2.Count);

            // コンテナに登録したオブジェクトを破棄
            container.Teardown(objNoLifeTime1);
            container.Teardown(objNoLifeTime2);

            // クラスを登録する。
            // ContainerControlledLifetimeManagerは、存続期間をシングルトンに設定するためのクラス。
            container.RegisterType<Registered>(new ContainerControlledLifetimeManager());

            // 1回目の取得
            var objSingleton = container.Resolve<Registered>();

            // デフォルト値 999 と表示される
            Console.WriteLine(objSingleton.Count);

            // 値を888に変更
            objSingleton.Count = 888;

            // 2回目の取得
            var objSingleton2 = container.Resolve<Registered>();

            // 888 と表示される。確かにシングルトンになっている。
            Console.WriteLine(objSingleton2.Count);

            Console.ReadLine();
        }
    }

    // デフォルトコンストラクタがあるクラス
    class Registered
    {
        public Registered()
        {
            this.Count = 999;
        }

        public int Count { get; set; }
    }
}

カテゴリー: Unity | コメントをどうぞ

Unity のヘルプファイル

まずはダウンロードします。

http://entlib.codeplex.com/releases

んで、ダブルクリックで開いてみても、あら、使えない。なんじゃこらぁあ!

こんな壊れたヘルプファイルしかねぇんじゃ、使えねぇよ!・・・って思っていました。
でもReleaseNotesのページをよーく見たら、こんなこと書いてありました。

Note: When downloading a CHM file, remember to Unblock it (Right Click and then look under Security) as it may be blocked by the OS.

んーと。よくわかんないけど、Unblock it とか書いてあるので、ヘルプファイルを右クリックします。

あ!ブロックの解除ってある!これをクリック⇒OKクリック。

おーちゃんと表示された。

カテゴリー: Unity | コメントをどうぞ