次の方法で共有


ワークフローからの OData フィードの利用

WCF Data Services は .NET Framework のコンポーネントです。これを使用すると、Representational State Transfer (REST) のセマンティクスを使用し、Open Data Protocol (OData) を使用して Web またはイントラネット上のデータを公開および使用するサービスを作成できます。 OData は、URI でアドレス指定できるリソースとしてデータを公開します。 HTTP 要求を送信し、データ サービスが返す OData フィードを処理できるのであれば、どのようなアプリケーションでも OData ベースのデータ サービスと対話できます。 さらに、WCF Data Services には .NET Framework アプリケーションから OData フィードを使用する場合のプログラミング エクスペリエンスを向上させるクライアント ライブラリが含まれています。 このトピックでは、クライアント ライブラリを使用した場合と使用しない場合のワークフローでの OData フィードの使用の概要について説明します。

Northwind OData サービス サンプルの使用

このトピックの例では、https://services.odata.org/Northwind/Northwind.svc/ に含まれている Northwind データ サービス サンプルを使用します。 このサービスは OData SDK に含まれており、Northwind データベース サンプルへの読み取り専用アクセスを提供します。 書き込みアクセスが必要な場合、またはローカルの WCF Data Service が必要な場合は、「 クイック スタート (WCF Data Services) 」の手順に従って、Northwind データベースへのアクセスを提供するローカルの OData サービスを作成できます。 クイックスタートの手順に従う場合は、このトピックのコード例に指定されている URI をローカルの URI に置き換えてください。

クライアント ライブラリを使用した OData フィードの使用

WCF Data Services には、.NET Framework およびクライアント アプリケーションから OData フィードを簡単に使用できるクライアント ライブラリが含まれています。 これらのライブラリは、HTTP メッセージの送受信を簡略化します。 また、メッセージ ペイロードをエンティティ データを表す CLR オブジェクトに変換します。 クライアント ライブラリには、 DataServiceContext および DataServiceQuery<TElement>という 2 つのコア クラスがあります。 これらのクラスを使用すると、データ サービスをクエリして、返されるエンティティ データを CLR オブジェクトとして処理できます。 ここではクライアント ライブラリを使用するアクティビティを作成するための 2 つの方法を説明します。

WCF Data Service へのサービス参照の追加

Northwind クライアント ライブラリを生成する場合は、Visual Studio 2012 の [サービス参照の追加] ダイアログ ボックスを使用して参照を Northwind OData サービスに追加できます。

[サービス参照の追加] ダイアログ ボックスを示すスクリーンショット。

サービスによって公開されるサービス操作はなく、 [サービス] ボックスの一覧には Northwind データ サービスによって公開されるエンティティを表す項目が含まれていることに注意してください。 サービス参照を追加すると、これらのエンティティに対するクラスが生成され、クライアント コードで使用できるようになります。 このトピックの例ではこれらのクラスと NorthwindEntities クラスを使用してクエリを実行します。

注意

詳細については、「データ サービス クライアント ライブラリの生成 (WCF Data Services)」を参照してください。

非同期メソッドの使用

Web のリソースにアクセスするときに発生することのある、待機時間に伴う問題に対処するために、WCF Data Services には非同期でアクセスすることをお勧めします。 WCF Data Services クライアント ライブラリにはクエリを呼び出すための非同期メソッドが含まれ、Windows Workflow Foundation (WF) には非同期アクティビティを作成するための AsyncCodeActivity クラスがあります。 AsyncCodeActivity 派生アクティビティを書き込んで、非同期メソッドが含まれる .NET Framework クラスを利用することも、非同期に実行されるコードをメソッドに含め、デリゲートを使用して呼び出すこともできます。 ここでは、 AsyncCodeActivity 派生アクティビティの例を 2 つ紹介します。1 つは WCF Data Services クライアント ライブラリの非同期メソッドを使用し、もう 1 つはデリゲートを使用しています。

注意

詳細については、「非同期操作 (WCF Data Services)」および非同期アクティビティの作成に関するページを参照してください。

クライアント ライブラリの非同期メソッドの使用

DataServiceQuery<TElement> クラスには、OData サービスを非同期で照会するための BeginExecute メソッドと EndExecute メソッドが用意されています。 これらのメソッドは、 BeginExecute 派生クラスの EndExecute オーバーライドと AsyncCodeActivity オーバーライドから呼び出すことができます。 AsyncCodeActivity BeginExecute オーバーライドが戻ると、ワークフローはアイドル状態になることができ (永続化はされない)、非同期操作が完了すると、EndExecute がランタイムによって呼び出されます。

