Работа с REST API из .Net на примере WebApi DirectumRX

44 5

Идея статьи возникла при решении задачи вызова WebApi, разработанных специалистами Заказчика. При анализе появилось три варианта решения данной задачи, которые мы и рассмотрим. В статье мы будем обращаться к WebApi DirectumRX, тем самым, затронем еще и особенности обращения к WebApi DirectumRX. Отмечу, что это могут быть любые другие REST API, любой другой системы.

Само решение WebApi DirectumRX представляют собой интеграционное API, которое использует протокол OData, которое является одним из готовых стандартов для создания RESTful API.

(Open Data Protocol (OData) – открытый веб-протокол для запроса и обновления данных. Он позволяет выполнять операции с ресурсами, используя в качестве запросов HTTP-команды, а также обмениваться данными в форматах JSON или XML).

Механизмы формирования запросов в .Net будем рассматривать на простейшем примере отправки POST запроса для создания новой должности. Предположим, что веб-сервисы развернуты на локальной машине по адресу 'http://localhost/WebApi/odata’. Если все корректно развернуто, то можно получить описание методов, выполнив GET запрос по адресу 'http://localhost/WebApi/odata/$metadata’ или просто открыв его в браузере.

Последовательность реализации

1. Сформируем json-тело запроса:

JSON BODY:
{
  "Name": "Специалист первой категории",
  "Status": "Active"
}

2. Выполним POST запрос для создания должности 'http://localhost/WebApi/odata/JobTitle.

3. Получим ответ от сервиса. Это может быть как успешное создание:

STATUS CODE: 201
STATUS DESCRIPTION: Created
JSON BODY:
{
  "@odata.context": "http://localhost/WebApi/odata/$metadata#JobTitle/$entity",
  "Id": 1,
  "Name": "Специалист первой категории",
  "Status": "Active"
}

так и ошибка:

STATUS CODE: 404
STATUS DESCRIPTION: Not Found
JSON BODY:
{
  "error": {
    "code": "",
    "message": "No HTTP resource was found that matches the request URI 'http://localhost/WebApi/odata/JobTitle'."
  }
}

При выполнении пунктов 1-3 используются инструменты разработчика браузера или вспомогательные программы, такие как Postman. В результате мы получили знание о структуре данных и правиле сериализации/десериализации этих данных в структурированные объекты.

4. Внутри приложения, которое будет подключаться к WebApi, создадим классы должности для сериализации тела запроса:

/// <summary>
/// Должность RX.
/// </summary>
class TestJobTitle
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Status { get; set; }
}

и ошибки для десериализации полученного ответа:

/// <summary>
/// Содержимое ошибки.
/// </summary>
class ResponseError
{
    public string code { get; set; }
    public string message { get; set; }
}

/// <summary>
/// Ответ сервиса с ошибкой.
/// </summary>
class ResponseErrorHeader
{
    public ResponseError error { get; set; }
}

5. Выполним обращение к веб-сервисам, используя один из интерфейсов .Net. Подробнее об этом в следующей части статьи.

Выбор интерфейса .Net для вызова метода WebApi

На данном этапе мы уже можем «поиграться» с объектами, сериализовать/десериализвать их, отладить, посмотреть какие строки формируются при сериализации, сравнить их с ожидаемыми телами запроса и ответа. В примерах ниже используется сборка Newtonsoft.Json, которая позволяет в одну строчку кода сериализовать/десериализвать dynamic объекты.

Теперь рассмотрим особенности реализаций обращения к сервису через различные интерфейсы .Net. И сразу же сделаем все запросы асинхронными, т.к. методы могут вызываться как в фоновых процессах, так и в клиентских действиях.

Вариант 1. WebRequest и WebResponse

Самый низкоуровневый способ реализации. Из-за чего реализация получается с большим количеством строк кода. В то же время предоставляет больше понимания/контроля над всеми действиями. Сборка доступна в среде разработки DirectumRX.

