Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
29
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Здесь мы выделили важную деталь: Chrome сообщил, что он предпочитает получать данные application/xml всем остальным. Web API учитывает это предпочтение и отправляет данные XML.

Мы останавливаемся на этом потому, что общая проблема при работе с Web API - получение нежелательного формата данных. Так происходит потому, что в заголовке Accept указывается неправильное предпочтение формата, или оно вообще отсутствует.

Как работают API контроллеры

Чтобы больше узнать о том, как работает контроллер API, перейдите по ссылке /api/reservation/3. Вы увидите следующий JSON (или соответствующий XML, если вы используете другой браузер):

{"ReservationId":3,"ClientName":"Jacqui","Location":"Paris"}

На этот раз наш запрос вернул информацию об объекте Reservation, значение ReservationId которого соответствует последнему сегменту URL. Формат URL и использование сегментов URL должны напомнить вам о главе 13, в которой объяснялись принципы работы маршрутов в MVC Framework.

В контроллерах API есть своя конфигурация маршрутизации, которая полностью отделена от всего остального приложения. Конфигурацию по умолчанию, которую Visual Studio создает для новых проектов, можно увидеть в файле /App_Start/WebApiConfig.cs, который показан в листинге 25-9 .

Листинг 25-9: Содержимое файла WebApiConfig.cs по умолчанию

using System;

using System.Collections.Generic; using System.Linq;

using System.Web.Http;

namespace WebServices

{

public static class WebApiConfig

{

public static void Register(HttpConfiguration config)

{

config.Routes.MapHttpRoute( name: "DefaultApi",

routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional} );

}

}

}

Файл WebApiConfig.cs содержит маршруты, которые используются контроллерами API, но обычные маршруты MVC, которые определены в файле RouteConfig.cs, в нем не используются. Функция Web API реализована как автономная функция ASP.NET, ее можно использовать вне MVC Framework, следовательно, Microsoft дублировала ключевую функциональность MVC Framework из пространства имен System.Web.Http, чтобы отделить Web API от MVC. (Это кажется странным при разработке приложения в MVC Framework, но все же имеет смысл, поскольку Microsoft позволяет использовать Web API разработчикам, которые не работают с MVC).

641

При запуске приложения вызывается статический метод Register из метода Application_Start файла Config.asax; этот метод используется для регистрации маршрутов Web API. Метод Register получает объект HttpConfiguration, который предоставляет доступ к маршрутизации через свойство Routes. Маршруты создаются с помощью метода MapHttpRoute.

Принцип выбора действий в контроллере API

Стандартный маршрут Web API, который показан в листинге 25-9, содержит статический сегмент api и переменные сегментов controller и id, причем последний является дополнительным. Ключевым отличием от обычного маршрута MVC является то, что в нем нет переменной сегмента action; с этого момента поведение контроллеров API будет более понятным.

Когда в приложение поступает запрос, соответствующий маршруту Web API, действие определяется по методу HTTP, который использовался в запросе. Когда мы проверили работу контроллера API, перейдя по ссылке /api/reservation, браузер указал метод GET.

Класс ApiController, который является основой контроллеров API, узнает из маршрута, какой контроллер должен обрабатывать запрос, и использует метод HTTP для поиска подходящих методов действий. По соглашению методы действий контроллеров API должны содержать приставку с именем метода действия, который они поддерживают, и тип модели, с которым они работают. Но это только соглашение, поскольку Web API найдет любой метод действия, в имени которого указан метод HTTP, использовавшийся в запросе.

В нашем примере это означает, что для запроса GET выбор будет проводиться между

GetAllReservations и GetReservation, но если бы методы назывались DoGetReservation или просто ThisIsTheGetAction, они бы тоже учитывались.

