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; } } |
後は、実行してみます。当然ながら、まだデータクエリの部分は実装していないので、以下のようにメタデータの指定を行います。
https://localhost:2015/WcfDataService1.svc/$metadata
結果は以下のようになります。コード内で定義したさまざまな値が表示されているのが分かります。当然ながら、まだデータクエリの部分は実装していないのでエラーになります。
ここまで作成したものは以下です。
https://cid-f1df6ad9b682ecf0.skydrive.live.com/self.aspx/2010^_TechDays/MyDataProvider^_1.zip
次回はWordデータを取得するところまで作成したいと思います。