次の例では、2 つの入力引数がある OrdersByCustomer アクティビティが定義されています。 引数 CustomerId は、返す注文を識別する顧客を表し、引数 ServiceUri は、照会する OData サービスの URI を表します。 アクティビティは AsyncCodeActivity<IEnumerable<Order>> から派生するので、クエリ結果を返すために使用される Result 出力引数も存在します。 BeginExecute オーバーライドは、指定された顧客の注文をすべて選択する LINQ クエリを作成します。 このクエリは、渡された UserStateAsyncCodeActivityContextとして指定され、その後クエリの BeginExecute メソッドが呼び出されます。 クエリの BeginExecute に渡されるコールバックと状態は、アクティビティの BeginExecute メソッドに渡されるものであることに注意してください。 クエリの実行が完了すると、アクティビティの EndExecute メソッドが呼び出されます。 クエリは UserStateから取得され、その後クエリの EndExecute メソッドが呼び出されます。 このメソッドは、指定されたエンティティ型の IEnumerable<T> を返します。この場合は Orderになります。 IEnumerable<Order>AsyncCodeActivity<TResult> のジェネリック型であるので、この IEnumerable はアクティビティの Result OutArgument<T> として設定されます。

class OrdersByCustomer : AsyncCodeActivity<IEnumerable<Order>>
{
    [RequiredArgument]
    public InArgument<string> CustomerId { get; set; }

    [RequiredArgument]
    public InArgument<string> ServiceUri { get; set; }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        NorthwindEntities dataContext = new NorthwindEntities(new Uri(ServiceUri.Get(context)));

        // Define a LINQ query that returns Orders and
        // Order_Details for a specific customer.
        DataServiceQuery<Order> ordersQuery = (DataServiceQuery<Order>)
            from o in dataContext.Orders.Expand("Order_Details")
            where o.Customer.CustomerID == CustomerId.Get(context)
            select o;

        // Specify the query as the UserState for the AsyncCodeActivityContext
        context.UserState = ordersQuery;

        // The callback and state used here are the ones passed into
        // the BeginExecute of this activity.
        return ordersQuery.BeginExecute(callback, state);
    }

    protected override IEnumerable<Order> EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the DataServiceQuery from the context.UserState
        DataServiceQuery<Order> ordersQuery = context.UserState as DataServiceQuery<Order>;

        // Return an IEnumerable of the query results.
        return ordersQuery.EndExecute(result);
    }
}

次の例では、指定された顧客の注文一覧を OrdersByCustomer アクティビティが取得した後、返された注文を ForEach<T> アクティビティが列挙し、各注文の日付をコンソールに書き込みます。

Variable<IEnumerable<Order>> orders = new Variable<IEnumerable<Order>>();
DelegateInArgument<Order> order = new DelegateInArgument<Order>();

Activity wf = new Sequence
{
    Variables = { orders },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new OrdersByCustomer
        {
            ServiceUri = "http://services.odata.org/Northwind/Northwind.svc/",
            CustomerId = "ALFKI",
            Result = orders
        },
        new ForEach<Order>
        {
            Values = orders,
            Body = new ActivityAction<Order>
            {
                Argument = order,
                Handler = new WriteLine
                {
                    Text = new InArgument<string>((env) => string.Format("{0:d}", order.Get(env).OrderDate))
                }
            }
        }
    }
};

WorkflowInvoker.Invoke(wf);

このワークフローを呼び出すと、次のデータがコンソールに書き込まれます。

Calling WCF Data Service...
8/25/1997
10/3/1997
10/13/1997
1/15/1998
3/16/1998
4/9/1998

注意

OData サーバーへの接続を確立できない場合は、次のような例外が表示されます。

ハンドルされていない例外: System.InvalidOperationException: この要求の処理中にエラーが発生しました。 ---> System.Net.WebException: リモート サーバーに接続できません。---> System.Net.Sockets.SocketException: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。または接続済みのホストが応答しなかったため、確立された接続は失敗しました。

クエリによって返されたデータをさらに処理する必要がある場合は、アクティビティの EndExecute オーバーライドで実行できます。 BeginExecuteEndExecute はどちらもワークフロー スレッドを使用して呼び出されます。これらのオーバーライド内のコードは非同期では実行されません。 追加の処理が大規模なものであるか、時間がかかるか、クエリ結果がページングされる場合は、次のセクションで説明する方法を検討してください。この方法では、デリゲートを使用してクエリを実行し、追加の処理を非同期で行います。

デリゲートの使用

.NET Framework クラスの非同期メソッドを呼び出すほかに、AsyncCodeActivity ベースのアクティビティでは独自のメソッドのいずれかに非同期ロジックを定義することもできます。 このメソッドは、アクティビティの BeginExecute オーバーライド内でデリゲートを使用することで指定します。 このメソッドが戻ると、ランタイムによってアクティビティの EndExecute オーバーライドが呼び出されます。 OData サービスをワークフローから呼び出すときは、このメソッドを使用してサービスを照会し、追加の処理を実行できます。