Чтобы выбрать между двумя методами действий, контроллер смотрит на аргументы, которые принимают контроллеры, и переменные маршрутизации. При запросе API по ссылке /api/reservation, в которой нет переменных маршрутизации за исключением controller, был выбран метод GetAllReservations, потому что он не принимает аргументов. Когда мы запросили URL /api/reservation/3, мы поставили значение для опциональной переменной сегмента id; для этого запроса лучше подходит метод GetReservation, потому что он принимает аргумент id.

Для вызова других действий в нашем контроллере API Reservation будут использоваться другие методы HTTP: POST, DELETE и PUT. Это служит основой стиля REST (Representation State Transfer) Web API, также известного как REST-сервис, в котором операция определяется комбинацией URL и метода HTTP, использованного в запросе к данному URL.

Примечание

REST является стилем API, а не четко определенной спецификацией, поэтому существуют разногласия по поводу того, что именно делает веб-службу RESTсервисом. Одной из точек разногласий является то, что «пуристы» не считают вебслужбы, которые возвращают JSON, REST-сервисами. Как и все споры по поводу архитектурных шаблонов, этот возник из-за скучных и несерьезных причин. Мы рассматриваем применение шаблонов с практической стороны и убеждены, что сервисы JSON являются REST-сервисами.

642

Соотнесение методов HTTP с методами действий

Как мы уже объяснили, базовый класс ApiController использует метод HTTP, чтобы узнать, какой метод действий должен обрабатывать запрос. Это хороший подход, но он означает, что вам придется назначать методам действий неестественные имена, противоречащие соглашениям, которые вы могли бы использовать в приложении. Например, метод PutReservation более естественно было бы назвать UpdateReservation. Имя UpdateReservation не только сделало бы цель метода более очевидной, но и позволило бы напрямую соотносить действия контроллера и методы хранилища.

Подсказка

Вы могли бы попытаться наследовать класс хранилища от ApiController и

использовать методы хранилища напрямую как Web API. Мы настоятельно не рекомендуем так делать и советуем создавать отдельный контроллер, даже если он будет таким же простым, как в нашем примере. В какой-то момент методы, которые вы захотите использовать в API, и возможности хранилища начнут отличаться, и отдельный контроллер API упростит решение этой проблемы.

Пространство имен System.Web.Http содержит набор атрибутов, с помощью которых можно указать, для какого метода HTTP должно использоваться действие. В листинге 25-10 показано, как мы применили два из этих атрибутов, чтобы создать более естественные имена методов.

Листинг 25-10: Указываем методы HTTP, которые поддерживает действие, с помощью атрибутов

using System.Collections.Generic; using System.Web.Http;

using WebServices.Models;

namespace WebServices.Controllers

{

public class ReservationController : ApiController

{

private IReservationRepository repo = ReservationRepository.getRepository();

public IEnumerable<Reservation> GetAllReservations()

{

return repo.GetAll();

}

public Reservation GetReservation(int id)

{

return repo.Get(id);

}

[HttpPost]

public Reservation CreateReservation(Reservation item)

{

return repo.Add(item);

}

[HttpPut]

public bool UpdateReservation(Reservation item)

{

return repo.Update(item);

}

public void DeleteReservation(int id)

{

repo.Remove(id);

643

}

}

}

Вы можете заметить, что в Web API дублируются функции MVC Framework, которые мы рассмотрели в главе 17. Атрибуты HttpPost и HttpPut, которые мы использовали в листинге 25-10, имеют точно такую же цель, что и одноименные атрибуты в главе 17, но они определены в пространстве имен System.Web.Http, а не System.Web.Mvc. Эти атрибуты работают таким же образом, и в итоге мы получили более полезные имена методов, которые будут работать с HTTPметодами POST и PUT. (Конечно, существуют атрибуты для всех методов HTTP, в том числе GET,

DELETE и т.д.)

Написание JavaScript кода для использования

Web API

Мы создали наш контроллер API и объяснили, как URL вида /api/reservation/3 сопоставляется с методом действия в зависимости от метода HTTP. Теперь пора написать код JavaScript, с помощью которого мы будем использовать созданный Web API. Мы будем использовать jQuery, чтобы управлять HTML-элементами представления Views/Home/Index.cshtml и обрабатывать запросы Ajax, которые будем отправлять к действиям контроллера Reservation.

