Partager via


Consommation de flux OData à partir d’un workflow

WCF Data Services est un composant du .NET Framework qui vous permet de créer des services qui utilisent le protocole OData (Open Data Protocol) pour exposer et consommer des données sur le web ou l’intranet à l’aide de la sémantique de REST (Representational State Transfer). OData expose les données sous forme de ressources adressables par des URI. Toute application peut interagir avec un service de données basé sur OData si elle peut envoyer une requête HTTP et traiter le flux OData retourné par un service de données. WCF Data Services inclut également des bibliothèques clientes qui fournissent une expérience en programmation plus riche lorsque vous consommez des flux OData à partir des applications .NET Framework. Cette rubrique fournit une vue d'ensemble de la consommation d'un flux OData dans un workflow avec et sans l'utilisation de bibliothèques clientes.

Utilisation de l’exemple de service OData Northwind

Les exemples présentés dans cette rubrique s’appuient sur l’exemple de service de données Northwind accessible à https://services.odata.org/Northwind/Northwind.svc/. Ce service fait partie du SDK OData et fournit un accès en lecture seule à l’exemple de base de données Northwind. Si vous souhaitez un accès en écriture ou un service de données WCF local, vous pouvez suivre la procédure de démarrage rapide WCF Data Services pour créer un service OData local qui donne accès à la base de données Northwind. Si vous suivez la procédure de démarrage rapide, remplacez l'URI local par celui fourni dans l'exemple de code de cette rubrique.

Consommation d’un flux OData à l’aide des bibliothèques clientes

WCF Data Services inclut des bibliothèques clientes qui vous permettent de consommer plus facilement un flux OData depuis le .NET Framework et les applications clientes. Ces bibliothèques simplifient l'envoi et la réception des messages HTTP. Elles traduisent également la charge utile de message dans les objets CLR qui représentent des données d'entité. Les bibliothèques clientes comprennent les deux classes principales DataServiceContext et DataServiceQuery<TElement>. Ces classes vous permettent d'interroger un service de données, puis d'utiliser les données d'entité retournées sous forme d'objets CLR. Cette rubrique décrit deux approches de création d'activités qui utilisent les bibliothèques clientes.

Ajout d’une référence de service au service de données WCF

Pour générer les bibliothèques clientes Northwind, vous pouvez utiliser la boîte de dialogue Ajouter une référence de service dans Visual Studio 2012 pour ajouter une référence au service OData Northwind.

Capture d’écran de la boîte de dialogue Ajouter une référence de service.

Notez qu'aucune opération du service n'est exposée par le service et que la liste Services contient des éléments représentant les entités exposées par le service de données Northwind. Lorsqu'une référence de service est ajoutée, les classes sont générées pour ces entités et peuvent être utilisées dans le code client. Les exemples de cette rubrique utilisent ces classes et la classe NorthwindEntities pour exécuter les requêtes.

Utilisation de méthodes asynchrones

Pour résoudre les problèmes de latence possibles qui peuvent se produire lors de l'accès aux ressources sur le Web, il est recommandé d'accéder à WCF Data Services de façon asynchrone. Les bibliothèques clientes WCF Data Services contiennent des méthodes asynchrones pour appeler les requêtes, et Windows Workflow Foundation (WF) fournit la classe AsyncCodeActivity pour créer des activités asynchrones. Les activités dérivées AsyncCodeActivity peuvent être écrites pour tirer parti des classes .NET Framework qui ont des méthodes asynchrones, ou le code qui doit être exécuté de façon asynchrone peut être inséré dans une méthode et appelé à l’aide d’un délégué. Cette section contient deux exemples d'une activité dérivée AsyncCodeActivity ; une qui utilise les méthodes asynchrones des bibliothèques clientes WCF Data Services et une qui utilise un délégué.

Utilisation des méthodes asynchrones des bibliothèques clientes

La classe DataServiceQuery<TElement> fournit les méthodes BeginExecute et EndExecute pour interroger un service OData de façon asynchrone. Ces méthodes peuvent être appelées à partir des substitutions BeginExecute et EndExecute d'une classe dérivée AsyncCodeActivity . Si la substitution AsyncCodeActivity BeginExecute retourne, le workflow peut être inactif (mais pas persistant), et si la tâche asynchrone est terminée, la méthode EndExecute est appelée par le runtime.

