Chiamata di un servizio OData da un client .NET (C#)

di Mike Wasson

Scaricare il progetto completato

Questa esercitazione illustra come chiamare un servizio OData da un'applicazione client C#.

Versioni software usate nell'esercitazione

In questa esercitazione verrà illustrata la creazione di un'applicazione client che chiama un servizio OData. Il servizio OData espone le entità seguenti:

  • Product
  • Supplier
  • ProductRating

Diagramma che mostra le entità del servizio dati O e un elenco delle relative proprietà, con frecce di connessione per mostrare come ogni relazione o lavorare insieme.

Gli articoli seguenti descrivono come implementare il servizio OData nell'API Web. Non è tuttavia necessario leggerli per comprendere questa esercitazione.

Generare il proxy del servizio

Il primo passaggio consiste nel generare un proxy di servizio. Il proxy del servizio è una classe .NET che definisce i metodi per l'accesso al servizio OData. Il proxy converte le chiamate al metodo in richieste HTTP.

Diagramma che mostra le chiamate di richiesta H T P del proxy di servizio in esecuzione dall'applicazione, tramite il proxy del servizio e al servizio dati O.

Iniziare aprendo il progetto di servizio OData in Visual Studio. Premere CTRL+F5 per eseguire il servizio in locale in IIS Express. Si noti l'indirizzo locale, incluso il numero di porta assegnato da Visual Studio. È necessario questo indirizzo quando si crea il proxy.

Aprire quindi un'altra istanza di Visual Studio e creare un progetto di applicazione console. L'applicazione console sarà l'applicazione client OData. È anche possibile aggiungere il progetto alla stessa soluzione del servizio.

Nota

I passaggi rimanenti fanno riferimento al progetto console.

In Esplora soluzioni fare clic con il pulsante destro del mouse su Riferimenti e scegliere Aggiungi riferimento al servizio.

Screenshot della finestra Esplora soluzioni, che mostra il menu in

Nella finestra di dialogo Aggiungi riferimento al servizio digitare l'indirizzo del servizio OData:

http://localhost:port/odata

dove la porta è il numero di porta.

Screenshot della finestra

Per Spazio dei nomi digitare "ProductService". Questa opzione definisce lo spazio dei nomi della classe proxy.

Fare clic su Vai. Visual Studio legge il documento dei metadati OData per individuare le entità nel servizio.

Screenshot della finestra di dialogo

Fare clic su OK per aggiungere la classe proxy al progetto.

Screenshot della finestra di dialogo Esplora soluzioni, che mostra il menu sotto il

Creare un'istanza della classe proxy del servizio

Main All'interno del metodo creare una nuova istanza della classe proxy, come indicato di seguito:

using System;
using System.Data.Services.Client;
using System.Linq;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:1234/odata/");
            var container = new ProductService.Container(uri);

            // ...
        }
    }
}

Usare di nuovo il numero di porta effettivo in cui è in esecuzione il servizio. Quando si distribuisce il servizio, si userà l'URI del servizio live. Non è necessario aggiornare il proxy.

Il codice seguente aggiunge un gestore eventi che stampa gli URI della richiesta nella finestra della console. Questo passaggio non è obbligatorio, ma è interessante vedere gli URI per ogni query.

container.SendingRequest2 += (s, e) =>
{
    Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};

Eseguire query sul servizio

Il codice seguente ottiene l'elenco di prodotti dal servizio OData.

class Program
{
    static void DisplayProduct(ProductService.Product product)
    {
        Console.WriteLine("{0} {1} {2}", product.Name, product.Price, product.Category);
    }

    // Get an entire entity set.
    static void ListAllProducts(ProductService.Container container)
    {
        foreach (var p in container.Products)
        {
            DisplayProduct(p);
        } 
    }
  
    static void Main(string[] args)
    {
        Uri uri = new Uri("http://localhost:18285/odata/");
        var container = new ProductService.Container(uri);
        container.SendingRequest2 += (s, e) =>
        {
            Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
        };

        // Get the list of products
        ListAllProducts(container);
    }
}

Si noti che non è necessario scrivere codice per inviare la richiesta HTTP o analizzare la risposta. La classe proxy esegue questa operazione automaticamente quando si enumera la Container.Products raccolta nel ciclo foreach .

