Llamar a un servicio OData desde un cliente .NET (C#)
por Mike Wasson
Descargar el proyecto completado
En este tutorial se muestra cómo llamar a un servicio OData desde una aplicación cliente de C#.
Versiones de software usadas en el tutorial
- Visual Studio 2013 (funciona con Visual Studio 2012)
- Biblioteca cliente de Servicios de datos de WCF
- Web API 2. (El servicio OData de ejemplo se compila con Web API 2, pero la aplicación cliente no depende de la API web).
En este tutorial, le guiaré por la creación de una aplicación cliente que llama a un servicio OData. El servicio OData expone las siguientes entidades:
Product
Supplier
ProductRating
En los artículos siguientes se describe cómo implementar el servicio OData en la API web. (Sin embargo, no es necesario leerlos para comprender este tutorial).
- Crear un punto de conexión de OData en Web API 2
- Relaciones de entidades de OData en Web API 2
- Acciones de OData en Web API 2
Generación del proxy de servicio
El primer paso es generar un proxy de servicio. El proxy de servicio es una clase .NET que define métodos para acceder al servicio OData. El proxy traduce las llamadas de método a solicitudes HTTP.
Para empezar, abra el proyecto de servicio OData en Visual Studio. Presione CTRL+F5 para ejecutar el servicio localmente en IIS Express. Anote la dirección local, incluido el número de puerto que asigna Visual Studio. Necesitará esta dirección al crear el proxy.
A continuación, abra otra instancia de Visual Studio y cree un proyecto de aplicación de consola. La aplicación de consola será nuestra aplicación cliente de OData. (También puede agregar el proyecto a la misma solución que el servicio).
Nota:
Los pasos restantes hacen referencia al proyecto de consola.
En el Explorador de soluciones, haga clic con el botón derecho en Referencias y seleccione Agregar referencia de servicio.
En el cuadro de diálogo Agregar referencia de servicio, escriba la dirección del servicio OData:
http://localhost:port/odata
donde puerto es el número de puerto.
En Espacio de nombres, escriba "ProductService". Esta opción define el espacio de nombres de la clase de proxy.
Haga clic en Ir. Visual Studio lee el documento de metadatos de OData para detectar las entidades en el servicio.
Haga clic en Aceptar para agregar la clase de proxy al proyecto.
Crear una instancia de la clase de proxy de servicio
Dentro del método Main
, cree una nueva instancia de la clase de proxy, como se indica a continuación:
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);
// ...
}
}
}
De nuevo, use el número de puerto real en el que se ejecuta el servicio. Al implementar el servicio, usará el URI del servicio activo. No es necesario actualizar el proxy.
El código siguiente agrega un controlador de eventos que imprime los URI de solicitud en la ventana de la consola. Este paso no es necesario, pero es interesante ver los URI de cada consulta.
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
Consulta del servicio
El código siguiente obtiene la lista de productos del servicio 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);
}
}
Tenga en cuenta que no es necesario escribir ningún código para enviar la solicitud HTTP o analizar la respuesta. La clase de proxy lo hace automáticamente al enumerar la colección Container.Products
en el bucle foreach.
Al ejecutar la aplicación, la salida debe ser similar a la siguiente:
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
Para obtener una entidad por id., use una cláusula where
.
// 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);
}
}
En el resto de este tema, no mostraré toda la función Main
, solo el código necesario para llamar al servicio.
Aplicar opciones de consulta
OData define las opciones de consulta que se pueden usar para filtrar, ordenar, paginar datos, etc. En el proxy de servicio, puede aplicar estas opciones mediante varias expresiones LINQ.
En esta sección, mostraré breves ejemplos. Para obtener más información, consulte el tema Consideraciones de LINQ (Servicios de datos de WCF) en MSDN.
Filtrado ($filter)
Para filtrar, use una cláusula where
. El siguiente ejemplo filtra por categoría de producto.
// 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);
}
}
Este código corresponde a la siguiente consulta de OData.
GET http://localhost/odata/Products()?$filter=Category eq 'apparel'
Observe que el proxy convierte la where
cláusula en una expresión OData $filter
.
Ordenación ($orderby)
Para ordenar, use una cláusula orderby
. En el ejemplo siguiente se ordena por precio, de mayor a menor.
// 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);
}
}
Esta es la solicitud de OData correspondiente.
GET http://localhost/odata/Products()?$orderby=Price desc
Paginación del lado cliente ($skip y $top)
En el caso de los conjuntos de entidades grandes, es posible que el cliente quiera limitar el número de resultados. Por ejemplo, un cliente podría mostrar 10 entradas a la vez. Esto se denomina paginación del lado cliente. (También hay paginación del lado servidor, donde el servidor limita el número de resultados). Para realizar la paginación del lado cliente, use los métodos LINQ Skip y Take. En el ejemplo siguiente se omiten los primeros 40 resultados y se toman los 10 siguientes.
// 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);
}
}
Esta es la solicitud de OData correspondiente:
GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10
Seleccione ($select) y expanda ($expand)
Para incluir entidades relacionadas, use el método DataServiceQuery<t>.Expand
. Por ejemplo, para incluir el Supplier
para cada Product
:
// 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);
}
}
Esta es la solicitud de OData correspondiente:
GET http://localhost/odata/Products()?$expand=Supplier
Para cambiar la forma de la respuesta, use la cláusula select de LINQ. En el ejemplo siguiente se obtiene solo el nombre de cada producto, sin otras propiedades.
// 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);
}
}
Esta es la solicitud de OData correspondiente:
GET http://localhost/odata/Products()?$select=Name
Una cláusula select puede incluir entidades relacionadas. En ese caso, no llame a Expandir; el proxy incluye automáticamente la expansión en este caso. En el ejemplo siguiente se obtiene el nombre y el proveedor de cada producto.
// 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);
}
}
Esta es la solicitud de OData correspondiente. Observe que incluye la opción $expand.
GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name
Para obtener más información sobre $select y $expand, consulte Uso de $select, $expand y $value en Web API 2.
Agregue una nueva entidad
Para agregar una nueva entidad a un conjunto de entidades, llame a AddToEntitySet
, donde EntitySet es el nombre del conjunto de entidades. Por ejemplo, AddToProducts
agrega un nuevo Product
al conjunto de entidades Products
. Al generar el proxy, Servicios de datos de WCF crea automáticamente estos métodos AddTo fuertemente tipados.
// 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);
}
}
Para agregar un vínculo entre dos entidades, use los métodos AddLink y SetLink. El código siguiente agrega un nuevo proveedor y un nuevo producto y, a continuación, crea vínculos entre ellos.
// 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);
}
}
Use AddLink cuando la propiedad de navegación sea una colección. En este ejemplo, vamos a agregar un producto a la colección Products
en el proveedor.
Use SetLink cuando la propiedad de navegación sea una sola entidad. En este ejemplo, vamos a establecer la propiedad Supplier
en el producto.
Actualizar o aplicar revisiones
Para actualizar una entidad, llame al método 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);
}
}
La actualización se realiza cuando se llama a SaveChanges. De forma predeterminada, WCF envía una solicitud HTTP MERGE. La opción PatchOnUpdate indica a WCF que envíe un HTTP PATCH en su lugar.
Nota:
¿Por qué PATCH frente a MERGE? La especificación HTTP 1.1 original (RCF 2616) no definió ningún método HTTP con semántica de "actualización parcial". Para admitir actualizaciones parciales, la especificación de OData definió el método MERGE. En 2010, RFC 5789 definió el método PATCH para las actualizaciones parciales. Puede leer parte del historial de esta entrada de blog en el blog de Servicios de datos de WCF. En la actualidad, se prefiere PATCH a MERGE. El controlador OData creado por el scaffolding de API web admite ambos métodos.
Si desea reemplazar toda la entidad (semántica PUT), especifique la opción ReplaceOnUpdate. Esto hace que WCF envíe una solicitud HTTP PUT.
container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
Eliminar una entidad
Para eliminar una entidad, llame a 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();
}
}
Invocar una acción de OData
En OData, las acciones son una manera de agregar comportamientos del lado servidor que no se definen fácilmente como operaciones CRUD en entidades.
Aunque el documento de metadatos de OData describe las acciones, la clase proxy no crea ningún método fuertemente tipado para ellas. Todavía puede invocar una acción OData mediante el método genérico Execute. Sin embargo, deberá conocer los tipos de datos de los parámetros y el valor devuelto.
Por ejemplo, la acción RateProduct
toma el parámetro denominado "Rating" de tipo Int32
y devuelve un double
. En el código siguiente se muestra cómo invocar esta acción.
int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();
Para obtener más información, consulteAcciones y operaciones del servicio de llamadas.
Una opción es extender la clase Container para proporcionar un método fuertemente tipado que invoca la acción:
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();
}
}
}