Dans l'exemple suivant, une activité OrdersByCustomer est définie et contient deux arguments d'entrée. L'argument CustomerId représente le client qui identifie les commandes à retourner, et l'argument ServiceUri représente l'URI du service Odata à interroger. Comme l'activité dérive de AsyncCodeActivity<IEnumerable<Order>> , il y a aussi un argument de sortie Result , utilisé pour retourner le résultat de la requête. La substitution BeginExecute crée une requête LINQ qui sélectionne toutes les commandes du client spécifié. Cette requête est spécifiée comme UserState du AsyncCodeActivityContextpassé, puis la méthode BeginExecute de la requête est appelée. Notez que le rappel et l'état qui sont passés à la méthode BeginExecute de la requête sont ceux qui sont passés à la méthode BeginExecute de l'activité. Lorsque l'exécution de la requête est terminée, la méthode EndExecute de l'activité est appelée. La requête est récupérée dans la propriété UserState, puis la méthode EndExecute de la requête est appelée. Cette méthode retourne un IEnumerable<T> du type d'entité spécifié, dans ce cas Order. IEnumerable<Order> étant le type générique de AsyncCodeActivity<TResult>, ce IEnumerable est défini comme Result OutArgument<T> de l'activité.

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);
    }
}

Dans l'exemple suivant, l'activité OrdersByCustomer récupère une liste de commandes pour le client spécifié, puis une activité ForEach<T> énumère les commandes retournées et écrit la date de chacune d'elles dans la console.

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);

Lorsque ce workflow est appelé, les données suivantes sont écrites dans la console :

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

Notes

S'il est impossible d'établir une connexion au serveur OData, vous obtiendrez une exception semblable à la suivante :

Exception non gérée : System.InvalidOperationException : Une erreur s'est produite lors du traitement de cette requête. ---> System.Net.WebException : Impossible de se connecter au serveur distant. ---> System.Net.Sockets.SocketException : Une tentative de connexion a échoué car la partie connectée n’a pas répondu convenablement au-delà d’une certaine durée ou une connexion établie a échoué car l’hôte de connexion n’a pas répondu.

Si un traitement supplémentaire des données retournées par la requête est nécessaire, il peut être effectué dans la substitution EndExecute de l'activité. Les méthodes BeginExecute et EndExecute sont appelées à l'aide du thread de workflow, et le code de ces substitutions ne s'exécute pas de façon asynchrone. Si le traitement supplémentaire est étendu ou de longue durée, ou les résultats de la requête sont paginés, vous devez envisager d'adopter l'approche décrite dans la section suivante. Cette approche utilise un délégué pour exécuter la requête et effectuer le traitement supplémentaire de façon asynchrone.

Utilisation d’un délégué

Outre appeler la méthode asynchrone d’une classe .NET Framework, une activité basée sur une AsyncCodeActivity peut aussi définir la logique asynchrone dans l’une de ses méthodes. Cette méthode est spécifiée en utilisant un délégué dans la substitution BeginExecute de l'activité. Lorsque la méthode retourne, le runtime appelle la substitution EndExecute de l'activité. Lors de l'appel d'un service OData d'un workflow, cette méthode peut être utilisée pour interroger le service et fournir un traitement supplémentaire.

Dans l'exemple suivant, une activité ListCustomers est définie. Cette activité interroge l'exemple de service de données Northwind et retourne un List<Customer> qui contient tous les clients dans la base de données Northwind. La tâche asynchrone est effectuée par la méthode GetCustomers . Cette méthode interroge le service pour tous les clients, puis les copie dans un List<Customer>. Elle vérifie ensuite si les résultats sont paginés. Le cas échéant, elle interroge le service pour la page suivante de résultats, les ajoute à la liste et continue jusqu'à ce que tous les clients aient été récupérés.

Notes

Pour plus d’informations sur la pagination dans WCF Data Services, consultez Procédure : Charger des résultats paginés (WCF Data Services).

