Одна из особенностей ASP.NET MVC, которые нравятся мне больше всего, — возможность предоставлять фасад методов, легко запускаемых из HTTP-клиентов, включая страницы на основе jQuery, мобильные приложения и код на чистом C#. В течение длительного времени создание этого уровня сервисов осуществлялось в царстве сервисов Windows Communication Foundation (WCF). Предпринимались попытки специализации WCF для HTTP, например введение механизма webHttpBinding и инфраструктур наподобие REST Starter Kit, теперь уже отправленного на свалку истории. Однако ни один из этих подходов на практике так и не позволил убрать с пути разработчиков такие препятствия, как слишком утомительное конфигурирование WCF, чрезмерное использование атрибутов и структур, не рассчитанных на тщательное тестирование. Затем появилась технология Web API — новая облегченная инфраструктура, ориентированная на HTTP, обеспечивающая тестируемость и независимая от среды хостинга (например, от IIS).
Но Web API — это еще и программный интерфейс, который, по моему мнению, выглядит даже слишком похоже на ASP.NET MVC. Впрочем, это замечание не несет в себе негатив, потому что ASP.NET MVC является четким и тщательно определенным программным интерфейсом. На самом деле Web API изначально была моделью программирования, которая выглядела похожей на WCF, а уж потом выросла в подобие ASP.NET MVC.
В этой статье я дам обзор Web API с точки зрения среднего разработчика, использующего ASP.NET MVC, и сосредоточусь на функциональной области Web API, которая обладает преимуществом по сравнению с чистой ASP.NET MVC: согласование контента (content negotiation).
Краткий обзор Web API
Web API — инфраструктура, с помощью которой можно создать библиотеку классов, способных обрабатывать HTTP-запросы. Конечную библиотеку (наряду с некоей начальной конфигурацией) можно разместить в исполняющей среде, и к ней можно обращаться по HTTP. Открытые методы в классах контроллеров становятся конечными точками HTTP. Настраиваемые правила маршрутизации помогают определить форму URL, используемых для доступа к конкретным методам. Однако за исключением маршрутизации (routing) большая часть того, что определяет форму URL по умолчанию в Web API, является соглашением, а не конфигурацией.
Если вы занимаетесь разработкой на основе ASP.NET MVC, то в этот момент, возможно, прервете чтение и поинтересуетесь, с какой стати вам может понадобиться новая инфраструктура, которая вроде бы просто дублирует концепцию контроллеров, «уже имеющуюся» в ASP.NET MVC.
Если кратко, то да, по-видимому, вам не требуется Web API в ASP.NET MVC, поскольку вы можете получить почти ту же функциональность через обычные контроллеры. Например, можно легко возвращать данные в виде JSON, XML-строк, двоичных данных или чистого текста. Вы можете формировать шаблоны URL так, как считаете необходимым.
Тот же класс контроллера может обслуживать JSON-данные или HTML-представление, и вы можете легко отделить контроллеры, возвращающие HTML, от контроллеров, которые просто возвращают данные. По сути, на практике часто включают класс ApiController в проект, куда вы помещаете все конечные точки, возвращающие только данные. Вот пример:
public class ApiController : Controller {
public ActionResult Customers()
{
var data = _repository.GetAllCustomers();
return Json(data, JsonRequestBehavior.AllowGet); }
…
}
Web API использует лучшее из архитектуры ASP.NET MVC и расширяет ее в двух основных областях. Во-первых, вводится новый логический уровень, известный как согласование контента со стандартным набором правил для запроса данных в определенном формате — JSON, XML или каком-то другом. Во-вторых, в Web API нет никаких зависимостей от ASP.NET и IIS, а точнее, от библиотеки system.web.dll. Конечно, ее можно разместить в приложении ASP.NET под управлением IIS. Однако, хотя это, вероятно, остается пока самым распространенным сценарием, библиотеку Web API можно разместить в любом другом приложении, которое предоставляет среду хостинга, например в Windows-службе, WPF- или консольном приложении.
В то же время, если вы эксперт в разработке с применением ASP.NET MVC, концепции Web API в отношении контроллеров, привязки модели, маршрутизации и фильтров операций будут вам знакомы.
Почему разработчики, использующие Web Forms, любят Web API
Если вы разработчик под ASP.NET MVC, то поначалу, возможно, не уловите преимуществ Web API, поскольку ее модель программирования выглядит почти идентично таковой в ASP.NET MVC. Однако, если вы разработчик под Web Forms, ничто смущать вас не должно. С помощью Web API предоставлять конечные точки HTTP из приложения Web Forms превратится в детскую игру. От вас потребуется лишь добавить один или более классов, похожих на следующий:
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
Заметьте: это тот же код, которым вы воспользовались бы для добавления контроллера Web API в приложение ASP.NET MVC. Кроме того, вы должны указать маршруты (routes). Вот кое-какой код, который должен выполняться при запуске приложения:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional });
Если с помощью атрибута NonAction не задано иное, любые открытые методы класса, соответствующие исходным соглашениям по именованию и маршрутизации, являются открытыми конечными точками с поддержкой HTTP-вызовов. Их можно вызывать из любого клиента без необходимости в генерации прокси-классов, ссылок на web.config или специального кода.
Соглашения по маршрутизации в Web API требуют, чтобы URL начинался с «/api», за которым следует имя контроллера. Заметьте, что здесь нет четкого выражения имени операции. Операция определяется типом запроса: GET, PUT, POST или DELETE. Имя метода, начинающееся с Get, Put, Post или Delete, по соглашению сопоставляется с соответствующей операцией. Например, метод GetTasks в TaskController будет вызываться для любого GET-запроса на такой URL, как /api/task.
Web API — независимо от кажущегося сходства поведения и имен классов с таковыми в ASP.NET MVC — содержится в отдельном наборе сборок и использует совершенно другой набор типов; System.Net.Http является основной сборкой.
Согласование контента в Web API
Термин «согласование контента» (content negotiation) часто используется для описания процесса анализа структуры входящего HTTP-запроса, чтобы выяснить форматы, в которых клиент готов получать ответы. Однако с технической точки зрения, согласование контента — это процесс, в котором клиент и сервер определяют лучший из возможных форматов представления для последующего использования при взаимодействии друг с другом. Анализ запроса обычно подразумевает просмотр пары HTTP-заголовков, таких как Accept и Content-Type. В частности, Content-Type применяется на сервере для обработки POST- и PUT-запросов, а на клиенте — для выбора компонента форматирования HTTP-ответов. Content-Type не используется для GET-запросов.
Но внутренняя механика согласования контента намного сложнее. Ранее упомянутый сценарий самый типичный (ввиду использования соглашений и реализаций по умолчанию), но он не является единственно возможным.
Процессом согласования в Web API управляет класс DefaultContentNegotiator. Он реализует открытый интерфейс (IContentNegotiator), поэтому при необходимости его можно полностью заменить. На внутреннем уровне механизм согласования (negotiator) по умолчанию применяет несколько критериев, чтобы установить идеальный формат ответа.
Механизм согласования работает со списком зарегистрированных компонентов форматирования (formatters) media-типов — они преобразуют объекты в указанный формат. Механизм согласования перебирает список компонентов форматирования и останавливается, обнаружив первое совпадение. У компонента форматирования есть пара способов сообщить механизму согласования о том, что он может сериализовать ответ для текущего запроса.
Первая проверка выполняется для контента набора MediaTypeMappings, который по умолчанию пуст во всех предопределенных компонентах форматирования media-типов. Сопоставление media-типа указывает условие: если проверка успешна, компонент форматирования должен сериализовать ответ на входящий запрос. Существует несколько сопоставлений предопределенных media-типов. Одно из них увязано на определенный параметр в строке запроса. Например, вы можете разрешить XML-сериализацию, просто указав, что в строку запроса, используемую для вызова Web API, добавляется выражение xml=true. Чтобы это произошло, вы должны поместить в конструктор собственного компонента форматирования media-типа в XML следующий код:
MediaTypeMappings.Add(new QueryStringMapping("xml", "true", "text/xml"));
Аналогично вы можете выразить свои предпочтения, добавив расширение к URL или включив собственный HTTP-заголовок:
MediaTypeMappings.Add(new UriPathExtensionMapping("xml", "text/xml"));
MediaTypeMappings.Add(new RequestHeaderMapping("xml", "true",
StringComparison.InvariantCultureIgnoreCase, false,"text/xml"));
В случае расширения пути URL это означает, что следующий URL будет сопоставлен с компонентом форматирования в XML:
http://server/api/news.xml
Заметьте, что для работы расширений пути URL нужно иметь специальный маршрут (ad hoc route), например:
config.Routes.MapHttpRoute(
name: "Url extension",
routeTemplate: "api/{controller}/{action}.{ext}/{id}",
defaults: new { id = RouteParameter.Optional }
);
В случае собственных HTTP-заголовков конструктор класса RequestHeaderMapping принимает имя заголовка, его ожидаемое значение и пару дополнительных параметров. Один из необязательных параметров указывает желательный режим сравнения строк, а другой — имеет тип Boolean и задает, следует ли выполнять сравнение применительно ко всей строке. Если механизм согласования не может найти совпадение по компоненту форматирования, используя информацию сопоставления media-типа, он просматривает стандартные HTTP-заголовки вроде Accept и Content-Type. Если совпадение не найдено, он вновь перебирает список зарегистрированных компонентов форматирования и проверяет, можно ли сериализовать возвращаемый запросом тип с помощью одного из компонентов форматирования.
Чтобы добавить собственный компонент форматирования, вставьте в стартовый код приложения (например, в метод Application_Start) нечто вроде следующего кода:
config.Formatters.Add(xmlIndex, new NewsXmlFormatter());
Настройка процесса согласования
По большей части сопоставления media-типов позволяют легко реализовать любые специфические требования к сериализации. Однако вы всегда можете заменить механизм согласования по умолчанию, написав производный класс и переопределив метод MatchRequestMediaType:
protected override MediaTypeFormatterMatch MatchRequestMediaType(
HttpRequestMessage request, MediaTypeFormatter formatter)
{
...
}
Вы можете создать свой механизм согласования контента с помощью нового класса, который реализует интерфейс IContentNegotiator. Создав такой механизм, вы регистрируете его в исполняющей среде Web API:
GlobalConfiguration.Configuration.Services.Replace(
typeof(IContentNegotiator),
new YourOwnNegotiator());
Этот код обычно помещается в global.asax или в один из удобных обработчиков конфигурации, которые Visual Studio создает за вас посредством шаблона проекта ASP.NET MVC Web API.
Управление форматированием контента из клиента
Наиболее распространенный сценарий для согласования контента в Web API — применение заголовка Accept. Этот подход делает форматирование контента полностью прозрачным для вашего кода Web API. Вызывающий устанавливает должным образом заголовок Accept (например, как text/xml), и инфраструктура Web API соответственно его обрабатывает. Следующий код показывает, как задать заголовок Accept в jQuery-вызове конечной точки Web API, чтобы получить некий ответ в виде XML:
$.ajax({
url: "/api/news/all",
type: "GET",
headers: { Accept: "text/xml; charset=utf-8" }
});
В коде на C# вы задаете заголовок Accept так:
var client = new HttpClient();
client.Headers.Add("Accept", "text/xml; charset=utf-8");
Любой HTTP API в любой среде программирования позволяет определять HTTP-заголовки. И если вы предвидите, что у вас будут вызовы, где они могут создать проблемы, лучший способ их решения — добавить сопоставление media-типа так, чтобы URL содержал всю требуемую информацию о форматировании контента.
Учтите, что ответ прямо зависит от структуры HTTP-запроса. Попробуйте запросить Web API URL из адресной строки Internet Explorer 10 или Chrome. Не удивляйтесь, когда вы обнаружите, что получили JSON в первом случае и XML во втором. Заголовки Accept по умолчанию могут различаться в разных браузерах. В принципе, если некий API будет использоваться третьими сторонами, вы должны иметь механизм выбора формата вывода, основанный на URL.
Области применения Web API
С архитектурной точки зрения, Web API — большой шаг вперед. Эта инфраструктура становится еще важнее после недавнего выпуска NuGet-пакета Open Web Interface for .NET (OWIN) (Microsoft.AspNet.WebApi.Owin) и Project Katana, которые облегчают размещение API во внешних приложениях через стандартный набор интерфейсов. Если вы создаете решения, отличные от приложений ASP.NET MVC, применить Web API элементарно. Но какой смысл использовать Web API в веб-решении, основанном на ASP.NET MVC?
В случае чистой ASP.NET MVC вы можете легко создать HTTP-фасад, не изучая ничего нового. Вы можете сравнительно легко согласовывать контент, добавив немного кода в базовый класс какого-то контроллера или в любой метод, которому это нужно (или создав согласуемый ActionResult). Это не сложнее включения дополнительного параметра в сигнатуру метода операции, его проверки и последующей сериализации ответа в XML или JSON по результатам проверки. Это решение практично, пока вы ограничиваетесь использованием XML или JSON. Но если вам надо принимать во внимание больше форматов, то, по-видимому, вы предпочтете задействовать Web API.
Как упоминалось, Web API можно размещать вне IIS, например в Windows-службе. Очевидно, если API находится в приложении ASP.NET MVC, вы привязаны к IIS. Следовательно, тип хостинга зависит от целей создаваемого вами API-уровня. Если он рассчитан на использование лишь сайтом ASP.NET MVC, тогда Web API вам скорее всего не нужен. Если же созданный вами API-уровень является настоящим «сервисом», предоставляющим API какого-то бизнес-контекста, то применять Web API в ASP.NET MVC имеет смысл.