WCF Data Servicesの新機能 – カスタムプロバイダ1


WCF Data Servicesの新機能、今回は6)カスタムプロバイダをご紹介します。(2/24に実施されたTechDaysセッションのフォローアップも兼ねています)

このシリーズの目次は以下になります。
1) WCF Data Servicesの新機能 – 射影
2) WCF Data Servicesの新機能 – カウント
3) WCF Data Servicesの新機能 – Server Driven Paging(SDP)
4) WCF Data Servicesの新機能 – Feed Customization
5) WCF Data Servicesの新機能 – データバインド
6) WCF Data Servicesの新機能 – カスタムプロバイダ1
7) WCF Data Servicesの新機能 – カスタムプロバイダ2
8) WCF Data Servicesの新機能 – リクエストパイプライン
9)Open Data Protocolの実装 – Share Point Server 2010のデータを操作する

また以下のトピックに関しては1)の記事を参照してください。
■名称の変更
■新バージョン
■更新モジュール(開発環境)

■カスタムプロバイダ
WCF Data Servicesでは、以下の3種類のデータプロバイダの使用が可能です。

1)Entity Framework provider
リレーショナルデータをマップしたデータモデルの定義(EDM)を使用して、ADO.NET Entity Framework ベースのデータサービスを提供します。主にSQL Serverになりますが、もちろんEntity Frameworkに対応していれば、サードパーティ製のデータプロバイダも使用することが可能です。

2)Reflection provider
こちらは、リフレクションを使用します、既存のデータクラスを基に定義されたデータモデルの使用が可能になります。データのクエリにはIQueryableインターフェイスを用います。また、更新を可能にするにはIUpdatableインターフェイスの実装が必要です。

3)Custom Data Service Providers
これが新たに加わった手法です。動的にデータモデルの定義を可能にします。またデータプロバイダに関しての詳細な制御も可能になります。

■どのような時に使用するべきか?
色々な観点はあるのですが、
・データサービス(モデル)が今後も開発対象であり発展する可能性がある
・データモデルに関して細かく制御したい(ログ取得、公開しないメンバー、リネームなど)
・公開したいデータモデルに対して、必ずしも厳密な型定義ができない
・BlobストリームやServerDrivenPagingなどの新機能の実装を行いたい
などなどです。

■使用インターフェイス
以下を使用して機能を実装します。
- IDataServiceMetadataProvider
- IDataServiceQueryProvider
- IDataServiceUpdateProvider
- IDataServiceStreamProvider
- IDataServicePagingProvider

■カスタムプロバイダの作成(メタデータの提供)
これから、実際にカスタムプロバイダを作成してみたいと思います。お題目として、ちょっと迷ったのですが、普通に自分で考えたProductクラスとかを公開しても面白くないので、Word2007形式のdocxをWCF Data Servicesでデータの取得を可能にするプロバイダを作成することにします。ただ、記事が長くなるので、まず今回はmetadataの取得ができるところまでを目指しましょう。

カスタムプロバイダの作成には、上記のインターフェイスを用いるわけですが、もちろんデータサービスを作成する根本的なインターフェイスを用いる必要があります。IServiceProvider、DataService<T> の2つです。この2つのインターフェイスを継承したクラスを作成します。

IServiceProvider が持つGetServiceメソッドでは、クライアントから要求が有った場合に適切なインターフェイスをもつオブジェクトを返却しなくてはなりません。この適切なインターフェイスが上記の使用インターフェイスになるわけです。イメージとしては以下のような感じですね。IDataServiceMetadataProviderはサービスのメタデータを返し、IDataServiceQueryProviderは、データの取得要求にこたえます。最低この2つが必要です。

public object GetService(Type serviceType)
{
    if (serviceType == typeof(IDataServiceMetadataProvider))
        return metadata;
    else if (serviceType == typeof(IDataServiceQueryProvider))
        return query;
    else
        return null;
}

上記を踏まえてメインとなるクラスを記述したのが以下です。(事前にクラスライブラリのプロジェクトを新規作成して、参照の追加で、System.Data.Servicesを追加してください。)

DetaService<T> は、これまでEDMから得られるコンテキスト情報を型として渡していましたが、今回は自分で用意したクラスをデータコンテキストとして使用したいと思います。

また、コンストラクタでIDataServiceQueryProviderとIDataServiceMetadataProviderを継承したオブジェクトを実体化しています。(当然まだクラスの定義はありません)

加えてwhere以下は、内部で使用しているMyMetadataProvider(これから定義します)での指定に必要であるため、あえてつけて有ります。

public class MyDataService<T> : DataService<T>, IServiceProvider where T : MyDataProvider.DataContext.MyContext, new()
{
    public IDataServiceMetadataProvider metadata;
    public IDataServiceQueryProvider query;

    public MyDataService()
    {
        metadata = new MyMetadataProvider<T>();
        query = new MyQueryProvider<T>(metadata);
    }

    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(IDataServiceMetadataProvider))
            return metadata;
        else if (serviceType == typeof(IDataServiceQueryProvider))
            return query;
        else
            return null;
    }

}

上記のMyMetadataProviderを定義します。もちろんIDataServiceMetadataProvider を継承します。ここでは、ResourceSets とTypeプロバティの要求に対して、適切な値(Dictionary)を返す必要があります。実際この2種類の値を作成しているのがコンストラクタなのですが、Typeは実際のデータサービスのメタデータ情報(型情報など)になるので、データコンテキストの中で定義するべきかと思います。そのためMyContext抽象クラスを用意して、そこから定義されるメソッド(CreateResourceType)で行いたいと思います。といった理由から、インターフェイスの型に制限を設けました。(where以下です)

  加えてContainerName 、ContainerNamespace 辺りに適当な文字を入れています。後はお決まりだと思ってそのまま記述してください。