次の例では、 ListCustomers アクティビティを定義します。 このアクティビティは、Northwind データ サービス サンプルを照会し、Northwind データベース内の顧客をすべて含む List<Customer> を返します。 非同期操作は GetCustomers メソッドによって実行されます。 このメソッドは、サービスに対してすべての顧客を照会し、これらの顧客を List<Customer>にコピーします。 次に、結果がページングされているかどうかを確認します。 ページングされている場合は、サービスに対して結果の次のページを照会し、それを一覧に追加します。処理は顧客データをすべて取得するまで続行されます。

注意

WCF Data Services でのページングの詳細については、「方法: ページングされた結果を読み込む (WCF Data Services)」を参照してください。

顧客がすべて追加されると、一覧が返されます。 GetCustomers メソッドはアクティビティの BeginExecute オーバーライドで指定されます。 メソッドには戻り値があるので、メソッドを指定するために Func<string, List<Customer>> が作成されます。

注意

非同期操作を実行するメソッドに戻り値がない場合は、Action の代わりに Func<TResult> が使用されます。 両方の方法を使用した非同期の例の作成例については、非同期アクティビティの作成に関するページを参照してください。

この Func<TResult>UserState に割り当てられ、BeginInvoke が呼び出されます。 呼び出すメソッドはアクティビティの引数の環境にアクセスできないので、引数 ServiceUri 値は BeginExecuteに渡されたコールバックおよび状態と共に、最初のパラメーターとして渡されます。 GetCustomers が戻ると、ランタイムによって EndExecuteが呼び出されます。 EndExecute 内のコードはデリゲートを UserStateから取得し、 EndInvokeを呼び出し、結果を返します。この結果は、 GetCustomers メソッドから返された顧客の一覧です。

class ListCustomers : AsyncCodeActivity<List<Customer>>
{
    [RequiredArgument]
    public InArgument<string> ServiceUri { get; set; }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        // Create a delegate that references the method that implements
        // the asynchronous work. Assign the delegate to the UserState,
        // invoke the delegate, and return the resulting IAsyncResult.
        Func<string, List<Customer>> GetCustomersDelegate = new Func<string, List<Customer>>(GetCustomers);
        context.UserState = GetCustomersDelegate;
        return GetCustomersDelegate.BeginInvoke(ServiceUri.Get(context), callback, state);
    }

    protected override List<Customer> EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the delegate from the UserState and call EndInvoke
        Func<string, List<Customer>> GetCustomersDelegate = (Func<string, List<Customer>>)context.UserState;
        return (List<Customer>)GetCustomersDelegate.EndInvoke(result);
    }

    List<Customer> GetCustomers(string serviceUri)
    {
        // Get all customers here and add them to a list. This method doesn't have access to the
        // activity's environment of arguments, so the Service Uri is passed in.

        // Create the DataServiceContext using the service URI.
        NorthwindEntities context = new NorthwindEntities(new Uri(serviceUri));

        // Return all customers.
        QueryOperationResponse<Customer> response =
            context.Customers.Execute() as QueryOperationResponse<Customer>;

        // Add them to the list.
        List<Customer> customers = new List<Customer>(response);

        // Is this the complete list or are the results paged?
        DataServiceQueryContinuation<Customer> token;
        while ((token = response.GetContinuation()) != null)
        {
            // Load the next page of results.
            response = context.Execute<Customer>(token) as QueryOperationResponse<Customer>;

            // Add the next page of customers to the list.
            customers.AddRange(response);
        }

        // Return the list of customers
        return customers;
    }
}

次の例では、顧客の一覧を ListCustomers アクティビティが取得した後、その一覧を ForEach<T> アクティビティが列挙し、各顧客の会社名と連絡先名をコンソールに書き込みます。

Variable<List<Customer>> customers = new Variable<List<Customer>>();
DelegateInArgument<Customer> customer = new DelegateInArgument<Customer>();

Activity wf = new Sequence
{
    Variables = { customers },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new ListCustomers
        {
            ServiceUri = "http://services.odata.org/Northwind/Northwind.svc/",
            Result = customers
        },
        new ForEach<Customer>
        {
            Values = customers,
            Body = new ActivityAction<Customer>
            {
                Argument = customer,
                Handler = new WriteLine
                {
                    Text = new InArgument<string>((env) => string.Format("{0}, Contact: {1}",
                        customer.Get(env).CompanyName, customer.Get(env).ContactName))
                }
            }
        }
    }
};

WorkflowInvoker.Invoke(wf);

このワークフローを呼び出すと、次のデータがコンソールに書き込まれます。 このクエリは多くの顧客を返すので、ここでは出力の一部のみが表示されています。

