Опубликован: 04.05.2010 | Уровень: для всех | Доступ: платный
Практическая работа 4:

Добавление динамических компонент в Интернет-магазин

< Лекция 11 || Практическая работа 4: 1234 || Лекция 12 >

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 (DTOData 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. Страница с описанием сервиса
Тестовая страница для операции GetProduct сервиса WebProductService

увеличить изображение
Рис. 15.4. Тестовая страница для операции GetProduct сервиса WebProductService
< Лекция 11 || Практическая работа 4: 1234 || Лекция 12 >
Зарина Каримова
Зарина Каримова
Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008
Akiyev Begench
Akiyev Begench
Беларусь, Полоцк, полоцкий государственный университет