Для получения информации, специфичной для протокола HTTPS, используются два класса: HttpWebRequest и HttpWebResponse, которые наследуются соответственно от WebRequest и WebResponse.

 

Пример 1. Обращение с помощью System.Net.WebRequest и System.Net.WebResponse:

public Dictionary<int, string> CallWebRequest(string url, string userName, string password, string json)
{
    var callResult = new Dictionary<int, string>();

    // Формирование запроса.
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.ContentType = "application/json";
    request.Method = "POST";

    // Basic-Authorization: Авторизация по логину и паролю.
    var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password));
    request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(byteArray));

    // Запись тела запроса.
    using (Stream stream = request.GetRequestStream())
    {
        var encoder = new UTF8Encoding();
        var resultByteArr = encoder.GetBytes(json);
        stream.Write(resultByteArr, 0, resultByteArr.Length);
    }

    // Асинхронный запрос веб-сервиса.
    var result = request.BeginGetResponse(null, null);
    result.AsyncWaitHandle.WaitOne();
    try
    {
        using (var response = request.EndGetResponse(result) as HttpWebResponse)
        {
            using (var streamReader = new StreamReader(response.GetResponseStream()))
            {
                // Успешное создание записи должности.
                string responseBody = streamReader.ReadToEnd();
                     var jobTitle = JsonConvert.DeserializeObject<TestJobTitle>(responseBody);
                callResult = new Dictionary<int, string>() { { (int)response.StatusCode, string.Format("Создана запись {0}, с ИД {1}", jobTitle.Name, jobTitle.Id) } };
            }
        }
    }
    catch (WebException wex)
    {
        using (var response = wex.Response as HttpWebResponse)
        {
            using (StreamReader streamReader = new StreamReader(response.GetResponseStream(), true))
            {
                // Ошибка запроса.
                string responseBody = streamReader.ReadToEnd();
                var errorHeader = JsonConvert.DeserializeObject<ResponseErrorHeader>(responseBody);
                callResult = new Dictionary<int, string>() { { (int)response.StatusCode, errorHeader.error.message } };
            }
        }
    }
    return callResult;
}

Вариант 2. WebClient.

Абстракция над WebRequest и WebResponse для самых популярных действий. Меньше кода. Сборка доступна в среде разработки DirectumRX.

Пример 2. Обращение с помощью System.Net.WebClient:

public Dictionary<int, string> CallWebClient(string url, string userName, string password, string json)
{
    var callResult = new Dictionary<int, string>();

    using (var client = new WebClient())
    {
        // Basic-Authorization: Авторизация по логину и паролю.
        var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password));
        client.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(byteArray));
        client.Headers.Add("Content-Type", "application/json");
        client.Encoding = Encoding.UTF8;

        try
        {
            // Асинхронный запрос веб-сервиса.
            string responseBody = client.UploadString(url, "POST", json);

            // Успешное создание записи должности.
            var jobTitle = JsonConvert.DeserializeObject<TestJobTitle>(responseBody);
            callResult = new Dictionary<int, string>() { { 202, string.Format("Создана запись {0}, с ИД {1}", jobTitle.Name, jobTitle.Id) } };
        }
        catch (WebException wex)
        {
            // Получить содержимое ответа.
            using (var response = wex.Response as HttpWebResponse)
            {
                using (StreamReader streamReader = new StreamReader(response.GetResponseStream(), true))
                {
                    // Ошибка запроса.
                    string responseBody = streamReader.ReadToEnd();
                    var errorHeader = JsonConvert.DeserializeObject<ResponseErrorHeader>(responseBody);
                    callResult = new Dictionary<int, string>() { { (int)response.StatusCode, errorHeader.error.message } };
                }
            }
        }
    }

    return callResult;
}

Вариант 3. HttpClient.