public class MyMetadataProvider<T> : IDataServiceMetadataProvider where T : MyDataProvider.DataContext.MyContext, new()
{
    private Dictionary<string, ResourceType> resourceTypes = new Dictionary<string, ResourceType>();
    private Dictionary<string, ResourceSet> resourceSets = new Dictionary<string, ResourceSet>();

    public MyMetadataProvider()
    {
        //メタデータの作成
        T ct = new T();
        ResourceType RT = ct.CreateResourceType();
        RT.SetReadOnly();
        resourceTypes.Add(RT.FullName, RT);

        ResourceSet RS = new ResourceSet(RT.Name, RT);
        RS.SetReadOnly();
        resourceSets.Add(RS.Name, RS);
    }

    public string ContainerName
    {
        get { return "こだか"; }
    }

    public string ContainerNamespace
    {
        get { return "ネームすぺーす"; }
    }

    public IEnumerable<ResourceType> Types
    {
        get { return this.resourceTypes.Values; }
    }

    public IEnumerable<ResourceSet> ResourceSets
    {
        get { return this.resourceSets.Values; }
    }

    public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)
    {
        return resourceSets.TryGetValue(name, out resourceSet);
    }

    public bool TryResolveResourceType(string name, out ResourceType resourceType)
    {
        return resourceTypes.TryGetValue(name, out resourceType);
    }

    public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation)
    {
        serviceOperation = null;
        return false;
    }

    public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
    {
        yield break;
    }

    public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
    {
        throw new NotImplementedException("No relationships.");
    }

    public bool HasDerivedTypes(ResourceType resourceType)
    {
        return false;
    }

    public IEnumerable<ServiceOperation> ServiceOperations
    {
        get { yield break; }
    }
}

次にMyQueryProviderを定義します。今回はデータの取得は行わないので、こちらもお決まりの記述になります。

public class MyQueryProvider<T> : IDataServiceQueryProvider where T : MyDataProvider.DataContext.MyContext
{
    T currentDataSource;
    public object CurrentDataSource
    {
        get { return currentDataSource; }
        set { currentDataSource = value as T; }
    }

    public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
    {
      throw new NotImplementedException();
    }

    public ResourceType GetResourceType(object target)
    {
        throw new NotImplementedException();
    }

    public bool IsNullPropagationRequired
    {
        get { return true; }
    }

    public object GetOpenPropertyValue(object target, string propertyName)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target)
    {
        throw new NotImplementedException();
    }

    public object GetPropertyValue(object target, ResourceProperty resourceProperty)
    {
        throw new NotImplementedException();
    }

    public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters)
    {
        throw new NotImplementedException();
    }
}

そして、最後にデータコンテキストを用意します。
はじめに抽象クラスMyContextです。メタデータ用に関数を一つ(CreateResourceType)定義しています。これはMyMetadataProviderのコンストラクタで使用していました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Services.Providers;

namespace MyDataProvider.DataContext
{
    public abstract class MyContext
    {
        public abstract ResourceType CreateResourceType();
    }
}

今回公開するデータの基となるクラスです。今回は静的なクラスを使用します。
最終的にWordドキュメントをWCFDataServcesで公開したいので、そのためのメンバをもったクラス定義にしています。

public class WordText
{
      public int Id { get; set; }
      public string InnerText { get; set; }
      public string InnerXML { get; set; }
}

以下が実際のデータコンテキスト本体です。ここでデータタイプの指定をしています。リフレクションプロバイダーの場合、属性をつけられたクラスそのものが公開されていたのですが、カスタムプロバイダの場合は以下のように細かい指定が可能です。(例えば公開したくないメンバがあっても問題ありません。)

  public class WordTextContext : MyContext
  {
      public override ResourceType CreateResourceType()
      {
          var productType = new ResourceType(typeof(WordText),
                                                 ResourceTypeKind.EntityType,
                                                 null,
                                                 "Namespace",
                                                 "WordText",
                                                 false
                                                  );
          var Id = new ResourceProperty("Id",
                                         ResourcePropertyKind.Key |
                                         ResourcePropertyKind.Primitive,
                                         ResourceType.GetPrimitiveResourceType(typeof(int))
                                          );

          var InnerText = new ResourceProperty("InnerText",
                                                 ResourcePropertyKind.Primitive,
                                                 ResourceType.GetPrimitiveResourceType(typeof(string))
                                                 );
          var InnerXML = new ResourceProperty("InnerXML",
                                                   ResourcePropertyKind.Primitive,
                                                   ResourceType.GetPrimitiveResourceType(typeof(string))
                                                );

          productType.AddProperty(Id);
          productType.AddProperty(InnerText);
          productType.AddProperty(InnerXML);
          return productType;
      }
  }

実際に使用してみましょう。WebApplicationを新規で作成して、これまで作成していてクラスライブラリを参照追加します。そして、WCF Data Servicesを追加して以下のコードを記述します。Boldの箇所が追加したプロバイダの指定ですね。

public class WcfDataService1 : MyDataProvider.MyDataService<MyDataProvider.DataContext.WordTextContext>
{
     public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

後は、実行してみます。当然ながら、まだデータクエリの部分は実装していないので、以下のようにメタデータの指定を行います。
http://localhost:2015/WcfDataService1.svc/$metadata

結果は以下のようになります。コード内で定義したさまざまな値が表示されているのが分かります。当然ながら、まだデータクエリの部分は実装していないのでエラーになります。

image

ここまで作成したものは以下です。
http://cid-f1df6ad9b682ecf0.skydrive.live.com/self.aspx/2010^_TechDays/MyDataProvider^_1.zip

次回はWordデータを取得するところまで作成したいと思います。


Skip to main content