Поделиться через


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

Майк Уассон

Скачать завершенный проект

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

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

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

  • Product
  • Supplier
  • ProductRating

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

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

Создание прокси-сервера службы

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

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

Начните с открытия проекта службы 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();
        }
    }
}