
- •15. Практическое занятие: Добавление динамических компонент в Интернет-магазин
- •15.1. Введение
- •15.2. Применение ajax
- •15.2.1. Подключение клиентских сценариев
- •15.2.2. Работа с UpdatePanel
- •15.3. Работа с веб-службами в asp.Net ajax
- •15.3.1. Создание веб-службы
- •15.3.2. Вызов веб-службы из клиенсткого сценария
- •15.4. Отладка JavaScript
- •15.5. Профилирование JavaScript
- •15.6. Ключевые термины
- •15.7. Краткие итоги
15.3. Работа с веб-службами в asp.Net ajax
Теперь, когда мы научились работать с языком JavaScript и сумели оптимизировать работу сайта при помощи ASP.NET AJAX стоит оценить преимущества и недостатки текущей реализации Интернет-магазина. Нам удалось сократить время работы серверного кода (хотя и незначительно) за счет того, что мы используем UpdatePanel, и теперь метод Page.Render отрисовывает не всю странице целиком, а только определенные части. Более того, уменьшился и объем данных, передаваемый от сервера клиенту, опять же за счет того, что передается не вся страница. Второе преимущество, которого нам удалось добиться, заключается в том, что пользователь не наблюдает процесс перезагрузки всей страницы при каждой обратной передаче, что делает пользовательский интерфейс страницы более естественным.
Среди недостатков стоит отметить тот факт, что каждое действие пользователя приводит к отправке на сервер и получение от него большого объема данных в виде ViewState. Если же отказаться от использования ViewState, то для восстановления страницы на сервере необходимо либо хранить все данные в сессии, либо каждый раз извлекать их из базы данных. Еще один недостаток заключается в том, что при использовании достаточно сложного интерфейса, насыщенного большим количеством сложных компонент, время их рендеринга на сервере сильно возрастает.
Один из подходов к решению этих проблем заключается в использовании веб-сервисов. Вместо того чтобы каждый раз ради отправки или получения данных посылать или получать всю страницу целиком, можно сделать так, чтобы клиентский код JavaScript обращался на сервер, получал необходимые данные и сам же их отображал. Этот подход серьезно сократит трафик между сервером и клиентом (в случае с таблицей продуктов, о которой в дальнейшем пойдет речь, вместо 17-18 килобайт данных будет передаваться 1 килобайт) и снизит нагрузку на сервер, так как теперь его основные обязанностями будут заключаться в том, чтобы манипулировать данными.
При этом, однако, многократно возрастет нагрузка на клиент. Это может привести к тому, что данный подход станет неприменим в случаях, когда у клиентов слабые компьютеры или на них установлены старые браузеры, плохо поддерживающие JavaScript.
Тем не менее, для сайта, с которыми работает одновременно множество людей, этот подход способен серьезно улучшить производительность приложения.
15.3.1. Создание веб-службы
Для того чтобы добавить веб-сервис в проект, необходимо кликнуть правой кнопкой мыши по проекту в окне Solution Explorer и выбрать раздел меню Add New Item. В открывшемся диалоговом окне, необходимо выбрать элемент Web Service (рис. 15.2).
Рис. 15.2. Окно добавления нового веб-сервиса в проект
В результате в проект будет добавлено два файла: WebProductService.asmx и WebProductService.cs, причем последний фал будет помещен в директорию App_code.
Чтобы разрешить вызов веб-служб (ASMX) из клиентского сценария на веб-странице ASP.NET, необходимо добавить на страницу элемент управления ScriptManager. Чтобы определить ссылку на веб-службу, необходимо добавить дочерний элемент asp:ServiceReference к элементу управления ScriptManager. После этого необходимо установить URL-адрес веб-службы в качестве значения атрибута ссылки на сервер path. Объект ServiceReference определяет необходимость создания прокси-класса JavaScript для вызова указанной веб-службы в ASP.NET.
Так как в нашем случае ScriptManager определен на мастере страниц, изменим его код следующим образом:
<asp:ScriptManager ID="Scriptmanager1" runat="server">
<Services>
<asp:ServiceReference Path="~/WebProductService.asmx" />
</Services>
</asp:ScriptManager>
Теперь необходимо реализовать код этого сервиса. Для примера сделаем так, чтобы сервис мог возвращать список продуктов с учетом указанной категории и подкатегории и при этом поддерживал постраничный вывод.
Прежде чем приступить к реализации самого сервиса, напишем два вспомогательных класса. Первый класс ProductDTO (DTO – Data Transfer Object) будет представлять собой описание структуры данных продукта, которую сервис будет отправлять клиенту.
[Serializable]
public class ProductDTO
{
public int ProductID { get; set; }
public string ProductNumber { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public decimal ListPrice { get; set; }
public string FullSize { get; set; }
public string Weight { get; set; }
public ProductDTO(Product p)
{
ProductID = p.ProductID;
ProductNumber = p.ProductNumber;
Name = p.Name;
Color = p.Color;
ListPrice = p.ListPrice;
FullSize = p.FullSize;
Weight = p.Weight + p.WeightUnitMeasureCode;
}
public ProductDTO()
{
}
}
Примечание 1: Атрибут [Serializable] в данном случае необязателен. Он показывает, что объекты этого классы должны поддерживать возможность представляться в виде строки, которую можно передать от одного сервиса к другому так, чтобы получающий сервис смог восстановить передаваемый объект. Сериализовать объект можно в xml-файл, json-объект или в бинарный код.
Примечание 2: так как данный класс сериализуем, необходимо чтобы у него был определен публичный конструктор без параметров.
Примечание 3: нам необходимо разработать вспомогательные классы, так как класс Product не сериализуем. Это связано с тем, что сериализации подвергаются все public свойства, среди которых есть ссылки на объекты, которые также рекурсивно сериализуются. Так, например, Product имеет ссылку ProductSubcategory на свою подкатегорию, которая в свою очередь имеет ссылку Products. В результате при сериализации произойдет зацикливание.
Второй класс также будет использоваться для передачи информации клиенту. Он состоит из двух свойств:
Result предназначается для передачи коллекции ProductDTO, которая удовлетворяет текущим критериям поиска;
TotalCount содержит общее количество продуктов, которые соответствуют критериям поиска.
Этот класс позволит организовать постраничный вывод продуктов. Ниже представлен код этого класса:
public class ResultStructure
{
public object Result{get;set;}
public int TotalCount { get; set; }
}
Теперь, когда все вспомогательные структуры готовы, необходимо определить сервис, который будет обрабатывать запросы:
/// <summary>
/// Summary description for WebProductService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class WebProductService : System.Web.Services.WebService {
Этот код будет автоматически добавлен Visual Studio, при создании сервиса. Нам необходимо разкомментировать атрибут ScriptService, так как он позволяет вызвать сервис из JavaScript.
Дальше в самом классе реализуются доступные для вызова веб-методы. Все такие методы помечаются атрибутом WebMethod. В нашем примере мы разработаем только один метод GetProducts, который на вход будет получать текущую категорию, подкатегорию и номер страницы:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public ResultStructure GetProducts(string category, string subcategory, int skip)
{
DataClassesDataContext dcdc =
new DataClassesDataContext(
"Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True");
var query = from p in dcdc.Products select p;
if (!string.IsNullOrEmpty(subcategory))
{
query = query.Where(p => p.ProductSubcategoryID == Convert.ToInt32(subcategory));
}
else
{
if (!string.IsNullOrEmpty(category))
{
query = query.Where(p => p.ProductSubcategory.ProductCategoryID == Convert.ToInt32(category));
}
}
return new ResultStructure()
{
Result = query.Skip(skip).Take(10).Select(p => new ProductDTO(p)).ToList(),
TotalCount = query.Count()
};
}
Код веб-метода идентичен коду, который разрабатывался на предыдущих занятиях для заполнения GridView данными о продуктах, только теперь метод не привязывает данные к какому-либо серверному компоненту, а возвращает сериализованные данные состоящие из коллекции продуктов и их общего количества. В атрибуте ScriptMethod указан параметр ResponseFormat = ResponseFormat.Json, который указывает, что результат необходимо сериализовать в формате JSON. Мы выбрали этот формат, так как он является основным формат представления данных для языка JavaScript.
Если вызвать наш сервис и передать ему параметры, то будет получен ответ, аналогичный приведенному:
{"d":{"__type":"ResultStructure",
"Result":
[{"ProductID":982,"ProductNumber":"BK-M38S-42",
"Name":"Mountain-400-W Silver, 42",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"42 CM ","Weight":"27,13LB "},
{"ProductID":983,"ProductNumber":"BK-M38S-46",
"Name":"Mountain-400-W Silver, 46",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"46 CM ","Weight":"27,42LB "},
{"ProductID":984,"ProductNumber":"BK-M18S-40",
"Name":"Mountain-500 Silver, 40",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"40 CM ","Weight":"27,35LB"},
{"ProductID":985,"ProductNumber":"BK-M18S-42",
"Name":"Mountain-500 Silver, 42",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":986,"ProductNumber":"BK-M18S-44",
"Name":"Mountain-500 Silver, 44",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"44 CM ","Weight":"28,13LB "},
{"ProductID":987,"ProductNumber":"BK-M18S-48",
"Name":"Mountain-500 Silver, 48",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"48 CM ","Weight":"28,42LB "},
{"ProductID":988,"ProductNumber":"BK-M18S-52",
"Name":"Mountain-500 Silver,52",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"52 CM ","Weight":"28,68LB "},
{"ProductID":989,"ProductNumber":"BK-M18B-40",
"Name":"Mountain-500 Black, 40",
"Color":"Black","ListPrice":539.9900,
"FullSize":"40 CM ","Weight":"27,35LB "},
{"ProductID":990,"ProductNumber":"BK-M18B-42",
"Name":"Mountain-500 Black, 42",
"Color":"Black","ListPrice":539.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":991,"ProductNumber":"BK-M18B-44",
"Name":"Mountain-500 Black, 44",
"Color":"Black","ListPrice":539.9900,
"FullSize":"44 CM ","Weight":"28,13LB "}],
"TotalCount":32}}
При этом для сервиса будут доступны страницы, на которых будут представлены описание методов, которые предоставляет сервис, а также тестовые страницы, на которых можно вызвать метод, передав параметры. Впрочем, в случае использования JSON-формата данных, вызвать методы не получится, так как это приведет к ошибке. Примеры таких страниц представлены на рис. 15.3 и рис. 15.4.
Рис. 15.3. Страница с описанием сервиса
Рис. 15.4. Тестовая страница для операции GetProduct сервиса WebProductService