Quando si esegue l'applicazione, l'output dovrebbe essere simile al seguente:

GET http://localhost:60868/odata/Products
Hat 15.00   Apparel
Scarf   12.00   Apparel
Socks   5.00    Apparel
Yo-yo   4.95    Toys
Puzzle  8.00    Toys

Per ottenere un'entità in base all'ID, usare una where clausola.

// Get a single entity.
static void ListProductById(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        DisplayProduct(product);
    }
}

Per il resto di questo argomento, non visualizzerò l'intera Main funzione, solo il codice necessario per chiamare il servizio.

Applica opzioni di query

OData definisce le opzioni di query che possono essere usate per filtrare, ordinare, visualizzare dati di pagina e così via. Nel proxy del servizio è possibile applicare queste opzioni usando varie espressioni LINQ.

In questa sezione verranno illustrati brevi esempi. Per altre informazioni, vedere l'argomento CONSIDERAZIONI LINQ (WCF Data Services) in MSDN.

Filtro ($filter)

Per filtrare, usare una where clausola. Nell'esempio seguente viene filtrato in base alla categoria di prodotti.

// Use the $filter option.
static void ListProductsInCategory(ProductService.Container container, string category)
{
    var products =
        from p in container.Products
        where p.Category == category
        select p;
    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

Questo codice corrisponde alla query OData seguente.

GET http://localhost/odata/Products()?$filter=Category eq 'apparel'

Si noti che il proxy converte la clausola in un'espressione where OData $filter .

Ordinamento ($orderby)

Per ordinare, usare una orderby clausola. L'esempio seguente ordina per prezzo, dal più alto al più basso.

// Use the $orderby option
static void ListProductsSorted(ProductService.Container container)
{
    // Sort by price, highest to lowest.
    var products =
        from p in container.Products
        orderby p.Price descending
        select p;

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

Ecco la richiesta OData corrispondente.

GET http://localhost/odata/Products()?$orderby=Price desc

paging Client-Side ($skip e $top)

Per set di entità di grandi dimensioni, il client potrebbe voler limitare il numero di risultati. Ad esempio, un client potrebbe visualizzare 10 voci alla volta. Viene chiamato paging lato client. È disponibile anche il paging lato server, in cui il server limita il numero di risultati. Per eseguire il paging lato client, usare i metodi LINQ Skip e Take . L'esempio seguente ignora i primi 40 risultati e accetta il successivo 10.

// Use $skip and $top options.
static void ListProductsPaged(ProductService.Container container)
{
    var products =
        (from p in container.Products
          orderby p.Price descending
          select p).Skip(40).Take(10);

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

Ecco la richiesta OData corrispondente:

GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10

Selezionare ($select) e Espandi ($expand)

Per includere entità correlate, usare il DataServiceQuery<t>.Expand metodo . Ad esempio, per includere l'oggetto Supplier per ogni Productoggetto :

// Use the $expand option.
static void ListProductsAndSupplier(ProductService.Container container)
{
    var products = container.Products.Expand(p => p.Supplier);
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}\t{2}", p.Name, p.Price, p.Supplier.Name);
    }
}

Ecco la richiesta OData corrispondente:

GET http://localhost/odata/Products()?$expand=Supplier

Per modificare la forma della risposta, usare la clausola di selezione LINQ. L'esempio seguente ottiene solo il nome di ogni prodotto, senza altre proprietà.

// Use the $select option.
static void ListProductNames(ProductService.Container container)
{

    var products = from p in container.Products select new { Name = p.Name };
    foreach (var p in products)
    {
        Console.WriteLine(p.Name);
    }
}

Ecco la richiesta OData corrispondente:

GET http://localhost/odata/Products()?$select=Name

Una clausola select può includere entità correlate. In questo caso, non chiamare Espandi; il proxy include automaticamente l'espansione in questo caso. L'esempio seguente ottiene il nome e il fornitore di ogni prodotto.

// Use $expand and $select options
static void ListProductNameSupplier(ProductService.Container container)
{
    var products =
        from p in container.Products
        select new
        {
            Name = p.Name,
            Supplier = p.Supplier.Name
        };
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}", p.Name, p.Supplier);
    }
}

Ecco la richiesta OData corrispondente. Si noti che include l'opzione $expand .

GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name

Per altre informazioni su $select e $expand, vedere Uso di $select, $expand e $value nell'API Web 2.

Aggiungere una nuova entità

Per aggiungere una nuova entità a un set di entità, chiamare AddToEntitySet, dove EntitySet è il nome del set di entità. Ad esempio, AddToProducts aggiunge un nuovo Product al set di Products entità. Quando si genera il proxy, WCF Data Services crea automaticamente questi metodi AddTo fortemente tipizzato.

// Add an entity.
static void AddProduct(ProductService.Container container, ProductService.Product product)
{
    container.AddToProducts(product);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

Per aggiungere un collegamento tra due entità, usare i metodi AddLink e SetLink . Il codice seguente aggiunge un nuovo fornitore e un nuovo prodotto e quindi crea collegamenti tra di essi.

// Add entities with links.
static void AddProductWithSupplier(ProductService.Container container, 
    ProductService.Product product, ProductService.Supplier supplier)
{
    container.AddToSuppliers(supplier);
    container.AddToProducts(product);
    container.AddLink(supplier, "Products", product);
    container.SetLink(product, "Supplier", supplier);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

Utilizzare AddLink quando la proprietà di navigazione è un insieme. In questo esempio viene aggiunto un prodotto alla Products raccolta sul fornitore.

Usare SetLink quando la proprietà di navigazione è una singola entità. In questo esempio viene impostata la Supplier proprietà sul prodotto.

Aggiornamento/patch

Per aggiornare un'entità, chiamare il metodo UpdateObject .

static void UpdatePrice(ProductService.Container container, int id, decimal price)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    { 
        product.Price = price;
        container.UpdateObject(product);
        container.SaveChanges(SaveChangesOptions.PatchOnUpdate);
    }
}

L'aggiornamento viene eseguito quando si chiama SaveChanges. Per impostazione predefinita, WCF invia una richiesta HTTP MERGE. L'opzione PatchOnUpdate indica a WCF di inviare invece una PATCH HTTP.

Nota

Perché PATCH e MERGE? La specifica HTTP 1.1 originale (RCF 2616) non ha definito alcun metodo HTTP con semantica di "aggiornamento parziale". Per supportare gli aggiornamenti parziali, la specifica OData ha definito il metodo MERGE. Nel 2010 RFC 5789 ha definito il metodo PATCH per gli aggiornamenti parziali. È possibile leggere alcune delle cronologie in questo post di blog sul blog di WCF Data Services. Attualmente PATCH è preferibile rispetto a MERGE. Il controller OData creato dallo scaffolding dell'API Web supporta entrambi i metodi.

Se si vuole sostituire l'intera entità (semantica PUT), specificare l'opzione ReplaceOnUpdate . In questo modo WCF invia una richiesta HTTP PUT.

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

Eliminare un'entità

Per eliminare un'entità, chiamare DeleteObject.

static void DeleteProduct(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        container.DeleteObject(product);
        container.SaveChanges();
    }
}

Richiamare un'azione OData

In OData le azioni sono un modo per aggiungere comportamenti lato server che non sono facilmente definiti come operazioni CRUD sulle entità.

Anche se il documento dei metadati OData descrive le azioni, la classe proxy non crea metodi fortemente tipizzato per tali azioni. È comunque possibile richiamare un'azione OData usando il metodo Execute generico. Tuttavia, sarà necessario conoscere i tipi di dati dei parametri e il valore restituito.

Ad esempio, l'azione accetta il RateProduct parametro "Rating" di tipo Int32 e restituisce un oggetto double. Nel codice seguente viene illustrato come richiamare questa azione.

int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
    actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();

Per altre informazioni, vedereChiamata di operazioni e azioni del servizio.

Un'opzione consiste nell'estendere la classe Container per fornire un metodo fortemente tipizzato che richiama l'azione:

namespace ProductServiceClient.ProductService
{
    public partial class Container
    {
        public double RateProduct(int productID, int rating)
        {
            Uri actionUri = new Uri(this.BaseUri,
                String.Format("Products({0})/RateProduct", productID)
                );

            return this.Execute<double>(actionUri, 
                "POST", true, new BodyOperationParameter("Rating", rating)).First();
        }
    }
}