データの公開は簡単に出来ることがわかりました。では、こいつに対して何かしらアクションを実行したい!という時はどうすればいいのでしょうか?実は、WCF Data Services 5.0からServiceActionというやつが追加されてます。こいつを使うと、WCF Data Servicesに任意のアクションを追加することが出来ます。アクションを追加できる単位は、オブジェクトだったりオブジェクトの集合だったり、そういうのに紐づかないServiceActionも作成できます。WCF Data Services 5.0以前でもServiceOperationというものが提供されていましたが、こいつは引数にプリミティブ型しかダメだとか制約があったので、実際に使うには厳しい感じでした。ServiceActionは、シリアライズ可能なら複合型もいけるみたいなので期待大です!
サービスアクションの定義方法
ServiceActionをWCF Data Servicesで公開するには、IDataServiceActionProviderインターフェースと、IDataServiceUpdateProvider2インターフェースを実装したクラスを、IServiceProviderインターフェースを通じて公開しないといけません。一言でいうとメンドクサイです。ということで、簡単にハローワールド的なServiceActionを作ってみようと思います。たった1つのServiceActionを公開するのに、これだけのコードが必要です・・・!
namespace WebApplication1 { using System; using System.Collections.Generic; using System.Data.Services; using System.Data.Services.Providers; using System.Linq; public class ServiceActions : // サービスアクションを公開するためのProvider IDataServiceActionProvider, // サービス更新 + サービスアクション実行のためのProvider IDataServiceUpdateProvider2 { // 実行するアクションをためておく private List<Action> actions = new List<Action>(); // 公開するサービスアクション private ServiceAction action; public ServiceActions() { this.action = new ServiceAction( // アクション名はGreet "Greet", // 戻り値はstring ResourceType.GetPrimitiveResourceType(typeof(string)), // 何か特定のエンテティとは紐づかないのでnull null, // 何か特定のエンテティとは紐づかないのでNever OperationParameterBindingKind.Never, // アクションのパラメータ new[] { // string型のmessage new ServiceActionParameter("message", ResourceType.GetPrimitiveResourceType(typeof(string))) }); // アクションは設定が終わったらSetReadOnlyを呼ぶこと this.action.SetReadOnly(); } public bool AdvertiseServiceAction(DataServiceOperationContext operationContext, ServiceAction serviceAction, object resourceInstance, bool resourceInstanceInFeed, ref Microsoft.Data.OData.ODataAction actionToSerialize) { return true; } // サービスアクションを呼ぶクラスを作成 public IDataServiceInvokable CreateInvokable(DataServiceOperationContext operationContext, ServiceAction serviceAction, object[] parameterTokens) { // Greet決め打ち return new GreetInvokable((string)parameterTokens.First()); } // すべてのアクションを返す public IEnumerable<ServiceAction> GetServiceActions(DataServiceOperationContext operationContext) { yield return this.action; } // 特定のエンテティに紐づいたアクションを返す public IEnumerable<ServiceAction> GetServiceActionsByBindingParameterType(DataServiceOperationContext operationContext, ResourceType bindingParameterType) { yield break; } // アクションを取得する public bool TryResolveServiceAction(DataServiceOperationContext operationContext, string serviceActionName, out ServiceAction serviceAction) { // Greetしか無いことを想定した簡単実装 if (serviceActionName == "Greet") { serviceAction = this.action; return true; } serviceAction = null; return false; } // invokableを呼ぶアクションをリストに追加 void IDataServiceUpdateProvider2.ScheduleInvokable(IDataServiceInvokable invokable) { this.actions.Add(invokable.Invoke); } // リストに登録しておいたアクションを全実行 void IUpdatable.SaveChanges() { try { foreach (var action in this.actions) { action(); } } catch (Exception ex) { throw new DataServiceException( 500, ex.Message); } } // キャンセル void IUpdatable.ClearChanges() { this.actions.Clear(); } // 以下はエンテティの更新のときに必要なメソッドなのでServiceActionでは使わない void IDataServiceUpdateProvider.SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues) { throw new NotImplementedException(); } void IUpdatable.AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded) { throw new NotImplementedException(); } object IUpdatable.CreateResource(string containerName, string fullTypeName) { throw new NotImplementedException(); } void IUpdatable.DeleteResource(object targetResource) { throw new NotImplementedException(); } object IUpdatable.GetResource(IQueryable query, string fullTypeName) { throw new NotImplementedException(); } object IUpdatable.GetValue(object targetResource, string propertyName) { throw new NotImplementedException(); } void IUpdatable.RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved) { throw new NotImplementedException(); } object IUpdatable.ResetResource(object resource) { throw new NotImplementedException(); } object IUpdatable.ResolveResource(object resource) { throw new NotImplementedException(); } void IUpdatable.SetReference(object targetResource, string propertyName, object propertyValue) { throw new NotImplementedException(); } void IUpdatable.SetValue(object targetResource, string propertyName, object propertyValue) { throw new NotImplementedException(); } } public class GreetInvokable : IDataServiceInvokable { private string message; private string result; public GreetInvokable(string message) { this.message = message; } public object GetResult() { return result; } public void Invoke() { result = message + " hello!!"; } } }
正直やってられません。DataService
[ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class WcfDataService : DataService<SampleContext>, IServiceProvider { private readonly ServiceActions ActionProvider = new ServiceActions(); public static void InitializeService(DataServiceConfiguration config) { config.UseVerboseErrors = true; config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.SetServiceActionAccessRule("*", ServiceActionRights.Invoke); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } object IServiceProvider.GetService(Type serviceType) { if (serviceType == typeof(IDataServiceActionProvider)) { return this.ActionProvider; } if (serviceType == typeof(IDataServiceUpdateProvider2)) { return this.ActionProvider; } return null; } }
これは、IServiceProviderを実装するだけなので、簡単ですね。SetServiceActionAccessRuleでServiceActionを呼び出せるようにすることも忘れずに。
呼び出し方法
コンソールアプリケーションを作成して、そこにサービス参照を追加します。そして、以下のようなコードでサービスアクションを呼び出せます。残念ながら、サービスアクションは、呼び出すためのコードを作ってくれません…。
namespace ConsoleApplication4 { using ConsoleApplication4.ServiceReference1; using System; using System.Data.Services.Client; using System.Linq; class Program { static void Main(string[] args) { var client = new SampleContext( new Uri("http://localhost:49533/WcfDataService.svc")); var result = client.Execute<string>( new Uri("Greet", UriKind.Relative), "POST", true, new BodyOperationParameter("message", "たなか")) .First(); Console.WriteLine(result); } } }
実行すると「たなか hello!!」と表示されます。めんどくさいけどメデタシメデタシ。