Calling WCF Data Service...
Alfreds Futterkiste, Contact: Maria Anders
Ana Trujillo Emparedados y helados, Contact: Ana Trujillo
Antonio Moreno Taquería, Contact: Antonio Moreno
Around the Horn, Contact: Thomas Hardy
Berglunds snabbköp, Contact: Christina Berglund
...

クライアント ライブラリを使用しない OData フィードの使用

OData は、URI でアドレス指定できるリソースとしてデータを公開します。 クライアント ライブラリを使用するとこれらの URI が自動的に作成されますが、必ずしもクライアント ライブラリを使用する必要はありません。 OData サービスはクライアント ライブラリを使用せずに直接アクセスすることもできます。 クライアント ライブラリを使用しない場合、サービスの場所と必要なデータは URI によって指定され、結果が HTTP 要求への応答の中で返されます。 この生データはその後自由に処理または操作できます。 OData クエリの結果を取得する方法の 1 つとして、 WebClient クラスを使用する方法があります。 この例では、キー ALFKI が表す顧客の連絡先名が取得されます。

string uri = "http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/ContactName";
WebClient client = new WebClient();
string data = client.DownloadString(uri);
Console.WriteLine("Raw data returned:\n{0}", data);

このコードが実行されると、次の出力がコンソールに表示されます。

Raw data returned:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ContactName xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">Maria Anders</ContactName>

ワークフローでは、この例のコードを Execute ベースのカスタム アクティビティの CodeActivity オーバーライドに組み込むことができますが、InvokeMethod<TResult> アクティビティを使用して同じ機能を実行することもできます。 InvokeMethod<TResult> アクティビティは、ワークフローの作成者がクラスの静的メソッドやインスタンス メソッドを呼び出すことができるようにします。また、指定したメソッドを非同期で呼び出すオプションもあります。 次の例では、 InvokeMethod<TResult> アクティビティは DownloadString クラスの WebClient メソッドを呼び出して顧客一覧を返すように構成されています。

new InvokeMethod<string>
{
    TargetObject = new InArgument<WebClient>(new VisualBasicValue<WebClient>("New System.Net.WebClient()")),
    MethodName = "DownloadString",
    Parameters =
    {
        new InArgument<string>("http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders")
    },
    Result = data,
    RunAsynchronously = true
},

InvokeMethod<TResult> は、クラスの静的メソッドとインスタンス メソッドの両方を呼び出すことができます。 DownloadStringWebClient クラスのインスタンス メソッドであるため、 WebClient のために TargetObjectクラスの新規インスタンスが指定されます。 DownloadStringMethodNameとして指定され、クエリを含む URI は Parameters のコレクションで指定され、戻り値が Result の値に指定されます。 RunAsynchronously 値は trueに設定されます。これは、ワークフローに関するメソッドの呼び出しが非同期で実行されることを意味します。 次の例では、 InvokeMethod<TResult> アクティビティを使用するワークフローが作成され、Northwind データ サービス サンプルに対して特定の顧客の注文一覧が照会されます。返されたデータはコンソールに書き込まれます。

Variable<string> data = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { data },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new InvokeMethod<string>
        {
            TargetObject = new InArgument<WebClient>(new VisualBasicValue<WebClient>("New System.Net.WebClient()")),
            MethodName = "DownloadString",
            Parameters =
            {
                new InArgument<string>("http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders")
            },
            Result = data,
            RunAsynchronously = true
        },
        new WriteLine
        {
            Text = new InArgument<string>(env => string.Format("Raw data returned:\n{0}", data.Get(env)))
        }
    }
};

WorkflowInvoker.Invoke(wf);

このワークフローが呼び出されると、次の出力がコンソールに表示されます。 このクエリは複数の注文を返すので、ここでは出力の一部のみが表示されています。

Calling WCF Data Service...
Raw data returned:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>*
<feed
xml:base="http://services.odata.org/Northwind/Northwind.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<title type="text">Orders\</title>
<id>http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders\</id>
<updated>2010-05-19T19:37:07Z\</updated>
<link rel="self" title="Orders" href="Orders" />
<entry>
<id>http://services.odata.org/Northwind/Northwind.svc/Orders(10643)\</id>
<title type="text">\</title>
<updated>2010-05-19T19:37:07Z\</updated>
<author>
<name />
</author>
<link rel="edit" title="Order" href="Orders(10643)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customer" type="application/atom+xml;type=entry" title="Customer" href="Orders(10643)/Customer" />
...

この例は、ワークフロー アプリケーションの作成者が OData サービスから返された生データを使用できる方法の 1 つを示しています。 URI を使用して WCF Data Services にアクセスする方法の詳細については、「データ サービス リソースへのアクセス (WCF Data Services)」および OData: URI 規約に関するページを参照してください。