Абстракция над HttpWebRequest и HttpWebResponse. По сути, HttpClient - своего рода фабрика запросов, которая значительно упрощает реализацию многих сценариев работы с HTTP. Сборка не доступна в среде разработки DirectumRX.

Пример 3. Обращение с помощью System.Net.Http.HttpClient:

public Dictionary<int, string> CallHttpClient(string url, string userName, string password, string json)
{
    var callResult = new Dictionary<int, string>();

    using (var client = new HttpClient())
    {
        // Basic-Authorization: Авторизация по логину и паролю.
        var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password));
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

        // Асинхронный запрос веб-сервиса.
        var response = client.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json"));
        var responseBody = response.Result.Content.ReadAsStringAsync().Result;
        int statusCode = (int)response.Result.StatusCode;

        if (statusCode >= 200 && statusCode < 300)
        {
            // Успешное создание записи должности.
            var jobTitle = JsonConvert.DeserializeObject<TestJobTitle>(responseBody);
            callResult = new Dictionary<int, string>() { { statusCode, string.Format("Создана запись {0}, с ИД {1}", jobTitle.Name, jobTitle.Id) } };
        }
        if (statusCode >= 400 && statusCode < 500)
        {
            // Ошибка запроса.
                var errorHeader = JsonConvert.DeserializeObject<ResponseErrorHeader>(responseBody);
            callResult = new Dictionary<int, string>() { { statusCode, errorHeader.error.message } };
        }
    }

    return callResult;
}

Итоги

Плюсы и минусы каждого способа зависят от решаемых задач. Варианты можно обсудить в комментариях к данной статье. Ниже приведена сравнительная таблица клиентских сборок, найденная на просторах интернета:

WebClient

HttpClient

Доступно в более старых версиях .Net

Только .Net 4.5

Абстракция над WebRequest и WebResponse

Абстракция над HttpWebRequest и HttpWebResponse

Доступны синхронные и асинхронные методы

Только асинхронные методы

Предоставляет информацию о прогрессе загрузки

Не предоставляет информацию о прогрессе загрузки

Не использует повторно разрешенный DNS, cookie

Может повторно использовать разрешенный DNS, cookie

Для каждого конкурентного запроса необходимо создание экземпляра WebClient

Нет необходимости создавать экземпляр HttpClient для каждого запроса

Более сложная отладка запросов

Простая отладка запросов

Поддерживает FTP

Не поддерживает FTP

Приложения WinRT не могут использовать WebClient

Можно использовать с WinRT

Материала в интернете по ним не сказал бы что много, но достаточно. Каждый имеет свои «особенности». Например, в WebClient при успешном выполнении запроса невозможно получить код статуса ответа. В итоге, с большинством задач справляется HttpClient (при условии HTTP запросов), он наиболее простой в использовании и, в то же время мощный.

44
Авторизуйтесь, чтобы оценить материал.
5
Петр Бочкарев

Подскажите пожалуйста, как включить Rest API в Directum? 

У меня сейчас по http://localhost/WebApi/odata 404 ошибка

Андрей, добрый день!

Если при обращении к данной ссылке http://localhost/WebApi/odata/$metadata получаю ошибку следующего содержания:

Доступ к localhost запрещен

У вас нет прав для просмотра этой страницы.

HTTP ERROR 403

Можете посоветовать где глянуть настройки прав?

Тарас Мартынов

Добрый день! Получить или создать сущность понятно, вопрос как обновить уже имеющуюся по ИД? Можно на примере постмана, если есть такая возможность в штатной поставке сервиса.

Павел Федоров

Добрый день!

Подскажите, как работать с API, если Directum в облаке?

Анна Забалуева

Павел, уточните 1) требуется из Directum обращаться к какому-то внешнему API или же внешняя система должна обратиться к API Directum? 2) какая разновидность облака имеется в виду (публичное, где только коробка, или же выделенное, где коробка + заказные доработки)?

Авторизуйтесь, чтобы написать комментарий