Вызов службы OData из клиента .NET (C#)

Майк Уосон

Скачивание завершенного проекта

В этом руководстве показано, как вызвать службу OData из клиентского приложения C#.

Версии программного обеспечения, используемые в руководстве

В этом руководстве описано, как создать клиентское приложение, которое вызывает службу OData. Служба OData предоставляет следующие сущности:

  • Product
  • Supplier
  • ProductRating

Схема, показывающая сущности службы данных O и список их свойств, со стрелками подключения, чтобы показать, как каждая из них связана или работает вместе.

В следующих статьях описывается реализация службы OData в веб-API. (Однако вам не нужно читать их, чтобы понять это руководство.)

Генерировать прокси-сервер службы

Первым шагом является создание прокси-сервера службы. Прокси-сервер службы — это класс .NET, определяющий методы для доступа к службе OData. Прокси-сервер преобразует вызовы метода в HTTP-запросы.

Схема, демонстрирующая двусторонние вызовы запросов H T T P прокси службы, осуществляемые из приложения, через прокси-сервер службы и к сервису OData.

Начните с открытия проекта службы OData в Visual Studio. Нажмите клавиши CTRL+F5, чтобы запустить службу локально в IIS Express. Обратите внимание на локальный адрес, включая номер порта, назначаемого Visual Studio. Этот адрес потребуется при создании прокси-сервера.

Затем откройте другой экземпляр Visual Studio и создайте проект консольного приложения. Консольное приложение будет нашим клиентским приложением OData. Вы также можете добавить проект в то же решение, что и сервис.

Замечание

Остальные шаги относятся к проекту консоли.

В обозревателе решений щелкните правой кнопкой мыши Ссылки и выберите Добавить ссылку на службу.

Снимок экрана: окно обозревателя решений, в котором отображается меню в разделе

В диалоговом окне "Добавление ссылки на службу" введите адрес службы OData:

http://localhost:port/odata

где порт является номером порта.

Снимок экрана: окно

Для пространства имен введите "ProductService". Этот параметр определяет пространство имен прокси-класса.

Нажмите кнопку "Перейти". Visual Studio считывает документ метаданных OData, чтобы обнаружить сущности в службе.

Снимок экрана: диалоговое окно

Нажмите кнопку "ОК ", чтобы добавить в проект класс прокси-сервера.

Снимок экрана: диалоговое окно обозревателя решений, отображающее меню в разделе

Создание экземпляра класса прокси службы

В методе 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) на сайте 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

Постраничная навигация на стороне клиента ($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 select . В следующем примере получается только имя каждого продукта без других свойств.

// 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 автоматически создают эти строго типизированные методы 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. Сегодня PATCH предпочтительнее, чем MERGE. Контроллер OData, созданный шаблоном веб-API, поддерживает оба метода.

Если вы хотите заменить всю сущность (семантику PUT), укажите параметр ReplaceOnUpdate . Это вызывает отправку HTTP PUT запроса WCF.

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