Пользователь
0,2
рейтинг
8 января 2013 в 13:19

Разработка → RESTFul Api контроллеры в .NET MVC 4 tutorial

Приветствую.
Летом вышел релиз новой версии фреймворка, но поработать с ним получилось только недавно. В новой версии было добавлено много полезных штук, об одной из них, а именно ApiController, я хотел бы сегодня рассказать.
Благодаря им стало возможно делать RESTFull Api без лишних усилий. На небольшом примере заодно разберем работу с OData.

Создадим новый ASP MVC 4 Empty Project. Для примера, создадим контроллер, который будет реализовывать функционал по работе с топиками. Для начала добавим простую модель:
public class Topic
{
    public int Id { get; set; }

    public string Title { get; set; }
}

Добавим новый контроллер, унаследуем его от ApiController, пока без никаких действий:
public class TopicController : ApiController
{
}

Теперь наш контроллер доступен по адресу: localhost/api/topic. Если мы перейдем по нему, то получим сообщение о том, что в нашем контроллере не найдено ни одного действия, реализующего ответ на GET запрос. Так добавим же его в наш контроллер:
public class TopicController : ApiController
{
    public ICollection<Topic> Get()
    {
        return new Collection<Topic>
                    {
                        new Topic { Id = 1, Title = "Топик 1"},
                        new Topic { Id = 2, Title = "Топик 2"}
                    };
    } 
}

Если мы сделаем запрос на localhost/api/topic, то получим следующий ответ:
<ArrayOfTopicModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ApiControllerTutorial.Models">
    <TopicModel>
        <Id>1</Id>
        <Title>Топик 1</Title>
    </TopicModel>
    <TopicModel>
        <Id>2</Id>
        <Title>Топик 2</Title>
    </TopicModel>
</ArrayOfTopicModel>

Почему ответ в формате XML? Потому, что если мы не указали Content-Type в запросе, сериализатор по умолчанию вернет нам XML. Давайте получим в формате JSON. Для этого можно воспользоваться удобным приложением для Chromium — REST Console (за подсказку подобных плагинов/расширений для других браузеров буду благодарен). Укажем в Content-Type «json» и получим:
[{"Id":1,"Title":"Топик 1"},{"Id":2,"Title":"Топик 2"}]

Коллекцию топиков мы получили. Добавим новое действие в контроллер для получение одного топика по его идентификатору:
public Topic Get(int id)
{
    return new Topic
                {
                    Id = id,
                    Title = String.Format("Топик {0}", id)
                };
}

Запрос по адресу localhost/api/topic/5 вернет нам следующий ответ:
{"Id":5,"Title":"Топик 5"}

Добавим действие для добавления нашего топика:
public string Put(Topic model)
{
    return String.Format("Топик '{0}' создан!", model.Title);
}

И отправим по адресу localhost/api/topic следующий запрос:
{'Title':'Новый топик'}

Также в запросе укажем необходимые параметры: Request MethodPUT и Content typeapplication/json (Не перепутайте этот Content type с тем, о котором я говорил выше. Этот указывается в Content Headers, чтобы байндер знал, в каком формате пришли к нему данные, а для запроса топиков мы указывали Content type в Accept для сериализатора). И получим в ответ:
"Топик 'Новый топик' создан!"

Кстати о возвращаемом значении. В нашем случае я вернул строку с сообщением. Также можно возвращать HttpResponseMessage и манипулировать кодами ответа, в зависимости от успеха/неудачи операции:
public HttpResponseMessage Put(Topic model)
{
    if(String.IsNullOrEmpty(model.Title))
        return new HttpResponseMessage(HttpStatusCode.BadRequest);

    /*Логика сохранения*/

    return new HttpResponseMessage(HttpStatusCode.Created);
}

Действие для метода POST описывать не буду, т.к. отличий от PUT — нет. Добавим последнее действие DELETE:
public string Delete(int id)
{
    return String.Format("Топик {0} удален!", id);
}


Роутинг


А что, если мы захотим использовать наш контроллер для предоставления простого апи, без поддержки методов?
Добавим новый контроллер с двумя действиями:
public class TestRouteController : ApiController
{
    public string GetTopic(int id)
    {
        return String.Format("Topic {0}", id);
    }

    public string GetComment(int id)
    {
        return String.Format("Comment {0}", id);
    }
}

Если мы отправим запрос на localhost/api/testroute/5, то получим ошибку: Multiple actions were found that match the request. Связано это с тем, что Selector не знает какое действие ему выбрать. Давайте откроем WebApiConfig.cs и посмотрим на заданный там маршрут:
public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}

Как видим, у нас не задано в шаблоне определение действия для контроллера. Необходимое действие контроллера выбирается на основе Method'a запроса. Добавим ниже еще один маршрут:
config.Routes.MapHttpRoute(
                name: "DefaultApiWithAction",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

После этого, если сделать запрос к нашему контроллеру с прямым указанием необходимого действия (localhost/api/testroute/gettopic/5), то мы получим ответ. Либо можно задать у самого действия необходимый маршрут для него с помощью атрибута ActionName:
[ActionName("topic")]
public string GetTopic(int id)
{
    return String.Format("Topic {0}", id);
}

Теперь можно запрашивать localhost/api/testroute/topic/5

OData


Как вы уже обратили внимание, действия контроллером могут возвращать произвольные объекты либо коллекции объектов и они будут успешно сериализованы. Раньше нам необходимо было возвращать ActionResult, и перед этим вручную сериализовывать наши данные. В связи с этим открывается еще одна интересная возможность. Сначала установим OData (Nuget) с помощью пакетного менеджера:
PM> Install-Package Microsoft.AspNet.WebApi.OData -Pre

Добавим новый контроллер:
public class OdataController : ApiController
{
    [Queryable]
    public IQueryable<Topic> Get()
    {
        return new
            EnumerableQuery<Topic>(
            new Collection<Topic>
                {
                    new Topic{ Id = 1, Title = "1"},
                    new Topic{ Id = 2, Title = "2"},
                    new Topic{ Id = 3, Title = "3"},
                    new Topic{ Id = 4, Title = "4"},
                    new Topic{ Id = 5, Title = "5"}
                });
    }
}

Сделаем запрос на localhost/api/odata:
[{"Id":1,"Title":"1"},{"Id":2,"Title":"2"},{"Id":3,"Title":"3"},{"Id":4,"Title":"4"},{"Id":5,"Title":"5"}]

Ничего удивительного не произошло. Но посмотрим внимательнее на метод Get нашего контроллера. Он возвращает IQueryable и помечен атрибутом [Queryable], а это значит, что можно применять дополнительные запросы к нашей коллекции с помощью OData прямо в запросе. Сделаем несколько запросов с различными параметрами и посмотрим на ответ:
Запрос Ответ
localhost/api/odata [{«Id»:1,«Title»:«1»},
{«Id»:2,«Title»:«2»},
{«Id»:3,«Title»:«3»},
{«Id»:4,«Title»:«4»},
{«Id»:5,«Title»:«5»}]
localhost/api/odata?$skip=2 [{«Id»:3,«Title»:«3»},
{«Id»:4,«Title»:«4»},
{«Id»:5,«Title»:«5»}]
localhost/api/odata?$skip=1&$top=2 [{«Id»:2,«Title»:«2»},
{«Id»:3,«Title»:«3»}]
localhost/api/odata?$filter=(Id gt 1) and (Id lt 5) [{«Id»:2,«Title»:«2»},
{«Id»:3,«Title»:«3»},
{«Id»:4,«Title»:«4»}]
localhost/api/odata?$filter=(Id gt 1) and (Id lt 5)&$orderby=Id desc [{«Id»:4,«Title»:«4»},
{«Id»:3,«Title»:«3»},
{«Id»:2,«Title»:«2»}]

Магия, не правда ли?



Отправка форм контроллерам: http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-1#sending_complex_types
Все про OData: http://msdn.microsoft.com/en-us/library/ff478141.aspx
Архив с проектом: yadi.sk/d/k2KaG0cL1fXhA
Вячеслав @vyacheslav_ka
карма
127,7
рейтинг 0,2
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (57)

  • +1
    В плане OData, да и просто разных фильтров (постраничных выводов в том числе) меня всегда интересовал вопрос, как обычно решается вопрос с передачей общего количества элементов в той или иной коллекции. Видел только реализации, которые в дополнительный http header передают кол-во всех элементов.
    • 0
      Обычно с помощью добавления в запрос параметра $inlinecount, тогда нам вернется еще поле Count, в котором будет количество элементов. Но, к сожалению, пока в текущей версии OData для веб-апи не поддерживается этот параметр, и приходиться дописывать хаки, наподобие этого: www.strathweb.com/2012/08/supporting-odata-inlinecount-with-the-new-web-api-odata-preview-package/
      • +1
        из описанного в OData для Web API пока и правда далеко не все работает.
        В случае с $inlinecount меня всегда смущало то, что сигнатура ответа по сути меняется (тут еще вопрос с некой автодокументацией, хотя нормальная автодокументация это миф :), а в случае добавления http header вроде как нет.
        • 0
          Да, поэтому это еще и пре-версия. Надеюсь, когда выйдет полноценная версия, все полезные штуки будут включены. А где возникают проблемы при изменение сигнатуры ответа?
  • +1
    При работе с реальным данными фильтрация будет все равно происходить не на уровне БД? т.е. мне надо будет сначала вытащить все данные, а потом уже за работу возьмется OData?
    • +3
      Если использовать EF и возвращать Queryable контекст, то EF будет формировать запрос сразу с необходимыми фильтрами, и фильтрация будет происходить на стороне БД.
    • +3
      Возвращаемый тип — IQueryable, поэтому, насколько я понимаю, все параметры OData будут транслированы в методы расширения этого IQueryable. Если ваша реализация работает на уровне БД — будет вам счастье.
  • +1
    Всё хорошо, пока примеры достаточно простые.

    Попробовали использовать WebApi примерно три месяца назад. Первое время — классно.
    Затем появляются проблемы:

    1. Плохая поддержка тем же Ninject'ом (не знаю, как сейчас, но раньше у него не ладилось с ApiController).

    2. Невозможность биндить несколько аргументов метода из тела запроса. Т.е. если с клиента вы хотите передать всего 2 поля, и ваш метод в контроллере выглядит как
    public Something Put(string first, string second)
    

    то передавать параметры надо в урле. Сделать это красивым json-ом не получится. Есть директива [FromBody], но она может быть применена только к одному параметру. И придется определять модели для каждого чиха.

    3. Преимущество в виде роутинга сходит на нет как только разнообразие ваших операций выходит за рамки базового CRUD. Да и он позволяет проделывать очень интересные вещи, вроде:
    public string Get(int first)
     {
        return "hello";
    }
    
    public string Get(int first, bool other)
    {
        return "word";
    }
    

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

    Остается очень классная фича в возвращении типов, отличных от ActionResult, но это реально было реализовать и раньше.

    Как результат, нам WebApi не подошел и пришлось с болью «вырефакторивать» его обратно. Интересно было бы послушать людей, который имеют реально позитивный опыт его использования на чем-то сложном.
    • 0
      Вебапи хорошо использовать, например, с backbonejs, в котором у каждой сущности мы указываем адрес для запросов, и backbone уже оперируя веб-методами производит CRUD операции для каждой сущности.
      Сейчас на проекте используем веб-апи, роутинг веб-методами выпили сразу. В целом, впечатление положительное, хотя иногда приходиться извращаться с передачей параметров, а модель создавать не желательно.
    • +1
      Полностью согласен с коментом, испатыл боль применения wepApi — но остался на нем, хотя 10 раз думал вернуться к wcf
      • +1
        WCF — это RPC (Remote Procedure Call)
        WebAPI — это REST

        Разные это штуки, разные.
        • +1
          Что мешает настроить wcf на отдачу json/xml?
          • +1
            сам подход разный, если в лоб переносить какой нибудь большой wcf\wpf сервис на REST как раз и будут возникать трудности, описанные вещи, основное это то, что операции не укладываются в CRUD
            • +2
              Соглашусь, работы будет много, но огромный список методов:
              GetUsers
              GetUserGroups
              GetUserParentsAndDescriptions
              GetGroupById
              GetGroupsByName

              и так далее, всё это просто становится ресурсами

              GetUsers() -> /users/
              GetGroups() -> /groups/
              GetUserGroups() -> /users/{userId}/groups
              GetGroupUsers() -> /groups/{groupId/users
              GetUserParentsAndDescriptions() -> /users/{userId}/parents/

              и так далее.

              Получать ресурсы с параметрами — нам тоже ни кто не запрещает:
              /users/?q={request} и тут хоть свой сообственный, хоть google-style, хоть OData — как кому будет удобнее.
              • +1
                с методами get еще хорошо все, тут просто дополнительные фильтры (операция по сути одна — получение списка), я про другое, я про отельные «операции» над объектами, которые в RPC как раз в метод выделяются.
                Например у объекта есть несколько состояний, и есть несколько методов для перевода из одного состояния в другое, ну в моем случае это был объект payment для которого были операции:
                — создать (ну тут просто POST)
                — подтвердить оплату

                Для операции «подтвердить оплату» как раз не так легко сразу «найти место», как правило делается дополнительный route (типа api/{controller}/{id}/{action}) с поддержкой операций над объектами (api/payment/{id}/accept).

                По аналогии можно придумать, пусть у вас будет создание заказа, отдельными методами POST наполнения этого заказа товарами, но после всего этого надо будет отдельно подтвердить клиенту, что заказ надо теперь уже оформлять — это будет отдельный метод, и не уверен, что это должен быть PUT метод, скорее для этого метода должен быть отдельный url, с указанием action в пути.

                Или у нас есть создание нового платежа — в REST получится операция POST, а есть создание платежа на основе ошибочного, так называемая перепробивка — дополнительно проверяется что платеж ошибочный, что нет других перепробивок, ну и кроме создания нового платежа, делается связывание старого и нового, т.е. это уже отдельная «сложная» операция, которая немного выходит за рамки CRUD операций, и если в wcf можно было сделать новый метод в существующем сервисе (имя метода будет говорить само за себя), то тут операции не так просто засунуть в api/payment/… это даже не операция POST в api/payment, в некий отдельный action «fix» для кого то отдельного платежа, т.е. что то типа метода POST по адресу api/payment/{id}/fix

                Или есть отмена платежа, с точки зрения REST логичней положить метод создания заявки на отмену на POST api/paymentcancels, но так хочется увидеть ее вот тут — POST api/payment/{id}/cancel

                Т.е. сделать можно, но проблема в идеологии CRUD и RPC немного, хотя ничего такого уж страшного нет. Просто раньше надо было придумать название метода хорошее, а теперь еще и как это «название» уложить в REST.
                • +1
                  Есть такой ресурс как invoice — это счёт, на оплату, как вы знаете, а вот paid invoice — это как раз и есть оплаченый.

                  POST /invoices/paid/

                  <paid-invoice>
                  </paid-invoice>

                  Где link — это ссылка на инвойс который оплачивают, вот и всё. Чё сложного?

                  Второй ваш пример, заказ с товарами, ресурсы:

                  Order
                  Order-Item-Collection

                  POST /orders/

                  <order>
                    <name>Sample Order</name>
                  </order>
                  


                  POST /orders/1111/items

                  <order-item>
                     <name>Item 1</name>
                  </order-item>
                  


                  GET /orders/

                  <order-collection>
                    <orders>
                       <order>
                          <name>AAA</name>
                      </order>
                    </orders>
                  </order-collection>
                  


                  Оплатить заказ?

                  POST /orders/paid

                   <paid-order>
                      <order href="http://sample.com/orders/1" />
                   </paid-order>
                  


                  Какие проблемы?
                  • +1
                    Оплатить заказ?

                    POST /orders/paid


                    тут paid это action в рамках OrdersController все таки будет, или это отдельный контроллер? И сам url должен быть для оплаты заказа 1111 как oreders/1111/paid или oreders/paid/1111?

                    Я не говорю, что на WebAPI нельзя перенести логику, я просто про то, что при переходе с сервисов вопрос о том, а куда бы логичнее перенести вот такой метод слегка сбивает с толку. Происходит из-за того, что изначально кажется, что большая часть операций ложиться в метод HTTP (POST, PUT, GET, DELETE), и для простых систем это актуально, но позже приходится все равно прописывать дополнительные route, которые выходят за рамки «стандартного».

                    И, кстати, в случае с WebAPI сделать это очень просто :) Я больше все таки про то, что проблема «как бы мне назвать новый метод» в случае с REST становится чуть интереснее.
                • +1
                  Домашнее задание: на примере предыдущего ответа построить связи ресурсов «платёжка» :%)
                  • +1
                    <paid-order>
                        <order href="http://sample.com/orders/1" />
                     </paid-order>
                    


                    Мне до сих пор хочется увидеть где нибудь описание того, что такой способ ссылки более правильный, нежели

                    <paid-order>
                        <order-ref>1<order-ref/>
                     </paid-order>
                    


                    Точнее даже, когда какой способ более целесообразно использовать (потому что в реальности все зависит от ситуации). И есть ли какие нибудь «фреймворки» для организации такого ответа на стороне сервера, или обработки такого ответа на стороне клиента (не обязательно в .NET, просто в принципе).

                    Не говорю, что он неправильный, я просто не находил реального примера использования таких ссылок, и мне интересно есть ли какой нибудь готовый инструментарий для работы с таким подходом.
                    • +2
                      Просто знать, как работает маршрутизация в MVC/WebAPI

                      Всё строиться на контрактах в вашем приложении, скажем создаётся базовый класс: ResourceBase

                        public class ResourceLink
                        {
                              public string Rel { get; set; }
                              public string Href { get; set; }
                        }
                      
                        public class ResourceBase
                        {
                              public ResourceLink[] Links { get; set; }
                        }
                      
                      


                      Далее создаём себе столько ресурсов, сколько нам захочеться, к примеру с заказами и оплачеными заказами:

                         public class Invoice  : ResourceBase
                         {
                            public decimal GrandTotal { get; set; }
                         }
                      
                        public class InvoicePayment : ResourceBase
                        {      
                        }
                      


                      Каждому ресурсу присваиваем имя, как вам будет удобнее, можно опять-таки контрактом:

                      Invoice -> http://foo.com/rel/invoice InvoicePayment -> http://foo.com/rel/invoice-payment

                      Эти самые relationships должны быть постоянными и не изменяемыми, и более того уникальными.

                      Дальше всё просто:

                      POST /invoices/
                       <Invoice>
                         <GrandTotal>10.00</GrandTotal>
                       </Invoice>
                      


                      И создание оплаты:

                      POST /payments/
                      <InvoicePayment>
                        <Links>
                          <Link rel="http://foo.com/rel/invoice" href="http://bar.com/invoices/12345" />
                        </Links>
                      </InvoicePayment>
                      


                      Откуда взялся bar.com/invoices/12345?

                      Просто, для поддержки concurrency в CRUD операциях, сначала нужно получить данные а потом с ними работать, всё как в Entity Framework'e и прочих ORM'ах, что бы гарантировать то, кто изменяемый или удаляемый объект (Entity) существует.

                      var ctx = new DataContext();
                      var invoice = ctx.Invoices.Where(x=>x.Id==54123).FirstOrDefault();
                      ctx.Invoices.Delete(invoice); // не помню, может быть функция как-то по-другому называется.
                      ctx.SaveChanges();
                      


                      Точно так же и с ресурсами в REST. Что бы однозначно определить сам ресурс он должен иметь свой ID, а в нашем случаи некий href что гарантированно определяет ресурс.

                      Таким образом, после создания инвойса, мы получим ответ:
                       <Invoice>
                         <Links>
                            <Link rel="self" href="http://foo.com/invoices/12345" />
                         </Links>
                         <GrandTotal>10.00</GrandTotal>
                       </Invoice>
                      


                      Где ссылка которая имеет relationship «self» будет содержать ссылку на ресурс, такой себе контракт что бы гарантировать возможность получения уникального идентификатора каждого ресурса.

                      И для окончательного закрепления, вот работа с оплатой инвойса.

                      Запрос:
                      POST /invoices/

                       <Invoice>
                         <GrandTotal>10.00</GrandTotal>
                       </Invoice>
                      


                      Ответ:
                       <Invoice>
                         <Links>
                            <Link rel="self" href="http://bar.com/invoices/12345" />
                         </Links>
                         <GrandTotal>10.00</GrandTotal>
                       </Invoice>
                      


                      Имея контракт, что все ресурсы могут содержать коллекцию ссылок, и то, что каждый ресурс гарантированно содержит ссылку с relationship «self» получить идентификатор ресурса не представляется сложной задачей, нашли.

                      Запрос
                      POST /payments/

                       <InvoicePayment>
                        <Links>
                          <Link rel="http://foo.com/rel/invoice" href="http://bar.com/invoices/12345" />
                        </Links>
                      </InvoicePayment>
                      
                      


                      Как self превратился в foo.com/rel/invoice? Мы ведь договаривались что relationships — это well-known данные, так сказать константы которые гарантированно имеют уникальное имя и указывают на вполне определённый ресурс.

                      B наш ответ:
                       <InvoicePayment>
                        <Links>
                          <Link rel="self" href="http://bar.com/payments/12345" />
                          <Link rel="http://foo.com/rel/invoice" href="http://bar.com/invoices/12345" />
                        </Links>
                        <Created>01/09/12 10:00AM</Created>
                      </InvoicePayment>
                      


                      Как мы видим, наш ответ уже имеет ссылку на самого себя и ссылку на другой ресурс, получить который можно найдя ссылку с нужным relationship и сделав GET

                      Возникает вопрос, а как обрабатывать все эти ссылки на стороне сервера, т.е. в контроллере?
                      Просто! Через RouteData

                      public void Post(Payment payment)
                      {
                         var invoiceLink = payment.Links.Single(x=>x.Rel=="http://foo.com/rel/invoice");
                         var invoiceData = RouteTable.Routes.GetRouteData(invoiceLink.Href); // в WebApi будет чуть-чуть отличаться
                      }
                      


                      Перед тем, как объяснить, что же будет в RouteData нам нужно представить, как же выглядит контроллер для получения одного конкретного инвойса через GET

                      public class InvoiceController : ApiController
                      {
                          public Invoice Get(int invoiceId)
                          {
                                  // тут реализация
                          }
                      }
                      


                      Дак вот, в invoiceData будет ключ-значение, где ключом будут имена параметров метода, а значением — значение параметра. Т.е. в нашем случаи там будет ключ «invoiceId» со зрачением 12345

                      Теперь у нас есть привычные нам типы без всяких там ссылок, и можем их использовать как угодно в наших моделях.
                      • +2
                        Хорошо, руками это можно сделать, готовых механизмов нет. Опустим тогда технику.

                        У меня другой вопрос, на сколько удобно это использовать на стороне клиента. Когда стоит отдавать ссылку на связанный объект, когда лучше сразу отдать сам объект.

                        Ну допустим, Order и его Items. Мы хотим получить заказ с идентификатором 123, делаем GET по адресу order/123

                        В ответе нам надо сразу отдать список всех Item'ов (мы же все таки запрашиваем определенный заказ, а не их список, где эта информация может быть лишней)? А если отдавать список, то именно объекты Items или только Links на них?

                        Тут ведь весь вопрос, когда лучше использовать ссылки, а когда нет. Понимаю, что ответа легкого не получится, т.к. все зависит от ситуации :)

                        На сколько я вижу, есть логика использовать ссылки там, где есть вероятность того, что на стороне клиента связанный объект уже есть, или его не обязательно надо полностью получить в данный момент (может потребоваться позже, но не факт — отдаем ссылку на него).

                        Но в этом случае вопрос, на стороне клиента или должен быть реализован механизм, который в случае необходимости по Link сможет подгрузить полный объект (т.е. скрыть механизмы в некую обертку), либо самому писать получение связанных объектов, а тогда нет особой разницы указан ли адрес связанного объекта, ведь если его получение идет руками и он тебе нужен, ты и так знаешь как его получить.
                        Я к тому, что пока нет точного согласования того, как ссылки должны быть указаны, не появятся обертки, которые смогут это использовать (без лишней писанины для программиста), а значит пока что это, к сожалению, просто хорошая теория, которую можно применять, а можно и не применять.
                        А т.к. REST обычно используется в сайтах (т.е. не настольных клиентах, а через браузер), ожидать такой обертки надо от какого нибудь js фреймворка, т.е. это какая то ORM на js для сервисов, или такие уже есть?
                        • +1
                          Ок, где лежит объект с идентификатором 12345678? Как мне его получить?

                          Вряд ли вы сможете ответить на этот вопрос, а вот где лежит объект с идентификатором

                          foo.com/users/12345678

                          Как его получить?

                          Думаю, ответ на второй вопрос будет куда проще и понятнее.

                          Что касается избыточности, то тут я вас не поняла.

                          у нас есть коллекция

                          GET /orders

                          которая отвечает что-то типа такого:
                          <order-collection>
                              <links>
                                  <link rel="self" href="http://bar.com/orders" />
                              </links>
                             <orders>
                               <order-preview>
                                <links>
                                  <link rel="http://foo/order" href="http://bar.com/orders/1" />
                                </links>      
                               </order-preview>
                               <order-preview>
                                <links>
                                  <link rel="http://foo/order" href="http://bar.com/orders/2" />
                                </links>      
                               </order-preview>
                             <orderts>
                          </order-collection>
                          


                          И если нам захочется получить сам заказ, то мы просто найдём в коллекции тот, который нам нужен, и опять-таки сделаем GET к его href'у

                          GET http://bar.com/orders/2

                          и в ответ получим
                          <order>
                             <links>
                                 <link rel="self" href="http://bar.com/orders/2" />
                             </links>
                             <name>AAAA</name>
                             <created>BBBB</created>
                          </order>
                          
                          • 0
                            Ок, где лежит объект с идентификатором 12345678? Как мне его получить?

                            Вряд ли вы сможете ответить на этот вопрос, а вот где лежит объект с идентификатором

                            foo.com/users/12345678

                            Как его получить?

                            Думаю, ответ на второй вопрос будет куда проще и понятнее.


                            Это понятно все, link несет дополнительную информацию о том, как получить этот объект (плюшки от этого ясны), я про другое. Когда может возникнуть необходимость отдавать именно link, заместо самого объекта? Когда это целесообразнее?

                            С заказом я имел ввиду вот что, пример с ссылками:

                            <order>
                              <links>
                                <link rel="self" href="http://bar.com/orders/1234" />
                                <link rel="http://foo/orderitem" href="http://bar.com/orders/1234/item/1" />
                                <link rel="http://foo/orderitem" href="http://bar.com/orders/1234/item/2" />
                                <link rel="http://foo/orderitem" href="http://bar.com/orders/1234/item/3" />
                              </links>
                              <name>AAAA</name>
                              <created>BBBB</created>
                            </order>
                            


                            Пример без ссылок (пусть ссылка сама на себя останется):
                            <order>
                              <links>
                                <link rel="self" href="http://bar.com/orders/1234" />
                              </links>
                              <name>AAAA</name>
                              <created>BBBB</created>
                              <items>
                                <item ... />
                                <item ... />
                                <item ... />
                              </items>
                            </order>
                            


                            item полноценный объект, т.е. со всеми своими полями сразу.

                            Вопрос в том, что в случае получения конкретного order, скорее всего надо отдать и развернутые item, а не ссылки на них (иначе отображать вообще нечего будет). Иначе придется делать еще один запрос для получения всех items заказа (оно точно всегда так надо?)

                            А пример с order-collection так вообще страшный, т.к. по такому ответу можно узнать только общее кол-во объектов типа order и ссылки на них. А в списке хотелось бы хотя бы общую информацию, что бы как то отобразить можно было это.

                            В итоге основной вопрос — где грань между ссылкой и полноценным объектом? Какие плюсы со стороны клиента мы получаем при использовании ссылок?
                            • 0
                              Ок, зайдём с точки зрения RPC

                              int[] GetItemsIdentificators()
                              Item[] GetItems()
                              


                              Обе функции работают и часто встречаются, но давай-те подумаем, что получение идентификаторов скорее связано с необходимостью создать более сложный объект, который и будет ссылаться на эти самые идентификаторы.

                              Чаще всего, это построение связей многие-к-многим. Но нужно понимать, что хранение данных с такими связями — это решение задачи, а не её условие.

                              Изначально, если посмотреть на ERD диаграмму то там скорее всего будут сами сущности такие как: Заказ, Оплаченый Заказ, Элемент заказа, Группа поставщиков, Группы поставщика, возвращаясь к нашему примеру с заказами.

                              И вот именно две сущности: Группа поставщиков и Группы поставщика разворачиваются в три: Группы, Поставщики и Связи поставщика и групп, т.е. в стандартный вариант хранения связей многие-к-многим.

                              В REST оно так и останется Группой поставщиков, и Группы поставщика, полностью абстрагировавшись от механизма их хранения.

                              Но, возвращаясь к вопросу где та самая грань?

                              Допустим у нас есть сущности User и Group и условие, что они могут быть связаны многие-к-многим.

                              Для этого мы создаём ресурсы: User, Group, и ресурсы-коллекции для этих объектов.

                              GET /users/1

                              <user>
                                 <name> Usr 01 </name>
                                 <groups>
                                      <group>
                                           <links>
                                                 <link rel="foo/group" href="bar/groups/1" />
                                           </links>
                                           <name>Group 1</name>
                                      </group>
                                 </groups>
                              </user>
                              


                              Что бы создать пользователя с группами, мы делаем:
                              POST /users/

                              <user>
                                 <name> Usr 01 </name>
                                 <groups>
                                      <group>
                                           <links>
                                                 <link rel="foo/group" href="bar/groups/1" />
                                           </links>
                                      </group>
                                 </groups>
                              </user>
                              


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

                              Этот метод позволяет нам делать и вот такое:
                              <user>
                                 <name> Usr 01 </name>
                                 <groups>
                                      <group>
                                            <name>New Group for Usr 01</name>
                                      </group>
                                 </groups>
                              </user>
                              


                              Т.е. в одном запросе на создание пользователя мы сразу же можем и передать ранее не существующие группы, если это потребуется.

                              А возвращать объект только со ссылками на другие объекты или полный граф — это уже очень сильно зависит от задачи, т.к. каждый REST сервис служит определённым задачам, если в их списке всегда пользователь нужен с группами — то возвращать сразу же с группами, а если нужен пользователь и его группы опционально будут запрашиваться, то возвращать что-то типа такого:

                              <user>
                                <links>
                                     <link rel="self"  href="bar.com/users/1" />
                                     <link rel="foo/user-groups" href="bar.com/users/1/groups" />
                                </links>
                                <name>User 01</name>
                              </user>
                              


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

                              так же можно делать и
                              POST bar.com/users/1/groups

                              Что добавит пользователя в группу, или DELETE что бы избавить пользователя от членства в какой-то группе.

                              Не забывай-те, что можно поддерживать иерархию ресурсов, как в моём примере где группы вложены в ресурс пользователь, и наоборот.

                              bar.com/users/1/groups
                              bar.com/groups/1/users

                              • 0
                                Хорошо, но получается на стороне сервера, при создании пользователя надо отрабатывать так же ситуацию создания в том числе и группы?
                                Клиент при этом должен знать, что запрос можно сформулировать двумя способами (связать с существующей группой, связать с новой).

                                Получается, ссылки позволяют это описать легко в запросе, но реализация серверной части и клиентской становится труднее (лично я тут привык придерживаться принципа KISS)

                                Обе функции работают и часто встречаются, но давай-те подумаем, что получение идентификаторов скорее связано с необходимостью создать более сложный объект, который и будет ссылаться на эти самые идентификаторы.


                                В своей практике никогда не приходилось делать методов, которые отдают только идентификаторы, но вполне возможно мне просто повезло :)
                                Когда может возникнуть такая задача? Ну т.е. то, что клиент хочет создать новый объект с ссылками на другие объекты, про которые он пока ничего не знает? Мне может прийти на ум только создание нового объекта на основе существующего.
                        • +1
                          Отвечая на вопрос, где используется REST — везде, но в разных реализациях.

                          Огромное количество продуктов имеют API, у одних это REST у других — это RPC
                          Те продукты которые хотят что бы их API можно было использовать как десктопным так и веб-клиентам используют REST.

                          а что сложно получить на JS данные по ссылке?

                          Быстрый поиск мне выдал:
                          linq.js — LINQ for JavaScript

                          следовательно можно на JS писать конструкции типа:
                          var link = data.Links
                                                 .Where(function(x) { return x.rel=="http://foo.com/rel/invoice"; })
                                                 .Select(function(x) { return x.Href; })
                                                 .FirstOrDefault();
                          $.ajax({ url: link }).done(function(data) { console.log(data); });
                          


                          А ввиду асинхронной природы AJAXа, написать lazy-loading вряд ли получиться, так как это сделано в EF, максимум добавить функции Load(rel, successCallback, errorCallback);

                          На C# тоже ни чего особо сложного, примеры я уже приводить не буду.
                          • 0
                            У меня сейчас основной вопрос в следующем. Схема с ссылками интересная — не только идентификатор передается, но и по сути как получить полное описание объекта.
                            Но по факту пока приходится писать обработку этих ссылок ручками.
                            Чего хотелось бы на стороне клиента (на js или .NET не суть), так это возможности описать, что:
                            — у нас есть классы order, orderitem, invoice
                            — у нас есть некие связи между ними

                            Далее, на стороне клиента, получили мы список всех order (ну или части), items мы пока в интерфейсе нигде не показывали (и сервер отдал только ссылки на них, а может и уже полностью), но как только мы из модели обратились к items, у нас по всем ref и прочим этим штукам, внутри сами по себе (через ajax для js) подгрузились эти данные.
                            Или к примеру, мы загрузили список order, потом в другом месте загрузили список invoice, а за счет того, что мы получили links в обоих ответах, на нашей стороне объекты заодно и связались (для invoice не надо будет заново загружать order, информация по нему у нас есть).

                            Именно в этом случае, возможно и правда есть смысл именно в таких ссылках. Пока такого нет, не уверен, что информацию о том, как вытащить объект надо описывать в ответе сервера, а не в модели на стороне клиента. Выглядит красиво, но на сколько реально это упрощает разработку?
                            • 0
                              Вы пытаетесь придумать какой-то Lazy-Loading из этих ссылок, в теории — это возможно, но сильно ограничивает вас в платформе. Вы привыкли общаться с сервисами в рамках одного продукта, ну может быть ещё пару штук.

                              А я говорю об API на безе REST которым может пользоваться пару сонет не известных и не подконтрольных вам продуктов.

                              Упрощает разработку чего? Для каждой задачи есть свои средства и универсального ни чего нет и не будет.

                              Где-то удобнее использовать WCF с его SOAP т.к. задача диктует federation authentication со всякими WS-Trust, ActAS и прочими прелестями. Где-то WCF удобен тем, что нужно уровень доступа к данным предоставить двум разным приложениям в рамках одного решения, где нужна поддержка сессий, security context'ов

                              Rest это более лёгкий способ обеспечить доступ к данным и их изменений для веб-приложений, с любой платформы и на любом языке, который поддерживает работу с HTTP

                              Вы попытайтесь как-то на JS вызывать WCF сервис который требует WS-Trust :) Я пыталась, получилось, но на это ушло около 3х мес. работы.
                              • 0
                                я пытаюсь понять, что дает передача дополнительной информации о ссылке.

                                в этом примере:
                                <user>
                                  <links>
                                       <link rel="self"  href="bar.com/users/1" />
                                       <link rel="foo/user-groups" href="bar.com/users/1/groups" />
                                  </links>
                                  <name>User 01</name>
                                </user>
                                


                                ссылка
                                <link rel="foo/user-groups" href="bar.com/users/1/groups" />
                                


                                описывает, что у пользователя есть группы, и их можно получить вот так то. Т.е. это некая мета информация об объекте.
                                Если я работаю с пользователем и знаю что он может быть в группах, мне эта информация не на столько важна (я скорее всего уже знаю как получить группы пользователя). Или возможно эта информация есть в описании документации. Я пытаюсь понять, зачем эту мета информацию надо всегда передавать.
                                И прок от нее в чем, если мне надо глазками посмотреть адрес ссылки и понять, что связанные группы пользователя надо выбирать по url users/{id}/groups, если автоматом это не делается.

                                А я говорю об API на безе REST которым может пользоваться пару сонет не известных и не подконтрольных вам продуктов

                                это понятно, я просто с точки зрения клиента пытался понять полезность для меня таких ссылок.
                                • 0
                                  Т.е. это получается, что некое описание схемы объекта user (части его схемы), передается сразу вместе с его данными. Хотя кажется, что схема объекта и сами данные для него — разные вещи. Когда это реально необходимо делать?
                                  • 0
                                    В C# это выглядило бы как коллекция с группами, дак ещё и с поддержкой lazy-loading

                                    Фактически в моём примере это и есть, только ввиде XML объекта.

                                    Вы видимо окончательно запутались, прочитайте пару книг на тему построение веб-приложений. Все ваши вопросы только от недостатка опыта.

                                    Спасибо.

                                    • 0
                                      Опыт и прочтение книг — это разные вещи, суть моего вопроса как раз и сводилась к тому, что мне интересен реальный опыт использования организации именно таких ссылок (когда кроме идентификатора объекта передается еще и как его получить), и какие преимущества это давало в работе, по сравнению с тем, когда передается просто идентификатор связанного объекта.
          • +1
            кстати, до сих пор считаю, что настройка в wcf очень перемудренная, настроить, возможно, можно почти все, но делается это очень большим конфигом (да и разобраться с лету не так просто), и еще в итоге большая часть инструментария нацелена именно на SOAP протокол.
            • +1
              Конфигурация мудрёная, т.к. WCF — это очень абстрактная штуковина, которая «из коробки» умеет объединять системы чуть ли не голубиной почтой.

              Но, с моей, женской, точки зрения, то не такой уж он и страшный, указал endpoint и тип транспорта — всё. Остальное это уже детали.
              • +1
                WCF очень мощная и гибкая штука… конфиг из-за этого получился не очень тривиальным (можно настроить почти все и это правда клево, когда разберешься :)
                Со временем просто сделали «настройки по умолчанию», что позволяло задавать простую версию конфига, которая покрывает большую часть кейсов, если в качестве протокола используется SOAP, для всего остального (для голубиной почты, к примеру ;), начинается длинный конфиг :)
                Я думаю WebAPI появился в частности из-за того, что это более простой способ именно для REST — много схожего есть из проекта Restfull для WCF, да по сути его то и перенесли. При этом REST перебрался поближе к замечательному MVC, в котором как правило и возникала необходимость каких нибудь ajax запросов, из-за которых в контроллерах приходилось писать action, возвращающие json.
                И именно это показывает, что REST подход сложно уложить в RPC стиль. Хоть WCF и позволял это делать, но слишком многое в нем все таки для SOAP, а не REST.
                И я рад, что они это сделали ;) Внутри реализации как раз видно, что многое хорошее взяли из WCF как раз.
          • +2
            Сам по себе, формат ответа ни как не определяет архитектуру сервиса.

            Основными отличиями REST от WCF является то, что REST веб-ориентированный, тогда как WCF более универсальный.

            К примеру, у нас есть некая система управляющая пользователями. В REST для получения списка пользователей вам достаточно знать URL, /users/ или одного конкретного /users/1 Здесь, /users — является уникальным идентификатором ресурса «коллекция пользователей» и /users/1 — уникальный идентификатор одного конкретного пользователя. В WCF для получения списка пользователей вам необходимо знать больше, что бы получить ваши данные: протокол, имя метода, список и типы параметров.

            А будет ли REST сервис отдавать вам binary/octet-stream или application/xml — не важно, он от этого не станет RPC, точно так же как и WCF сервис отдавая application/xml или application/json не станет RESTом.

            (спорить, о том, что можно сделать /GetUsersById/1 на WCF — я не буду, это в чистом виде REST, хоть и с непривычными идентификаторами ресурсов)

            Возвращаясь к REST и уникальным идентификатором ресурсов, он несёт в себе ещё и расположение ресурса, позволяя строить взаимоотношения, абстрагируясь от их места расположения.

            Допустим, наш ресурс /users/1 выглядит в XML вот так:
               <user>
                 <id href="http://a.users.foo.com/users/1" />
                 <container href="http://users.bar.com/users/" />
              </user>
            


            И что мы получим? Мы получим отношения между ресурсами, полностью абстрагировавшись от того где они расположены в foo.com или bar.com

            И как домашнее задание: попробуйте такое провернуть с RPC
            • +1
              Кстати про расположение ресурсов, когда я читал про REST изначально, мне казалось это заманчивой идеей, сделать внутри объекта ссылку на объект другого ресурса.
              На практики, к сожалению, как я понял, нет общепринятых соглашений\стандартов по поводу того, как это должно быть организовано. Я имею ввиду, как такие ссылки анализировать на стороне клиента, как на стороне сервера легко описать, что в данном случае надо сериализировать это поле как ссылку, а не как полноценный объект (в WebAPI этого в частности нет). Хотя если говорить про сам REST это не некий стандарт, а больше просто некий подход к организацию сервисов.
              Это так же заманчиво, как wsdl схема у SOAP, которая есть, и по ней можно сгенерировать код для сервиса, но для понимания сервиса (связей в случае с REST) все равно надо читать документацию :)
              • +1
                Не обязательно. Всё зависит от того, как именно вы организуете ресурсы.

                А документация в любом случаи нужна, даже если у меня и WSDL.

                Угадайте что делает функция: AOUOpcodes(int, int, int, string, bool, bool)?
                • 0
                  По поводу документации согласен полностью, я больше про то, что WSDL (так же как и ссылки в REST) упрощают генерацию, парсинг объекта на программном уровне, но ни в коем случае не заменяют документацию.
                  Хотя я знаю некоторых людей, которые почему то думали, что тот же wsdl в силе заменить документацию. Более того, мне несколько раз присылали документацию к какому нибудь шлюзу в виде только лишь одной ссылки на wsdl, хорошо что при этом еще были какие нибудь контакты :)
    • +3
      Поддержка IoC в WebAPI на порядок лучше чем в MVC4.

      Основное отличие — это разные IDependencyResolver

      MVC4

      public interface IDependencyResolver
      {
          object GetService(Type serviceType);
          IEnumerable<object> GetServices(Type serviceType);
      }
      


      WebAPI

      
      public interface IDependencyScope : IDisposable
      {
          object GetService(Type serviceType);
          IEnumerable<object> GetServices(Type serviceType);
      }
      
      public interface IDependencyResolver : IDependencyScope, IDisposable
      {
          IDependencyScope BeginScope();
      }
      
      


      А разница заключается в том, что в WebAPI можно управлять временем жизни объектов в пределах одного запроса, что крайне важно если используется ORM, к примеру Entity Framework, где необходимо иметь один DataContext на один запрос.

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

      Во-вторых, NInject страдает от утечек памяти, и прочих странностей, можете прочитать книгу:
      Dependency Injection in .NET
      в которой подробно описываются IoC Framework'и аж на 500 стр.

      В-третьих:
      Идеология REST предполагает: Один URL — один ресурс
      Если для получения ресурса требуются дополнительные параметры, то проблем с этим в WebAPI вообще ни каких нет, создаётся что-то типа такого:

      public class ResourceQuery
      {
         public string Param1 { get; set; }
         public int OptionalParam2 { get; set; }
      }
      
      public class SampleResourceController : ApiController
      {
          public SampleResourceModel Get([FromUri] ResourceQuery query)
          {
              // и тут уж как вам больше нравится: ModelBinder, или самим писать проверки 
              if(!ModelState.IsValid)
              {
                    throw new HttpResponseException(HttpStatusCode.BadRequest);
              }
          }
      }
      
      


  • +1
    В чём смысл поста?

    Есть ведь OData in WebAPI

    OData всё ещё в разработке, а точнее в состоянии RC

    • +1
      это было в RC, они вынесли OData отдельно
  • +1
    Одно время интересовался темой WebAPI, но из за не проработанной поддержки ограничений в условиях запроса (можно только максимальные значения количества выбираемых элементов ограничить) решил отложить внедрение этого подхода.
    Возможно сейчас что то изменилось в этом направлении. Может кто то в курсе?
    • +1
      Вы о чём? Каких ограничений? Размер GET запроса?

      Огорчу я вас, — как было 2Кб так и осталось в IE, в других до 4Кб можно.)
      • +1
        Нужны ограничения в функциональности запроса, т.е. запрет или ограничение на использование той или иной конструкции в запросе для iqueryable ответов.

        Например нужно исключить возможность использовать $skip условие в запросе localhost/api/odata/?$skip=2 или ограничить это значение скажем числом не более 1000
        • +1
          А какой в этом смысл?

          Можете показать т.с. real-world пример, где понадобилось лимитировать OData?
          • +1
            — разграничение прав доступа к данным
            — ограничивание запросов по причине перформанса/тротлинга
            • +1
              разграничение прав доступа к данным


              авторизацию и аутентификацию в MVC никто не отменял, работает и для WebAPI.

              ограничивание запросов по причине перформанса/тротлинга


              это скорее задача веб-сервера, а не веб-сайта.
              • 0
                О'кей, конкретнее и с примерами.
                >разграничение прав доступа к данным:
                Я не хочу что бы пользователь Василий мог выбирать товары по цвету т.к. он не оплатил PRO аккаунт.
                >ограничивание запросов по причине перформанса/тротлинга
                В целях контроля производительности сервера я не хочу позволять пользователям выбирать более 1000 записей из таблицы «логи»(ведь он может 100 раз в секунду запрашивать по 100.000.000 записей из логов)

                Вполне реальные проблемы. Обычно эта логика описывается в контроллере.
          • +1
            Мне нужно что бы ввести разные ограничения доступа до истории записей для разных групп пользователей.
            • +1
              Думаю, это логичнее будет сделать на стороне сервера, т.е. мы берем идентификатор пользователя, получаем его права, и на основании этих прав уже выдаем коллекцию объектов, к которым разрешен доступ, пользователю. А вот уже с помощью OData можно накладывать дополнительные ограничения: пагинация, поиск, сортировка и т.д.
              • +1
                На стороне какого сервера?
                Мне нужно только ограничить выборку значений для пользователей.

                Например пользователям с ролью А — вся история, для пользователей с ролью Б — только последние 1000 записей, для пользователей с ролью В — только 10 последних записей.
                Как ограничить и при этом сохранить функциональность OData где конечный запрос до базы данных формируется уже за кулисами.
                Единственный способ который мне пришел в голову — переопределение фабрик, фильтров и т.д. что бы в конечном счете добраться до лямбда выражения формируемого после разбора и применения OData к IQueryable выражению с последующим анализом синтаксиса.
                Это жутко не удобно и не факт что это вообще можно будет сделать.
                • +1
                  OData вообще ничего не должна знать о правах пользователей и тому подобной логике. Если я правильно вас понял, то с помощью ЕФ это можно реализовать примерно действие контроллера так (псевдокод):
                  public IQueryable<History> History()
                  {
                      var currentRole = currentUser.GetCurrentRole();
                  
                      switch (currentRole)
                      {
                          case Role.A:
                              return context.Histories.AsQueryable();
                              break;
                          case Role.B:
                              return context.Histories.Skip(1000);
                              break;
                          case Role.C:
                              return context.Histories.Take(10);
                              break;
                      }
                  } 
                  

                  Т.е. нашу логику для фильтрации мы добавляем перед отдачей OData, и тогда все условия фильтрации в OData будут достраиваться к уже добавленным нами условиями.
                  • +1
                    В этом случае как быть если в строке запроса для пользователя с ролью C появится вот такая штука $skip=1000

                    Раз запрос достраивается уже после работы основного метода выборки, то нужен механизм контроля за этим процессом, которого я так и не нашел.
                    • 0
                      Сформируется следующий запрос: вложенным запросом выберется десять элементов, и потом из них пропустится 1000 элементов, т.е. по сути ничего не вернет. Профайлер ЕФ у меня дал такой SQL запрос:
                      SELECT
                      [Limit1].[Id] AS [Id],
                      [Limit1].[Name] AS [Name]
                      FROM ( SELECT TOP (10) [c].[Id] AS [Id], [c].[Name] AS [Name], row_number() OVER (ORDER BY [c].[Id] ASC) AS [row_number]
                              FROM [dbo].[Models] AS [c]
                      )  AS [Limit1]
                      WHERE [Limit1].[row_number] > 1000
                      ORDER BY [Limit1].[Id] ASC
                      
                      • +1
                        А как же быть с другими вкусняшками из OData которые позволяют фильтровать мой запрос?
                        • 0
                          Так они будут наложены на этот вложенный запрос (для пользователей с ролью С, в данном случае), т.е. пользователи не смогут получить данные, для которых у них нет прав.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.