Добавление динамических компонент в Интернет-магазин
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).
В результате в проект будет добавлено два файла: 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() { } }
Второй класс также будет использоваться для передачи информации клиенту. Он состоит из двух свойств:
- 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.