Вызов службы OData из клиента .NET (C#)
Майк Уассон
В этом руководстве показано, как вызвать службу OData из клиентского приложения C#.
Версии программного обеспечения, используемые в этом руководстве
- Visual Studio 2013 (работает с Visual Studio 2012)
- Библиотека клиентов служб данных WCF
- Веб-API 2. (Пример службы OData создается с помощью веб-API 2, но клиентское приложение не зависит от веб-API.)
В этом руководстве описано, как создать клиентское приложение, которое вызывает службу OData. Служба OData предоставляет следующие сущности:
Product
Supplier
ProductRating
В следующих статьях описывается, как реализовать службу OData в веб-API. (Однако вам не нужно читать их, чтобы понять это руководство.)
- Создание конечной точки OData в веб-API 2
- Отношения сущностей OData в веб-API 2
- Действия OData в веб-API 2
Создание прокси-сервера службы
Первым шагом является создание прокси-сервера службы. Прокси-сервер службы — это класс .NET, который определяет методы для доступа к службе OData. Прокси-сервер преобразует вызовы методов в HTTP-запросы.
Начните с открытия проекта службы OData в Visual Studio. Нажмите клавиши CTRL+F5, чтобы запустить службу локально в IIS Express. Запишите локальный адрес, включая номер порта, который назначает Visual Studio. Этот адрес понадобится при создании прокси-сервера.
Затем откройте другой экземпляр Visual Studio и создайте проект консольного приложения. Консольное приложение будет нашим клиентским приложением OData. (Проект также можно добавить в то же решение, что и служба.)
Примечание
Остальные шаги ссылаются на консольный проект.
В Обозреватель решений щелкните правой кнопкой мыши Ссылки и выберите Добавить ссылку на службу.
В диалоговом окне Добавление ссылки на службу введите адрес службы OData:
http://localhost:port/odata
где port — номер порта.
В поле Пространство имен введите "ProductService". Этот параметр определяет пространство имен прокси-класса.
Нажмите кнопку Переход. Visual Studio считывает документ метаданных OData для обнаружения сущностей в службе.
Нажмите кнопку ОК , чтобы добавить класс прокси в проект.
Создание экземпляра класса Service Proxy
В методе Main
создайте новый экземпляр прокси-класса следующим образом:
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);
// ...
}
}
}
Опять же, используйте фактический номер порта, на котором выполняется служба. При развертывании службы вы будете использовать универсальный код ресурса (URI) динамической службы. Вам не нужно обновлять прокси-сервер.
Следующий код добавляет обработчик событий, который выводит URI запроса в окно консоли. Этот шаг не является обязательным, но интересно просмотреть URI для каждого запроса.
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
Запрос к службе
Следующий код получает список продуктов из службы 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);
}
}
Обратите внимание, что вам не нужно писать код для отправки HTTP-запроса или анализа ответа. Прокси-класс выполняет это автоматически при перечислении Container.Products
коллекции в цикле foreach .
При запуске приложения выходные данные должны выглядеть следующим образом:
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
Чтобы получить сущность по идентификатору, используйте 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);
}
}
В остальной части этого раздела я не буду показывать всю Main
функцию, а только код, необходимый для вызова службы.
Применение параметров запроса
OData определяет параметры запроса , которые можно использовать для фильтрации, сортировки, страницы данных и т. д. В прокси-сервере службы эти параметры можно применять с помощью различных выражений LINQ.
В этом разделе я приведу краткие примеры. Дополнительные сведения см. в разделе Рекомендации по LINQ (WCF Data Services) на сайте MSDN.
Фильтрация ($filter)
Для фильтрации используйте where
предложение . В следующем примере выполняется фильтрация по категориям продуктов.
// 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);
}
}
Этот код соответствует следующему запросу OData.
GET http://localhost/odata/Products()?$filter=Category eq 'apparel'
Обратите внимание, что прокси-сервер преобразует where
предложение в выражение OData $filter
.
Сортировка ($orderby)
Для сортировки используйте orderby
предложение . В следующем примере выполняется сортировка по цене от самого высокого к самому низкому.
// 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);
}
}
Ниже приведен соответствующий запрос OData.
GET http://localhost/odata/Products()?$orderby=Price desc
Client-Side разбиение по страницам ($skip и $top)
Для больших наборов сущностей клиенту может потребоваться ограничить количество результатов. Например, клиент может отображать 10 записей одновременно. Это называется разбиением по страницам на стороне клиента. (Существует также разбиение по страницам на стороне сервера, где сервер ограничивает количество результатов.) Для выполнения разбиения по страницам на стороне клиента используйте методы LINQ Skip и Take . Следующий пример пропускает первые 40 результатов и принимает следующие 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);
}
}
Ниже приведен соответствующий запрос OData:
GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10
Выберите ($select) и разверните ($expand)
Чтобы включить связанные сущности DataServiceQuery<t>.Expand
, используйте метод . Например, чтобы включить для Supplier
каждого 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);
}
}
Ниже приведен соответствующий запрос OData:
GET http://localhost/odata/Products()?$expand=Supplier
Чтобы изменить форму ответа, используйте предложение выбора LINQ. В следующем примере возвращается только имя каждого продукта без других свойств.
// 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);
}
}
Ниже приведен соответствующий запрос OData:
GET http://localhost/odata/Products()?$select=Name
Предложение select может включать связанные сущности. В этом случае не вызывайте Expand; в этом случае прокси-сервер автоматически включает расширение. В следующем примере возвращается имя и поставщик каждого продукта.
// 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);
}
}
Ниже приведен соответствующий запрос OData. Обратите внимание, что он включает параметр $expand .
GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name
Дополнительные сведения о $select и $expand см. в статье Использование $select, $expand и $value в веб-API 2.
Добавление новой сущности
Чтобы добавить новую сущность в набор сущностей, вызовите AddToEntitySet
, где EntitySet — это имя набора сущностей. Например, AddToProducts
добавляет новый Product
объект в набор сущностей Products
. При создании прокси-сервера WCF Data Services автоматически создает эти строго типизированные методы AddTo.
// 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);
}
}
Чтобы добавить связь между двумя сущностями, используйте методы AddLink и SetLink . Следующий код добавляет нового поставщика и новый продукт, а затем создает связи между ними.
// 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);
}
}
Используйте AddLink , если свойство навигации является коллекцией. В этом примере мы добавляем продукт в коллекцию Products
поставщика.
Используйте SetLink , если свойство навигации является одной сущностью. В этом примере мы задаем Supplier
свойство для продукта.
Обновление или исправление
Чтобы обновить сущность, вызовите метод 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);
}
}
Обновление выполняется при вызове Метода SaveChanges. По умолчанию WCF отправляет http-запрос MERGE. Параметр PatchOnUpdate указывает WCF отправить HTTP PATCH.
Примечание
Почему PATCH и MERGE? Исходная спецификация HTTP 1.1 (RCF 2616) не определяла методы HTTP с семантикой частичного обновления. Для поддержки частичных обновлений в спецификации OData определен метод MERGE. В 2010 году RFC 5789 определил метод PATCH для частичных обновлений. Вы можете прочитать некоторые из журналов в этой записи блога на WCF Data Services блоге. В настоящее время предпочтительным вариантом является PATCH, а не MERGE. Контроллер OData, созданный с помощью формирования шаблонов веб-API, поддерживает оба метода.
Если вы хотите заменить всю сущность (семантику PUT), укажите параметр ReplaceOnUpdate . Это приводит к тому, что WCF отправляет HTTP-запрос PUT.
container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
Удаление сущности
Чтобы удалить сущность, вызовите 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();
}
}
Вызов действия OData
В OData действия — это способ добавления поведения на стороне сервера, которое сложно определить как операции CRUD для сущностей.
Хотя в документе метаданных OData описаны действия, прокси-класс не создает для них строго типизированные методы. Вы по-прежнему можете вызвать действие OData с помощью универсального метода Execute . Однако необходимо знать типы данных параметров и возвращаемое значение.
Например, RateProduct
действие принимает параметр с именем "Rating" типа Int32
и возвращает .double
В следующем коде показано, как вызвать это действие.
int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();
Дополнительные сведения см. в разделеВызов операций и действий службы.
Одним из вариантов является расширение класса Container для предоставления строго типизированного метода, который вызывает действие:
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();
}
}
}