Летом вышел релиз новой версии фреймворка, но поработать с ним получилось только недавно. В новой версии было добавлено много полезных штук, об одной из них, а именно 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 Method — PUT и Content type — application/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/
Добавить комментарий