jQuery - это отличная многофункциональная библиотека JavaScript, которую мы часто используем в собственных проектах и рекомендуем к использованию вам. В этой книге мы не сможем поместить руководство по jQuery, поэтому будем создавать функциональность JavaScript и рассказывать вам, что делает каждый блок кода, не вдаваясь в подробности о том, как работают функции jQuery. Дополнительные сведения о jQuery можно найти на сайте jquery.com или в книге Адама Pro jQuery.

Создаем базовую функциональность

Для начала мы создадим папку /Scripts/Home и добавим в нее новый файл JavaScript под названием Index.js (как упоминалось в главе 24, мы организовываем скрипты для отдельных приложений, следуя соглашению). Прежде чем сделать что-нибудь еще, мы добавим элемент script к определению секции scripts в представлении /Views/Home/Index.cshtml, который будет загружать наш код JavaScript, как показано в листинге 25-11.

Листинг 25-11: Добавляем элемент script для файла Index.js в представление Index.cshtml

@{

ViewBag.Title = "Index";

}

@section scripts {

<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

<script src="~/Scripts/Home/Index.js"></script>

}

Далее мы добавим в файл Index.js необходимые базовые функции, как показано в листинге 25-12.

Листинг 25-12: Добавляем базовые функции в файл Index.js

function selectView(view) {

$('.display').not('#' + view + "Display").hide(); $('#' + view + "Display").show();

}

function getData() {

644

$.ajax({ type: "GET",

url: "/api/reservation", success: function (data) { $('#tableBody').empty();

for (var i = 0; i < data.length; i++) { $('#tableBody').append('<tr><td><input id="id" name="id" type="radio"'

+'value="' + data[i].ReservationId + '" /></td>'

+'<td>' + data[i].ClientName + '</td>'

+'<td>' + data[i].Location + '</td></tr>');

}

$('input:radio')[0].checked = "checked"; selectView("summary");

}

});

}

$(document).ready(function () { selectView("summary"); getData(); $("button").click(function (e) {

var selectedRadio = $('input:radio:checked') switch (e.target.id) {

case "refresh": getData(); break;

case "delete": break;

case "add": selectView("add"); break;

case "edit": selectView("edit"); break;

case "submitEdit": break;

}

});

});

Мы определили три функции. Первая, selectView, изменяет видимость элементов div в классе display, так что отображаться будет только один набор элементов. Вторая функция, GetData, использует поддержку Ajax в jQuery для отправки запросов по адресу /api/reservation. Для добавления строк в таблицу в представлении используется массив объектов JSON; он заменяет заполнитель The data is loading, который был виден на рисунке 25-1 . Каждая строка в таблице содержит переключатель, с помощью которого пользователь сможет отредактировать или удалить объект Reservation.

Последняя функция будет передана в функцию jQuery ready, что означает, что она не будет выполнена до окончания загрузки и обработки содержимого страницы браузером. Мы вызываем функцию selectView для отображения только содержимого элемента summaryDisplay, getData - для загрузки данных по ссылке /api/reservation (как мы продемонстрировали ранее, это приведет к вызову метода GetAllReservations в контроллере Reservation). Мы также настроили обработчик событий, который будет выполняться после нажатия любой кнопки на странице. Мы использовали оператор switch, чтобы различать элементы button в зависимости от значения атрибута id. В его операторе case мы создаем различные запросы, которые затем отправим на сервер.

В данный момент для кнопки Refresh мы вызываем функцию getData, которая перезагружает данные от сервера, для кнопок Edit и Add - функцию selectView, которая отображает элементы, необходимые для создания и редактирования объектов модели.

645

Если вы запустите приложение и перейдете по корневой ссылке, то увидите изменения, созданные нашим базовым кодом JavaScript, как показано на рисунке 25-3.

