Варианты реализации WebServices в .net — WCF, REST, OData, WebAPI и другие умные слова — Part I

Недавний релиз ASP.Net MVC4 Beta заставил меня внимательно посмотреть на новые возможности и без того замечательного фреймворка. Среди этих возможностей наиболее любопытной мне показалась Web API, которая предоставляет удобный интерфейс создания открытых API для сайтов/сервисов, для использования их из различных клиентов (javascript, мобильные приложения, b2b-сервисы, а то и вовсе desktop-клиенты :)).

Заодно я решил слегка обновить свои знания о вариантах создания веб-сервисов на .net в принципе, и вспомнить, что нам на сегодняшний день предлагает WCF. Оказалось, что нового весьма немало.

Исторически Web API приходит в MVC из WCF Web API, принося, помимо удобного rest-синтаксиса методов еще и возможность standalone-запуска без обязательной необходимости в IIS.

MVC4 WebAPI ко всему прочему вместила в себя еще и функционал WCF Data Services, в части поддержки возвращаемого типа IQueryable и составления запросов на стороне клиента.
Допустим, у нас есть некий набор данных, который необходимо «открыть» через сервис, например — список Документов (открывать его будем в виде IQueryable<Document>). Как это было в WCF DataServices:

    public class DataProvider
    {
        public IQueryable<Document> Documents
        {
            get
            {
                return (new[]
                            {
                                new Document() {Info = "a", Title = "qwe1"},
                                new Document() {Info = "b", Title = "qwe2"},
                            }).AsQueryable();
            }
        }
    }

    public class DataService2 : DataService<DataProvider>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }

Мы создаем DataProvider, который включает в себя свойства, возвращающие IQueryable. После этого WCF-сервис, раскрывающий эти данные, создается «в одну строчку» — наследованием от базового класса DataService с указанием нашего DataProvider в качестве generic-аргумента.
Что получаем на выходе? Во-первых, традиционный для WCF способ работы с сервисами через SOAP: Add Service Reference в клиентском проекте на VS и вуа-ля:

            var client = new WcfWebApi.DataProvider(new Uri("http://localhost:44087/DataService2.svc/"));
            var docs = client.Documents.Where(x => x.Info == "a").ToList();

Фильтрация при этом происходит, естественно, на стороне сервера. Однако это не единственный способ работы с WCF WebAPI (до MVC WebAPI мы еще даже не дошли :))! Тот же самый запрос можно послать прямо из браузера! Открываем адрес: «http://localhost:44087/DataService2.svc/Documents?$filter=Info eq ‘a’«, получаем ответ:

<feed>
  <entry>
   <content type="application/xml">
      <m:properties>
        <d:ID m:type="Edm.Int32">0</d:ID>
        <d:Title>qwe1</d:Title>
        <d:Info>a</d:Info>
      </m:properties>
    </content>
  </entry>
</feed>

Конечно, формат ответа не самый удобный для чтения (и это я еще опустил ненужные детали :)), но тем не менее — «человекопонятный» URL делает принципиально возможным работу с таким сервисом и без использования SOAP-клиентов (например, из javascript). Эту возможность (выборка/сортировка через параметры запроса) даёт нам протокол OData, разработанный Microsoft, но переведенный в статус открытого. Возможности протокола очень широки, детальнее с ними ознакомиться можно на странице параметров URI, а я лишь приведу валидный OData-адрес для повышения любопытства: /Products?$filter=Price le 200 and Price gt 3.5&$orderby=Price desc&$skip=5&top=5. Несложно догадаться, что именно делает этот запрос :)
Протокол OData полноценно поддерживается WCF DataServices, а вот WebAPI — лишь частично. Однако базовые операции вполне работают (не работает, например, $select).

К слову, при совместном использовании DataService и EntityFramework можно достаточно просто и удобно «открывать наружу» и операции добавления и изменения БД-сущностей. В обход EF добавление и обновление реализовать тоже можно, но геморрой по написанию провайдеров ожидает приличный :) Эта теоретическая возможность сказывается минусом и в некоторой захламленности клиентского API к DataService (размер скролла как-бы намекает на количество функций :)):