Une fois tous les clients ajoutés, la liste est retournée. La méthode GetCustomers est spécifiée dans la substitution BeginExecute de l'activité. Comme la méthode a une valeur de retour, un Func<string, List<Customer>> est créé pour spécifier la méthode.

Notes

Si la méthode qui effectue la tâche asynchrone n'a pas de valeur de retour, un Action est utilisé à la place d'un Func<TResult>. Pour obtenir des exemples de création d’un exemple asynchrone à l’aide des deux approches, consultez Création d’activités asynchrones.

Ce Func<TResult> est assigné au UserState, puis BeginInvoke est appelé. Comme la méthode à appeler n'a pas accès à l'environnement des arguments de l'activité, la valeur de l'argument ServiceUri est passée comme premier paramètre, avec le rappel et l'état passés dans la méthode BeginExecute. Lorsque GetCustomers retourne, le runtime appelle EndExecute. Le code dans la méthode EndExecute récupère le délégué à partir de la propriété UserState, appelle EndInvoke, et retourne le résultat, qui correspond à la liste des clients retournés à partir de la méthode 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;
    }
}

Dans l'exemple suivant, l'activité ListCustomers récupère une liste de clients, puis une activité ForEach<T> les énumère et écrit le nom de la société et du contact de chacun des clients dans la console.

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);

Lorsque ce workflow est appelé, les données suivantes sont écrites dans la console : Comme cette requête retourne de nombreux clients, une seule partie du résultat s'affiche ici.

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
...

Consommation d’un flux OData sans utiliser les bibliothèques clientes

OData expose les données sous forme de ressources adressables par des URI. Lorsque vous utilisez les bibliothèques clientes, ces URI sont créés pour vous, mais vous n'avez pas besoin d'utiliser les bibliothèques clientes. Si vous le souhaitez, vous pouvez accéder aux services OData directement sans utiliser les bibliothèques clientes. Le cas échéant, l'emplacement du service et les données voulues sont spécifiés par l'URI et les résultats sont retournés dans la réponse à la requête HTTP. Vous pouvez ensuite traiter ou manipuler ces données brutes de la façon appropriée. Pour récupérer les résultats d'une requête OData, vous pouvez utiliser la classe WebClient . Dans cet exemple, le contact du client représenté par la clé ALFKI est récupéré.

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);

Lorsque ce code s'exécute, la sortie suivante s'affiche sur la console :

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>

Dans un workflow, le code de cet exemple peut être incorporé dans la substitution Execute d'une activité personnalisée basée sur CodeActivity, mais cette même fonctionnalité peut aussi être obtenue à l'aide de l'activité InvokeMethod<TResult>. L'activité InvokeMethod<TResult> permet aux auteurs de workflow d'appeler des méthodes d'instance et statiques d'une classe, ainsi que d'appeler la méthode spécifiée de façon asynchrone. Dans l'exemple suivant, une activité InvokeMethod<TResult> est configurée pour appeler la méthode DownloadString de la classe WebClient et retourner une liste de clients.

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> peut appeler à la fois des méthodes d'instance et statiques d'une classe. Comme DownloadString est une méthode d'instance de la classe WebClient , une nouvelle instance de la classe WebClient est spécifiée pour TargetObject. DownloadString est spécifiée pour la propriété MethodName, l'URI qui contient la requête est spécifié dans la collection Parameters , et la propriété Result est affectée à la valeur de retour. La valeur RunAsynchronously est affectée à la propriété true, ce qui signifie que l'appel de la méthode s'exécutera de façon asynchrone par rapport au workflow. Dans l'exemple suivant, un workflow est construit qui utilise l'activité InvokeMethod<TResult> pour interroger l'exemple de service de données Northwind pour une liste de commandes d'un client spécifique, puis les données retournées sont écrites dans la console.

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);

Lorsque ce workflow est appelé, la sortie suivante s'affiche sur la console. Comme cette requête retourne plusieurs commandes, une seule partie du résultat s'affiche ici.

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" />
...

Cet exemple fournit une méthode que les auteurs d'applications de workflow peuvent utiliser pour consommer les données brutes retournées par un service OData. Pour plus d’informations sur l’accès à WCF Data Services à l’aide d’URI, consultez Accès aux ressources de service de données (WCF Data Services) et OData : conventions d’URI.