Рисунок 25-3: Результат выполнения JavaScript кода

Добавляем поддержку редактирования новых объектов Reservation

Мы хотим использовать все методы действий контроллера Reservation, поэтому наш подход к редактированию объектов Reservation будет немного странным. В документе HTML у нас уже есть все необходимые данные для редактирования объекта Reservation, но мы запросим у сервера один объект Reservation, чтобы использовать объект GetReservation. В листинге 25-13 показано, как мы добавили операторы в файл Index.js для ответа на нажатие кнопки Edit.

Листинг 25-13: Отвечаем на нажатие кнопки Edit

$(document).ready(function() { selectView("summary"); getData(); $("button").click(function(e) {

var selectedRadio = $('input:radio:checked') switch (e.target.id) {

case "refresh": getData(); break;

case "delete": break;

case "add": selectView("add"); break;

case "edit": $.ajax({

type: "GET",

url: "/api/reservation/" + selectedRadio.attr('value'), success: function(data) {

$('#editReservationId').val(data.ReservationId); $('#editClientName').val(data.ClientName); $('#editLocation').val(data.Location); selectView("edit");

}

});

break;

case "submitEdit": break;

}

});

});

646

При создании строк таблицы в функции getData мы использовали значение свойства ReservationId каждого объекта Reservation, чтобы установить значение для элемента radio button, например:

<input name="id" id="id" type="radio" value="3"/>

Когда пользователь нажимает кнопку Edit, мы находим выбранный переключатель и используем его атрибут value в URL, который запросим у сервера. Если пользователь выбрал радиокнопку, показанную ранее, то мы запросим URL /api/reservation/3. Мы сообщаем jQuery, что хотим отправить запрос GET, и сочетание URL и метода HTTP приводят нас к методу действия

GetReservation контроллера Reservation.

Мы используем полученные данные JSON, чтобы установить значения элементов input в секции editDisplay, а затем вызываем функцию selectView, чтобы отобразить их пользователю, как показано на рисунке 25-4.

Рисунок 25-4: Выбор объекта для редактирования

Чтобы разрешить пользователю сохранить изменения, нам нужно заполнить блок case, который работает с id кнопки submitEdit, как показано в листинге 25-14.

Листинг 25-14: Сохраняем изменения на сервере

$(document).ready(function () { selectView("summary"); getData(); $("button").click(function (e) {

var selectedRadio = $('input:radio:checked') switch (e.target.id) {

case "refresh": getData(); break;

case "delete": break;

case "add": selectView("add"); break;

case "edit": $.ajax({

647

type: "GET",

url: "/api/reservation/" + selectedRadio.attr('value'), success: function (data) {

$('#editReservationId').val(data.ReservationId); $('#editClientName').val(data.ClientName); $('#editLocation').val(data.Location); selectView("edit");

}

});

break;

case "submitEdit": $.ajax({

type: "PUT",

url: "/api/reservation/" + selectedRadio.attr('value'), data: $('#editForm').serialize(),

success: function (result) { if (result) {

var cells = selectedRadio.closest('tr').children(); cells[1].innerText = $('#editClientName').val(); cells[2].innerText = $('#editLocation').val(); selectView("summary");

}

}

});

break;

}

});

});

Мы используем те же URL, что и для получения объекта Reservation, /api/reservation/3, но с методом HTTP PUT, следовательно, для обработки запроса будет использоваться метод действия PutReservation контроллера Reservation. Напомним, мы определили его следующим образом:

public bool PutReservation(Reservation item) { return repo.Update(item);

}

Обратите внимание, что аргументом этого метода действия является объект Reservation. Контроллеры API используют те же механизмы связывания, которые мы описали в главе 22, что означает, что нам не придется преобразовывать данные запроса в объект модели.

Добавляем поддержку удаления объектов Reservation

Вы уже поняли принцип работы API, и как метод HTTP изменяет метод действия, который будет обрабатывать наш запрос, даже если мы отправляем его по той же ссылке. В листинге 25-15 показано, как мы добавили поддержку удаления объектов Reservation, для чего используется метод

HTTP DELETE.

Листинг 25-15: Добавляем поддержку удаления объектов Reservation

case "delete": $.ajax({

type: "DELETE",

url: "/api/reservation/" + selectedRadio.attr('value'), success: function (data) {

selectedRadio.closest('tr').remove();

}

});

break;

648

В элементе table мы удаляем строку, которая содержит данные объекта Reservation, который был удален. Мы делаем это независимо от результата, который получим от сервера, что не будет иметь смысла в реальном проекте.

Добавляем поддержку создания объектов Reservation

Для создания новых объектов Reservation мы применим несколько иной подход. Для создания запросов PUT или DELETE легче будет использовать поддержку Ajax в jQuery, но для запросов POST и GET можно без проблем использовать ненавязчивый Ajax. Чтобы добавить поддержку создания новых объектов данных, нам понадобится только настроить объект AjaxOptions, который мы используем во вспомогательном методе Ajax.BeginForm в представлении Index.cshtml, как показано в листинге 25-16.

Подсказка

Если вы хотите использовать формы Ajax для всех запросов, или вы хотите использовать REST-сервис в браузере, который поддерживает только методы GET и POST, то с помощью вспомогательного метода Html.HttpMethodOverride вы

можете добавить в форму скрытый элемент, который будет интерпретирован контроллером API и использован для обращения к методам действий. Переопределять можно только запросы POST, но эта резервная техника может

быть полезной, особенно для старых браузеров.

Листинг 25-16: Настраиваем объект AjaxOptions для создания новых объектов модели

<div id="addDisplay" class="display"> <h4>Add New Reservation</h4>

@{

AjaxOptions addAjaxOpts = new AjaxOptions

{

OnSuccess = "getData", Url = "/api/reservation"

};

}

@using (Ajax.BeginForm(addAjaxOpts))

{

@Html.Hidden("ReservationId", 0) <p><label>Name:</label>@Html.Editor("ClientName")</p> <p><label>Location:</label>@Html.Editor("Location")</p> <button type="submit">Submit</button>

}

</div>

Эта форма будет отправлена по умолчанию с помощью метода POST, и нам не нужно создавать URL динамически, потому что метод действия PostReservation не принимает переменные сегментов в качестве параметров (он принимает объект Reservation, который создается механизмом связывания). Когда пользователь отправляет форму на сервер, будет вызван метод действия PostReservation, который создаст в хранилище новый объект Reservation. Когда запрос завершен, мы вызываем метод getData, чтобы обновить данные клиента и отобразить итоговое представление. Для простоты кода JavaScript мы этого не делаем, хотя сервер и отправляет нам вновь созданный объект в формате JSON, с помощью которого мы могли бы добавить в таблицу новую строку. Результат создания нового объекта Reservation показан на рисунке 25-5.

649

Рисунок 25-5: Добавляем новый объект Reservation

И это весь код, необходимый для завершения нашего Web API и простого приложения, в котором он используется. Как мы заметили ранее в этой главе, функция Web API очень проста, и все время уйдет на создание и тестирование клиента, который его использует. Чтобы создать сам API, необходимо наследовать новый контроллер от ApiController и создать методы действий с теми же именами, что и методы HTTP, с помощью которых вы хотите к ним обращаться.

Резюме

В этой главе мы показали вам, как с помощью функции Web API создать REST-сервис, который смогут использовать HTTP-клиенты. Хотя на самом деле Web API не является частью MVC Framework, он очень близок по принципам и структуре к MVC и, таким образом, знаком разработчикам MVC. Как мы показали, контроллеры Web API можно добавить в приложение наряду с обычными контроллерами MVC. В главе 26 (в заключение этой книги) мы покажем вам, как развертывать приложения MVC .

650

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]