Что же предлагает нам MVC4 WebAPI? Ну, почти то же самое :)

    public class DocumentsController : ApiController
    {
        // GET /api/documents
        public IQueryable<Document> Get()
        {
            return (new[] { 
                new Document() { Info = "a", Title = "qwe1" }, 
                new Document() { Info = "b", Title = "qwe2" }, }).AsQueryable();
        }

        // GET /api/documents/popular
        public IQueryable<Document> Popular()
        {
            return (new[] { 
                new Document { Info = "a", Title = "qwe3" }, 
                new Document { Info = "b", Title = "qwe4" }, }).AsQueryable();
        }


        // GET /api/documents/5
        public Document Get(int id)
        {
            return new Document { Info = "a", Title = "qwe3" };
        }

        // POST /api/documents
        public void Post(string value)
        {
        }

        // PUT /api/documents/5
        public void Put(int id, string value)
        {
        }

        // DELETE /api/documents/5
        public void Delete(int id)
        {
        }
    }

Собственно, код примера рассказывает почти обо всём :) В ASP.Net MVC4 появился новый базовый тип для контроллеров — ApiController (он, к слову, не наследуется от привычных ранее по MVC3 Controller или ControllerBase!), при наследовании от него мы получаем удобную возможность создавать различные обработчики/экшены в зависимости от HTTP-метода, которым был сделан запрос. Согласно конвенциям, для удаления используется http-метод DELETE, для обновления — PUT, для добавления — POST, для чтения — GET.
Всё обычно, просто и очевидно, и написание этих обработчиков (в отличие от WCF Data Service) проблем не представляет. Плюс, мы получаем всю полноту и мощь возможностей ASP.Net MVC — это и валидация, и инъекция зависимостей, и вся-вся-вся инфраструктура. Ну и самое главное — ответ на GET-запросы! Даже просто открыв адрес «http://localhost:44087/api/Documents/» в браузере мы получаем куда более понятный xml:

<ArrayOfDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Document>
    <ID>0</ID>
    <Title>qwe1</Title>
    <Info>a</Info>
  </Document>
  <Document>
    <ID>0</ID>
    <Title>qwe2</Title>
    <Info>b</Info>
  </Document>
</ArrayOfDocument>

А при запросе из javascript мы и вовсем получим в ответе любимый всеми javascript-разработчиками JSON:

[{"ID":0,"Info":"a","Title":"qwe1"},{"ID":0,"Info":"b","Title":"qwe2"}]

MVC4 анализирует заголовок Accept запроса, и форматирует ответ в соответствии с ним (для получения JSON должен быть указан Accept: application/json).

Настройка WebAPI сводится к добавлению в Global.asax следующего маршрута (он будет добавлен по умолчанию при создании WebAPI проекта):

            routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

Как видно, в этом маршруте отсутствует имя action’a, определение вызываемого метода идет исключительно по типу http-запроса. Иногда однако, как в примере выше, хочется сделать несколько GET-методов, возвращающих разные наборы:

// GET /api/documents - возврат всех документов
// GET /api/documents/popular - возврат популярных документов
// GET /api/documents/recent - возврат последних добавленных документов

Для этого маршрут нужно немного подправить:

            routes.MapHttpRoute(
                name: "DefaultApi2",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional, action = "Get" }
            );

Если документов в базе у вас более 9000 и нет желания по запросу /api/documents отдавать все сразу, то можно ограничить максимальное количество возвращаемых строк атрибутом ResultLimit:

        // GET /api/documents/limited
        [ResultLimit(10)]
        public IQueryable<Document> Popular()
        {
            return Documents;
        }

..длина заметки начинает меня пугать, поэтому детальная остановка на вариантах использования и примерах клиентских реализаций ASP.Net MVC4 WebAPI воспоследуют в следующий раз :)человекопонятный

Опубликовать в Facebook
Опубликовать в Google Plus

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *