Utilizzo di feed OData da un flusso di lavoro
WCF Data Services è un componente di .NET Framework che consente di creare servizi che usano Open Data Protocol (OData) per esporre e usare dati sul Web o su rete Intranet tramite la semantica REST (Representational State Transfer). In OData i dati vengono esposti come risorse indirizzabili tramite URI. Un'applicazione può interagire con un servizio dati basato su OData quando è in grado di inviare una richiesta HTTP e di elaborare il feed OData restituito da un servizio dati. In WCF Data Services sono inoltre disponibili librerie client che consentono di programmare in modo più completo quando si usano feed OData da applicazioni .NET Framework. In questo argomento viene fornita una panoramica sull'uso di un feed OData in un flusso di lavoro con e senza librerie client.
Utilizzo del servizio OData Northwind di esempio
Negli esempi di questo argomento viene usato il servizio dati Northwind di esempio disponibile all'indirizzo https://services.odata.org/Northwind/Northwind.svc/. Il servizio viene fornito come parte di OData SDK e consente di accedere in sola lettura al database Northwind di esempio. Se si desidera ottenere l'accesso in scrittura o un'istanza di WCF Data Services locale, seguire la procedura descritta in Guida rapida (WCF Data Services) per creare un servizio OData locale che consenta di accedere al database Northwind. Se si eseguono le operazioni indicate nella guida rapida, sostituire l'URI locale a quello indicato nel codice di esempio in questo argomento.
Uso di un feed OData tramite le librerie client
In WCF Data Services sono disponibili librerie client che consentono di usare con più facilità un feed OData da applicazioni .NET Framework e client. Queste librerie semplificano l'invio e la ricezione di messaggi HTTP, oltre a convertire il payload del messaggio in oggetti CLR che rappresentano dati di entità. Le librerie client rendono disponibili le due classi principali DataServiceContext e DataServiceQuery<TElement> che consentono di eseguire una query su un servizio dati e di usare quindi i dati di entità restituiti come oggetti CLR. Contenuto della sezione vengono descritti due approcci alla creazione di attività che usano le librerie client.
Aggiunta di un riferimento al servizio a WCF Data Services
Per generare le librerie client Northwind, è possibile usare la finestra di dialogo Aggiungi riferimento al servizio in Visual Studio 2012 per aggiungere un riferimento al servizio Northwind OData.
Si noti che non sono presenti operazioni del servizio esposte dal servizio stesso e che nell'elenco Servizi sono contenuti elementi che rappresentano le entità esposte dal servizio dati Northwind. Quando si aggiunge il riferimento al servizio, le classi verranno generate per queste entità e sarà possibile usarle nel codice client. Negli esempi di questo argomento vengono usate tali classi e la classe NorthwindEntities
per eseguire le query.
Nota
Per altre informazioni, vedere Generazione della libreria dati del servizio dati (WCF Data Services).
Uso di metodi asincroni
Per risolvere possibili problemi di latenza che potrebbero verificarsi quando si accede a risorse sul Web, è consigliabile accedere a WCF Data Services in modo asincrono. Le librerie client di WCF Data Services includono metodi asincroni per richiamare query e Windows Workflow Foundation (WF) fornisce la classe AsyncCodeActivity per creare attività asincrone. È possibile scrivere attività derivate da AsyncCodeActivity per sfruttare le classi di .NET Framework che dispongono di metodi asincroni oppure è possibile inserire il codice da eseguire in modo asincrono in un metodo e richiamarlo usando un delegato. Contenuto della sezione vengono forniti due esempi di un'attività derivata AsyncCodeActivity : in uno vengono usati i metodi asincroni delle librerie client di WCF Data Services e nell'altro viene usato un delegato.
Nota
Per altre informazioni, vedere Operazioni asincrone (WCF Data Services) e Creazione di attività asincrone.
Uso di metodi asincroni di librerie client
La classe DataServiceQuery<TElement> fornisce i metodi BeginExecute e EndExecute per l'esecuzione asincrona di una query su un servizio OData. Tali metodi possono essere chiamati da BeginExecute , mentre EndExecute esegue l'override di una classe AsyncCodeActivity derivata. Quando viene restituito l'override AsyncCodeActivity BeginExecute, il flusso di lavoro può diventare inattivo (ma non persistente) e, dopo il completamento delle operazioni asincrone, il metodo EndExecute viene richiamato dal runtime.
Nell'esempio seguente viene definita un'attività OrdersByCustomer
che dispone di due argomenti di input. L'argomento CustomerId
rappresenta il cliente che identifica gli ordini da restituire, mentre l'argomento ServiceUri
rappresenta l'URI del servizio OData su cui eseguire una query. Poiché l'attività deriva da AsyncCodeActivity<IEnumerable<Order>>
, è presente anche un argomento di output Result usato per restituire i risultati della query. L'override BeginExecute crea una query LINQ che seleziona tutti gli ordini del cliente specificato. Tale query viene specificata come la proprietà UserState dell'oggetto AsyncCodeActivityContextpassato e successivamente viene chiamato il metodo BeginExecute della query. Si noti che il callback e lo stato passati al metodo BeginExecute della query sono gli stessi passati al metodo BeginExecute dell'attività. Dopo che la query è stata eseguita, viene richiamato il metodo EndExecute dell'attività. Successivamente, la query viene recuperata da UserStatee viene chiamato il metodo EndExecute della query che restituisce un oggetto IEnumerable<T> del tipo di entità specificato, in questo caso Order
. Poiché IEnumerable<Order>
è il tipo generico di AsyncCodeActivity<TResult>, questo elemento IEnumerable viene impostato come Result OutArgument<T> dell'attività.
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);
}
}
Nell'esempio seguente l'attività OrdersByCustomer
recupera un elenco di ordini per il cliente specificato e successivamente un'attività ForEach<T> enumera gli ordini restituiti e scrive la data di ogni ordine nella 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);
Quando questo flusso di lavoro viene richiamato, nella console vengono scritti i dati seguenti:
Calling WCF Data Service...
8/25/1997
10/3/1997
10/13/1997
1/15/1998
3/16/1998
4/9/1998
Nota
Se non è possibile stabilire una connessione al server OData, verrà generata un'eccezione analoga alla seguente:
Eccezione non gestita: System.InvalidOperationException: Errore durante l'elaborazione della richiesta. ---> System.Net.WebException: Impossibile effettuare la connessione al server remoto ---> System.Net.Sockets.SocketException: Tentativo di connessione non riuscito. Risposta non corretta della parte connessa dopo un intervallo di tempo oppure mancata risposta dall'host connesso.
Se è necessaria un'elaborazione aggiuntiva dei dati restituiti dalla query, è possibile usare l'override EndExecute dell'attività. Sia BeginExecute che EndExecute vengono richiamati tramite il thread del flusso di lavoro e nessun codice in tali override viene eseguito in modo asincrono. Se l'elaborazione aggiuntiva è estesa o di lunga durata o se viene eseguito il paging dei risultati della query, è opportuno prendere in considerazione l'approccio discusso nella sezione seguente in cui viene usato un delegato per eseguire la query e completare l'elaborazione aggiuntiva in modo asincrono.
Uso di un delegato
Oltre a richiamare il metodo asincrono di una classe .NET Framework, un'attività basata su AsyncCodeActivity può definire anche la logica asincrona in uno dei relativi metodi. Questo metodo viene specificato tramite un delegato nell'override BeginExecute dell'attività. Quando il metodo restituisce un risultato, il runtime richiama l'override EndExecute dell'attività. In caso di chiamata di un servizio OData da un flusso di lavoro, il metodo può essere usato per eseguire una query sul servizio e completare eventuali elaborazioni aggiuntive.
Nell'esempio seguente viene definita un'attività ListCustomers
. che esegue una query sul servizio dati Northwind di esempio e restituisce un elemento List<Customer>
che contiene tutti i clienti del database Northwind. Il lavoro asincrono viene eseguito dal metodo GetCustomers
che esegue una query sul servizio per tutti i clienti, li copia in un elemento List<Customer>
e controlla se è stato eseguito il paging dei risultati. In caso affermativo, esegue una query sul servizio per la successiva pagina di risultati, li aggiunge all'elenco e continua fino a quando non sono stati recuperati tutti i dati del cliente.
Nota
Per altre informazioni sul paging in WCF Data Services, vedere Procedura: Caricare risultati di paging (WCF Data Services).
Una volta aggiunti tutti i clienti, viene restituito l'elenco. Il metodo GetCustomers
viene specificato nell'override BeginExecute dell'attività. Poiché il metodo restituisce un valore, viene creato un elemento Func<string, List<Customer>>
per specificarlo.
Nota
Se il metodo che esegue il lavoro asincrono non restituisce un valore, viene usato un oggetto Action anziché un oggetto Func<TResult>. Per esempi di creazione di un esempio asincrono usando entrambi gli approcci, vedere Creazione di attività asincrone.
Questo oggetto Func<TResult> viene assegnato a UserState, quindi viene chiamato BeginInvoke
. Poiché il metodo da richiamare non dispone dell'accesso all'ambiente di argomenti dell'attività, il valore dell'argomento ServiceUri
viene passato come primo parametro, insieme con il callback e lo stato passati a BeginExecute. Quando GetCustomers
restituisce un risultato, il runtime richiama EndExecute. Il codice in EndExecute recupera il delegato da UserState, chiama EndInvoke
e restituisce il risultato, ovvero l'elenco di clienti restituito dal metodo 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;
}
}
Nell'esempio seguente l'attività ListCustomers
recupera un elenco di clienti e successivamente un'attività ForEach<T> li enumera e scrive il nome dell'azienda e del contatto di ogni cliente nella 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);
Quando questo flusso di lavoro viene richiamato, nella console vengono scritti i dati seguenti. Poiché la query restituisce numerosi clienti, in questo contesto viene visualizzata solo una parte dell'output.
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
...
Uso di un feed OData senza le librerie client
In OData i dati vengono esposti come risorse indirizzabili tramite URI. Tali URI vengono creati automaticamente quando si usano le librerie client, sebbene queste ultime non siano sempre necessarie. È infatti possibile accedere direttamente ai servizi OData senza usare le librerie client. In questo caso il percorso del servizio e i dati desiderati vengono specificati dagli URI e i risultati vengono restituiti nella risposta alla richiesta HTTP. Tali dati possono quindi essere elaborati o modificati nel modo desiderato. Per recuperare i risultati di una query OData, è possibile usare la classe WebClient . In questo esempio viene recuperato il nome del contatto per il cliente rappresentato dalla chiave 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);
Quando questo codice viene eseguito, nella console viene visualizzato l'output seguente:
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>
In un flusso di lavoro il codice dell'esempio potrebbe essere incorporato nell'override Execute di un'attività personalizzata basata su CodeActivity, ma la stessa funzionalità può essere realizzata anche tramite l'attività InvokeMethod<TResult>. L'attività InvokeMethod<TResult> consente agli autori del flusso di lavoro di richiamare metodi statici e di istanza di una classe e dispone inoltre di un'opzione per richiamare il metodo specificato in modo asincrono. Nell'esempio seguente un'attività InvokeMethod<TResult> viene configurata per chiamare il metodo DownloadString della classe WebClient e per restituire un elenco di clienti.
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> può chiamare sia metodi statici che di istanza di una classe. Poiché DownloadString è un metodo di istanza della classe WebClient , viene specificata una nuova istanza della classe WebClient per TargetObject. DownloadString
viene specificato come MethodName, l'URI che contiene la query viene specificato nella raccolta Parameters e il valore restituito viene assegnato al valore di Result . Il valore RunAsynchronously viene impostato su true
per indicare che la chiamata al metodo verrà eseguita in modo asincrono rispetto al flusso di lavoro. Nell'esempio seguente viene creato un flusso di lavoro che usa l'attività InvokeMethod<TResult> per eseguire una query sul servizio dati Northwind di esempio per ottenere un elenco di ordini per un cliente specifico. Successivamente i dati restituiti vengono scritti nella 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);
Quando questo flusso di lavoro viene richiamato, nella console viene visualizzato l'output seguente. Poiché la query restituisce numerosi ordini, in questo contesto viene visualizzata solo una parte dell'output.
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" />
...
L'esempio fornisce un metodo a disposizione degli autori delle applicazioni flusso di lavoro per usare i dati non elaborati restituiti da un servizio OData. Per altre informazioni sull'accesso a WCF Data Services tramite gli URI, vedere Accesso alle risorse del servizio dati (WCF Data Services) e OData: convenzioni URI.