RESTFul Api контроллеры в .NET MVC 4

от автора

Приветствую.
Летом вышел релиз новой версии фреймворка, но поработать с ним получилось только недавно. В новой версии было добавлено много полезных штук, об одной из них, а именно 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

ссылка на оригинал статьи http://habrahabr.ru/post/164945/


Комментарии

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

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