Web API и Windows Azure. Часть 1. История, немного про REST, примеры.
Привет.
Начинаю новый цикл статей, на этот раз о Web API и приложении этого дела к Windows Azure. Статей будет неизвестно сколько, может, две, может десять, поэтому по мере наполнения буду добавлять всё в оглавление и потом, как обычно, причешу и соберу в PDF.
В этой части:
* История
* Зачем нужен Web API, если есть WCF, например
* Концепции REST и модель RMM
* Пример первого приложения ASP.NET MVC + Web API + jQuery + AJAX
* Использование OData в Web API
Итак, для того, чтобы понять, зачем всё это нужно, необходимо определиться с объектом изучения и его предназначением. Но сначала немного истории, чтобы было понятнее, почему (по моему мнению) была введена новая сущность в лице Web API.
История
Не углубляясь в подробности того, как было, достаточно сообщить, что Microsoft прошла долгий путь в вопросах разработки сервисоориентированных систем, который оброс несколькими основными столпами: ASMX, .NET Remoting, WCF. Используя веб-сервисы на основе ASMX (это ~2002 год), разработчики могли довольно просто создать веб-сервис, который реализовывал различные версии SOAP, но был доступен только поверх HTTP. Параллельно существовала технология Remoting, которая давала возможность разрабатывать сервисы, которые могли использовать не только HTTP. В 2006 году Microsoft выпустила .NET 3.0 и Windows Communication Foundation (WCF), которая разом покрывала то, что предлагали ASMX и .NET Remoting, и предлагала еще больше новых возможностей, включая поддержку новых стандартов. Например, с WCF можно разработать веб-сервис для TCP, с поддержкой аутентификации по токенам безопасности, и запустить его в собственноручно разработанном сервисе Windows. Лично мое знакомство с WCF началось в 2009 году, когда я написал небольшую оснастку для сбора логов с двух десятков машин в кластере – на головной машине был запущен WCF-сервис, остальные же машины выполняли раз в N минут скрипт на Powershell, который вызывал WCF-сервис и передавал ему данные. Не скажу, что это было совсем просто делать с первого раза, но сама элегантность подхода мне очень понравилась.
В 2007 году в версии CTP был выпущен фреймворк ASP.NET MVC. Уже тогда витала идея о том, что Интернет используется довольно просто – для обмена информацией между веб-страницами используются чаще всего текстовые сообщения, а не бинарные данные, используется HTTP, клиентская часть для обработки различных задач. Это привело к идее разработки того, что имело бы поддержку сериализации только в XML и JSON. Зачем, собственно, мне, если я хочу создать простейший RESTful сервис, использовать в целом местами тяжеловесный WCF, который, тем более, не особо ложился на REST? Вот тут и вступил в игру ASP.NET MVC со своим встроенным механизмом маршрутизации.
Маршрутизация MVC
В отличие от WCF, где сервис = адрес физического файла, механизм маршрутизации в MVC работает по иному принципу – он ассоциирует адрес с методом контроллера.
Если рассматривать пример, то, если бы имели сервис WCF, то обращение к нему происходило бы так:
Если ASP.NET MVC, то так:
http://server/MyService/Get/123
Как видите, подобная адресация позволяет скрыть детали внутренней реализации. С помощью маршрутизации MVC мы можем перевести запрос по вышеупомянутому адресу на любой метод любого контроллера. Все это приводит к гораздо более простой поддержке приложения – мы можем менять внутреннюю реализацию так, как представляется удобным, и, например, быстро менять старую версию реализации на новую, всего лишь подправив конфигурацию маршрутизации. Создание же Web API (в общем-то, "нашлепка" на ASP.NET) позволило разрабатывать с использованием этого механизма маршрутизации HTTP REST-сервисы, которые работают в одном проекте с ASP.NET MVC и, в общем-то, полностью интегрируются в контекст (не имплементации, а логический) MVC. Но, перед тем, как начать писать REST-сервис с Web API, нужно определиться с тем, что я называю REST-сервисом. Об этом – ниже.
Немного про REST
Многие думают, что если сделать линки красивыми, то их API уже будет 100% REST. На самом деле это, конечно же, не так, и REST, упомянутый впервые в диссертации одного умнейшего человека, не так прост, как кажется. Правила построения REST-сервисов диктуют также и ограничения, которые накладываются далее на архитектуру, и это надо учитывать и соглашаться (или не соглашаться).
Для того, чтобы понять, что под собой подразумевает путь к REST, можно воспользоваться моделью RMM (REST Maturity Model), представленной Леонардом Ричардсоном в 2008 году. Те, кто знаком с CMMI (Capability Maturity Model Integration), сразу увидят сходство в словах – оно неслучайно. Эта модель, как и CMMI, описывает несколько уровней соответствия некоторому своду правилу, методологии. RMM содержит 4 уровня – от 0 до 3. В этой модели все начинается с самого простого на уровне 0, когда API соответствует стилю RPC, и заканчивается удовлетворяющим всем основным парадигмам REST. Разумеется, что, если использовать эту модель для описывания собственной системы, если засели на каком-то уровне, меньшем последнего – этот сервис не REST. Давайте сначала посмотрим на не очень красивую картинку, схематично изображающую RMM, после чего перейдем к примеру и разберем его согласно всем уровням RMM. Примером нам будет служить сервис гостевой книги.
Уровень 0
Согласно уровню 0, у нас был бы простой WCF-сервис GuestService, у которого был бы один метод CreatePost(), который принимал бы аргументы заголовка записи, имени пользователя и его e-mail. В ответ метод возвращал бы номер записи. Специально для администратора было бы еще несколько методов – DeletePost() и UpdatePost(). Каждый из них принимал бы какое-то сообщение и возвращал какой-то ответ. И, конечно же, GetAllPosts(). Обычная система, ничего примечательного.
Таким образом, наш сервис на уровне 0 RMM выглядел бы так:
Метод | URL | HTTP-метод | Видимость |
CreatePost | /api/GuestService.svc | POST | Например, WSDL |
DeletePost | /api/GuestService.svc | POST | Например, WSDL |
UpdatePost | /api/GuestService.svc | POST | Например, WSDL |
GetAllPosts | /api/GuestService.svc | POST | Например, WSDL |
Согласно таблице, у нас есть некоторое API, каждая из операций которого имеет одну и ту же ссылку (URL). В целом же ссылка выглядит совершенно непонятно и непривязано к контексту – удаляем мы запись с номером 1, или обновляем пост с номером 100 – URL будет тот же самый. Эта система удовлетворяет уровню 0 RMM и основным характеристикам – один URL = один HTTP-метод. Если смотреть на HTTP-методы, то тут все тоже одинаково – везде POST, да еще и методы самому приходится внутри создавать. Одно из правил RESTful HTTP – не надо создавать самому какие-то методы. Надо соответствовать тому списку, который содержит доступные HTTP verbs. Но об этом чуть позже.
Да, и, конечно, в нашей ситуации с данным сервисом клиент должен каким-то образом знать о том, как вызвать то или иное действие, то есть должна наличествовать определенная связь, например, клиент должен знать о контрактах (вот он, WSDL). Это тоже нехорошо – лишние связи – зачем они нам? Тем более что REST гласит, что нужно знать только основной адрес, все остальные операции должны быть доступны через так называемые гипермедиа-элементы (ссылки, формы, элементы управления). Сервер должен управлять всем процессом – диктовать то, как должны выглядеть ссылки, формы, забирая эти знания на себя и не делясь им с клиентом, чтобы, если что-то изменилось, быстро отреагировать на эти изменения. Это все – компоненты и части принципа HATEOAS, о котором позже. Пока же перейдем на уровень 1.
Уровень 1
Итак, мы захотели превратить наш сервис в REST-сервис. Что же мы должны сделать? Во-первых, удовлетворить правилам уровня 1. Правила уровня 1 гласят, что REST-сервис должен быть ресурсоориентированным, то есть ориентироваться не на запрос-ответ и методы, как в RPC, а на ресурсы, ограничиваемые набором HTTP verbs. И всё. Есть набор доступных HTTP verbs – больше ничего не надо. GET, DELETE, POST, иногда PUT и еще более иногда HEAD и иже с ними. Этот принцип основополагающ для REST-сервисов – если у нас сервис предлагает большую пачку различных методов, он не будет REST (согласно RMM).
Таким образом, следуя правилу уровня 1 “Много URL, один HTTP метод”, построим новую табличку для API.
Метод | URL | HTTP-метод | Видимость |
CreatePost | /api/posts | POST | Например, WSDL |
DeletePost | /api/posts/1 | POST | Например, WSDL |
UpdatePost | /api/posts/1 | POST | Например, WSDL |
GetAllPosts | /api/posts | POST | Например, WSDL |
Но и тут образовывается проблема RESTful-ности – клиент продолжает не представлять о том, чем отличаются ссылки /api/posts и /api/posts – без контракта и, например, WSDL. Клиент должен иметь контракт. Связность продолжает наблюдаться, и, кроме этого, продолжает наблюдаться один HTTP-метод. Но, по крайней мере, наш сервис перебрался на 1 уровень модели RMM. Попробуем перейти на 2.
Уровень 2
Правило уровня 2 = “Много URL = Много HTTP-методов”. Здесь в полной мере надо переосмыслить свой сервис в сторону ресурсоориентированности. Что приходит от клиента? Как это обрабатывать? Куда посылать?
По факту, конечно, нет такого HTTP-метода, как CreatePost, и GetAllPosts тоже нет. Но есть POST и GET! И PUT и DELETE тоже. Мы можем ассоциировать наши методы, возможно, кое-что переименовав, и перейти на уровень 2. Здесь нужно уточнить, что HTTP-методы имеют некоторые особенности, например, PUT и DELETE идемпотентны. Это значит, что сколько бы мы не вызвали эти методы, они возвратят один и тот же результат- HTTP-ответ – например, сколько бы мы не вызывали PUT, будет изменяться одна и та же сущность. POST же не идемпотентен по понятной причине – создается новая сущность. GET не идемпотентен, но безопасен – в системе ничего не меняется и не должно меняться при любом количестве вызовов GET. Это очень важно, так как я встречал одну систему, в которой в методе GET на стороне сервера происходила логика изменения состояния сущностей в источнике данных. Это неправильно.
Посмотрим теперь, что произошло с нашим сервисом на уровне 2.
Метод | URL | HTTP-метод | Видимость |
CreatePost | /api/posts | POST | Например, WSDL |
DeletePost | /api/posts/1 | DELETE | Например, WSDL |
UpdatePost | /api/posts/1 | PUT | Например, WSDL |
GetPost | /api/posts | GET | Например, WSDL |
Уже лучше. Теперь у нас сервис, который имеет набор соответствующих HTTP-методов и различные URL (по большей части). Но мы всё ещё связаны с клиентом. Чтобы решить это, пойдем на уровень 3.
Уровень 3
Уровень 3 характеризуется одной емкой аббревиатурой – HATEOAS. Посыл этой аббревиатуры заключается в том, что клиент не должен знать ничего, кроме начального адреса сайта. Все остальное ему подскажет сам сайт – с помощью ссылок, кнопок, элементов управления. Клиент должен получать доступ к ресурсам и их управлению без предварительных знаний о том, как это сделать.
Нарушить связность можно, предоставив возможность клиенту менять состояние, используя то, что возвращает сервис – например, если клиент перешел на определенный адрес, ему может быть возвращены соответствующие данные. В любом случае, создатель сервиса должен быть уверен, что при нажатии на кнопку Submit веб-формы будет инициирован POST-запрос и вызван соответствующий метод.
Итак, удовлетворяющее 3 уровню API приведено в таблице. Хотелось бы обратить внимание, что, несмотря на всё это, даже если сервис удовлетворяет всем условиям RMM, могут быть определенные нюансы, которые нарушат всё.
Метод | URL | HTTP-метод | Видимость |
CreatePost | /api/posts | POST | HTTP POST |
DeletePost | /api/posts/1 | DELETE | HTTP DELETE |
UpdatePost | /api/posts/1 | PUT | HTTP PUT |
GetPost | /api/posts | GET | HTTP GET |
И последнее про REST
И последнее про REST – если удовлетворять стандартам, образовавшимся вокруг HTTP, то нельзя забывать про коды HTTP. Сервис должен возвращать известные коды ошибок. Это необязательное условие, но, если эти коды есть, значит, их придумали и утвердили умные люди, и желательно использовать их, чтобы тот, кто будет поддерживать сервис, не увидел ничего, что нарушило бы его уверенность в том, что автор сервиса писал код в сознательном состоянии.
Ниже приведена таблица с извесными кодами HTTP.
Код статуса | Значение |
200 | ОК, запрос выполнен успешно. |
201 | Запись создана – может включать ссылку на созданный ресурс |
202 | То же самое, что и 200, но используется в связке с асинхронной операцией. |
301 | Запрашиваемый ресурс был перемещен – нужно включать в сообщение URL на новое расположение ресурса |
400 | Плохо оформленный запрос. |
401 | Unauthorized – клиенту не разрешен доступ к ресурсу. |
403 | Доступ запрещен – клиент аутентифицировался, но не прошел авторизацию. |
404 | Ресурс не найден либо клиент не имеет доступа и не должен знать, почему. |
409 | Конфликт на сервере – используется в ответ на PUT, когда несколько клиентов используют один ресурс. |
500 | Ошибка на сервере. |
Web API
Напомню, что Web API появился в ASP.NET MVC 4 и сразу же вызвал достаточно много разговоров о себе. Очередная технология для помощи в создании веб-сервисов, опять REST, зачем нам это нужно? Это хороший вопрос – ведь есть всякие WCF и иже с ним. Но всё это вещи, если можно так выразиться, глобальные, и, когда нужно реализовывать какой-то небольшой сценарий, то надо тянуть за собой еще немного того, что не нужно и неизвестно, пригодится ли. Web API – это как раз то средство, которое позволяет очень просто, быстро и красиво реализовать сценарий RESTful-сервиса с использованием HTTP.
Создадим ASP.NET MVC 4 приложение и выберем соответствующий Web API шаблон.
После того, как проект будет создан, вы увидите обыкновенное MVC-приложение. Первой точкой входа будет новый контроллер – ValuesController, который является специализированным контроллером WebAPI и наследуется, в свою очередь, не от Controller, а от ApiController.
Как и в любом другом контроллере, scaffolding уже создает некоторый код, в случае Web API-контроллера это "болванки" для методов, замыкающих на себя HTTP verbs – GET, POST, PUT и DELETE.
HTTP Verb | Метод контроллера | Описание |
Get() | GET | Метод возвращает набор данных в виде массива. Может быть возвращен любой тип коллекции IEnumerable. |
Get(string) | GET | Метод возвращает одну сущность, которая связана с тем, что передается в виде string (часто это ID). |
Post(string) | POST | Метод добавляет новую сущность в систему. |
Put(string,string) | PUT | Метод обновляет существующую сущность. Различие между Put и Post состоит в том, что Post всегда создает новую сущность, Put – нет. |
Delete(string) | DELETE | Метод удаляет сущность из системы. |
Теперь добавим контекст для тестовой базы данных. В данном цикле статей я буду использовать тестовую базу данных, которую всегда использую, когда веду курсы в http://atraining.ru. Она состоит всего из двух очень простых несвязанных таблиц – CourseSet и Student. В дальнейшем, может, и усложнится, но это непринципиально для обсуждаемой темы.
Внесем изменения в контроллер WebAPI, определив соответствующим образом поведение его методов. Так как код очень простой и понятный, приведу его без комментариев.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Web.Http;
using WebApiJsonAjax.Models;
namespace WebApiJsonAjax.Controllers
{
public class ValuesController : ApiController
{
private atrainingdatabaseEntities _ctx = new atrainingdatabaseEntities();
// GET api/values
public List<Models.Student> Get()
{
var students = from e in _ctx.Student select e;
return students.ToList();
}
// GET api/values/5
public string Get(int id)
{
return "value" + id;
}
public Student Post(Student student)
{
return student;
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
var student = _ctx.Student.Where(s => s.id == id).FirstOrDefault();
_ctx.Student.Remove(student);
_ctx.SaveChanges();
}
}
}
Обращу внимание, что я не просто так где-то использую нотацию LINQ в виде расширений и лямбд, а где-то – в ином виде. Просто чтобы запомнить. На самом деле мне кажется, что нотация LINQ в виде расширений гораздо более понятна, несмотря на активное использование лямбд, но – на вкус и цвет.
Сохраним контроллер и попробуем протестировать то, что получилось. Нажмем F5 и перейдите на ссылку http://localhost:[port]/api/values . Обратим внимание, что при обращении с GET-запросом к этой ссылке будет выдан результат в виде JSON. WebAPI умеет работать и возвращать значения в виде JSON и XML, это зависит от того, что клиент понимает. Посмотрим, что он возвратит в Chrome и IE (скриншоты расположены соответственно), чтобы подойти к еще одной замечательной особенности Web API под названием Content Negotiation.
Весь процесс сериализации в JSON объекта типа Student взял на себя фреймворк, что не может не радовать – в отличие, например, от использования ASP.NET MVC, когда для того, чтобы возвратить JSON из контроллера, нужно использовать return Json явным образом. Давайте рассмотрим, почему же в двух браузерах сервис возвратил результат в разных форматах.
Content Negotiation
В стандарте HTTP есть такое понятие, как Content Negotiation, которое означает процесс согласования между клиентом и сервером деталей относительно их коммуникаций. Когда клиент обращается к серверу, он отправляет в запросе директиву Accept, в которой пишет, что и в какой мере он ожидает увидеть от сервера в ответе. Попробуем выполнить тот же самый запрос GET, но с помощью утилиты curl – с ее помощью можно довольно просто отслеживать, что происходит во время запроса. Дополнительно будут скриншоты из Fiddler. Также посмотрим запросы, которые отправляются Chrome и IE, с помощью интегрированных в сами браузеры средств.
Интерфейс Fiddler, с которым мы будем также работать параллельно curl.
Значение Accept – это то, что хотел бы увидеть клиент от сервера. Как видно из скриншотов, Chrome хочет видеть text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 (q – это “вес” формата, чем больше вес, тем больше “хочет” клиент увидеть результат именно в этом формате и указывает на это серверу). IE же хочет видеть text/html, application/xhtml+xml, */*. Хочу обратить внимание, что Web API не распознает то, что хочет видеть IE, и отправляет результат по умолчанию в JSON – у него нет поддержки application/xhtml+xml, но есть поддержка */*, то есть “приму всё, что предложит сервер”. Chrome же получает то, что хочет, в желаемом формате XML.
Давайте теперь посмотрим, как можно это сделать в curl и Fiddler и имитировать некоторые аспекты поведения. Обратимся с запросом GET.
Ответ в JSON, так как клиенту все равно, в каком формате придет результат.
Давайте укажем явным образом, что мы хотим увидеть.
Замечательно – формат ответа зависит от того, чего хочет клиент.
Этот процесс и называется Content Negotiation, и он является очень важным процессом в коммуникациях между клиентом и сервером. Хочу также обратить внимание на то, с какой простотой и элегантностью решен выбор между форматами. Указание же формата результата на сервере сопряжено с несколько более сложным кодом, который приведен ниже.
public HttpResponseMessage Get()
{
var students = (from e in _ctx.Student select e);
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new ObjectContent<IEnumerable<Student>>(students, new JsonMediaTypeFormatter());
resp.Headers.ConnectionClose = true;
resp.Headers.CacheControl = new CacheControlHeaderValue();
resp.Headers.CacheControl.Public = true;
return resp;
}
Если вы хотите возвратить результат именно в том формате, в котором хотите _вы_, не учитывая клиента, то необходимо получить доступ к сообщению, которое будет формироваться для результата, и определить его свойства, передав его специальному механизму – Formatter. Конечно же, вы можете писать свои конвертеры со сколь угодно сложной логикой.
Итак, в коде выше происходит:
1) Получаются данные из коллекции Student
2) Создается экземпляр класса HttpResponseMessage со статусом 201 OK. Это сообщение будет возвращено клиенту.
3) В содержание сообщения вкладывается наша модель данных, которая конвертируется в формат JSON.
4) Сообщение возвращается клиенту.
Давайте повторим наш запрос в curl, указав, что мы хотим видеть только application/xml.
Несмотря на Accept: application/xml, результат был возвращен в формате JSON.
Content Negotiation в Web API во всей своей красоте. А мы двигаемся дальше.
Использование Web API из представления MVC с помощью jQuery
У нас уже есть почти готовый REST-сервис. Но в Web API вполне закономерно не предусмотрен механизм генерации представлений. Свяжем сервис с представлением MVC.
Использовать Web API мы будем из представления Index у HomeController. С помощью jQuery заберем данные из сервиса и нарисуем простую табличку.
Привожу весь код страницы.
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">
<a href="~/">ASP.NET Web API</a></p>
</div>
</div>
</header>
<div id="body">
<section class="featured">
<div class="content-wrapper">
<table id="students"></table>
<script>
$(function () {
var $students = $("#students");
$.ajax({
url: "api/values",
contentType: "json",
success: function(data) {
$.each(data, function(index, item) {
var $row = $("#templates").find(".row-template").clone();
$row.find(".Name").html(item.name);
$row.find(".delete").click(function() {
$.ajax({ url: "api/values/" + item.id,
type: "DELETE",
success: function() {
$row.remove();
}
});
});
$students.append($row);
});
}
});
})
</script>
</div>
</section>
<section class="content-wrapper main-content clear-fix">
<div id="templates" style="display:none">
<table>
<tr class="row-template">
<td class="Name"></td>
<td><input type="button" value ="Del" class="delete"/></td>
</tr>
</table>
</div>
</section>
</div>
Код достаточно прост – мы получаем из сервиса данные и динамически заполняем таблицу. Ниже приведен скриншот текущего представления
. К каждой записи привязана кнопка, которую можно нажать, чтобы удалить запись.
Добавим остальную функциональность – PUT и POST. Для этого надо написать достаточно много кода. Приведу полные листинги представления и контроллера с выделенными красным участками измененного кода.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Web.Http;
using WebApiJsonAjax.Models;
namespace WebApiJsonAjax.Controllers
{
public class ValuesController : ApiController
{
private atrainingdatabaseEntities _ctx = new atrainingdatabaseEntities();
public HttpResponseMessage Get()
{
var students = (from e in _ctx.Student select e);
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new ObjectContent<IEnumerable<Student>>(students, new JsonMediaTypeFormatter());
resp.Headers.ConnectionClose = true;
resp.Headers.CacheControl = new CacheControlHeaderValue();
resp.Headers.CacheControl.Public = true;
return resp;
}
// GET api/values/5
public string Get(int id)
{
return "value" + id;
}
public HttpResponseMessage Post([FromBody]Student student)
{
try
{
_ctx.Student.Add(student);
_ctx.SaveChanges();
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e.Message);
};
return Request.CreateResponse(HttpStatusCode.OK);
}
// PUT api/values/5
public HttpResponseMessage Put(int id, Student student)
{
try
{
var studentToChange = _ctx.Student.FirstOrDefault(s => s.id == id);
studentToChange.name = student.name;
_ctx.SaveChanges();
} catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e.Message);
};
return Request.CreateResponse(HttpStatusCode.OK);
}
// DELETE api/values/5
public void Delete(int id)
{
var student = _ctx.Student.Where(s => s.id == id).FirstOrDefault();
_ctx.Student.Remove(student);
_ctx.SaveChanges();
}
}
}
В контроллере появилась реализация методов PUT и POST. В обоих методах операции завернуты в блок try/catch и, если что-то не получается, создается ответ, в который вкладывается сообщение об ошибке. Здесь нужно еще упомянуть дополнительный атрибут, который может изрядно помочь в работе – вы можете пометить аргумент метода контроллера как [FromBody] либо [FromUrl], что означает, соответственно, брать значение этого аргумента из тела запроса и из URL.
Код представления ниже. В него добавлены обработчики событий нажатия на кнопки изменения и создания записей. Хочу обратить внимание на использование JSON.stringify – эта функция конвертирует предложенное ей в JSON.
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">
<a href="~/">ASP.NET Web API</a></p>
</div>
</div>
</header>
<div id="body">
<section class="featured">
<div class="content-wrapper">
<table id="students"></table>
<script>
$(function() {
var $students = $("#students");
$.ajax({
url: "api/values",
contentType: "json",
success: function(data) {
$.each(data, function(index, item) {
var $row = $("#templates").find(".row-template").clone();
$row.find(".Name").html("<input type=’text’ class=’studentname’ id=’" + item.id + "’ value=’" + item.name + "’></input>");
$row.find(".delete").click(function() {
$.ajax({
url: "api/values/" + item.id,
type: "DELETE",
success: function() {
$row.remove();
}
});
});
$row.find(".change").click(function() {
var student = {
id: item.id,
name: $row.find(".studentname").attr("value")
};
$.ajax({
url: "api/values/" + item.id,
type: "PUT",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(student),
success: function() {
}
});
});
$students.append($row);
});
}
});
});
function addStudent() {
var student = {
name: $("#frm").find("#name").attr("value"),
};
$.ajax({
url: "api/values",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(student),
success: function() {
alert("Added!");
}
});
}
</script>
</div>
</section>
<section class="content-wrapper main-content clear-fix">
<div id="templates" style="display:none">
<table>
<tr class="row-template">
<form>
<td class="Name"></td>
<td><input type="button" value ="Change" class="change"/></td>
<td><input type="button" value ="Del" class="delete"/></td>
</form>
</tr>
</table>
</div>
<form id="frm">
<input type="text" name="name" id="name"/>
<td><input type="button" value ="Add" onclick="return addStudent();"/></td>
</form>
</section>
</div>
Сымитируем поведение операций создания, обновления и удаления с помощью curl и Fiddler. Обратите внимание, что в curl тело сообщения задается особым образом – если у нас сложная модель, то вводится name=Sychev, например, если какое-то одиночное значение, то необходимо, чтобы тело выглядело как -d "=Sychev".
POST:
PUT:
DELETE:
А вот, что бывает, если возвращать клиенту в ответе сообщение при ошибке.
Все, у нас готов REST-сервис на Web API с использованием ASP.NET MVC и jQuery. Теперь рассмотрим интересную дополнительную функциональность.
Поддержка OData в Web API
ASP.NET Web API имеет встроенную поддержку для некоторых параметров запросов OData, например, сортировки, фильтрации и разбиения на подмножества (paging).
Для того, чтобы начать использовать OData в Web API, необходимо установить соответствующий пакет с помощью NuGet. Вместе с пакетом OData установится пачка зависимостей.
Заменим метод Get на следующий код.
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Student> Get()
{
return _ctx.Student.AsQueryable();
}
Для использования метода в OData нужно пометить его как [Queryable] и указать, что он должен возвращать IQueryable. Сериализовать данные OData будет несколько иным образом, каким – мы посмотрим чуть ниже.
Теперь добавим в файл WebApiConfig в папке App_Start несколько вкусностей, которые попробуем опять же чуть ниже. Код WebApiConfig должен выглядеть как код ниже.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
GlobalConfiguration.Configuration.Formatters.JsonFormatter.AddQueryStringMapping("$format", "json", "application/json");
GlobalConfiguration.Configuration.Formatters.XmlFormatter.AddQueryStringMapping("$format", "xml", "application/xml");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Запустим проект и перейдем по ссылке http://localhost:[port]/api/values. Будет выведен XML, возвращаемый методом по OData.
Обратите внимание, что данные сериализуются несколько по-иному. Это нужно учитывать при разработке.
Теперь можно воспользоваться возможностями OData. Например, перейти по такой ссылке:
http://localhost:61020/api/values?$filter=(id eq 6)
Или по такой:
http://localhost:61020/api/values?$filter=(id eq 6)&$format=json
Format – нестандартная процедура, мы ее добавили дополнительно в файл WebApiConfig.
Используя OData, можно с помощью формирования ссылок в специальном легкоусвояемом расширяемом формате взаимодействовать с моделью данных, хранимой на сервере. Конечно же, нужно быть аккуратным, несмотря на то, что Web API OData предоставляет read only доступ к данным.
Спасибо за внимание.
Не получилось вызвать с помощью $.ajax({
url: “api/values/” + item.id,
type: “PUT”,
contentType: “application/json; charset=utf-8”,
data: JSON.stringify(student),
success: function() {
}
});
метод PUT
Какая ошибка?