Современные веб-технологии

Применение Веб-методов. Технология Jasp

Разбить на страницы
Показывать лекцию целиком

Презентацию к данной лекции Вы можете скачать здесь.

14.1. Веб-сервисы

14.1.1. Введение

На предыдущих лекциях была рассмотрена технология ASP.NET AJAX, которая позволяла существенно улучшить производительность Веб-приложений и в то же время улучшала работу пользователя с сайтом:

  • Сокращение времени работы серверного кода – использование UpdatePanel, за счет чего метод Page.Render отрисовывает не всю странице целиком, а только определенные части. Более того, при этом уменьшается и объем данных, передаваемый от сервера клиенту (опять же за счет того, что передается не вся страница ).
  • Пользователь не наблюдает процесс перезагрузки всей страницы при каждой обратной передаче, что делает пользовательский интерфейс страницы более естественным.
  • Среди недостатков подхода ASP.NET AJAX стоит отметить тот факт, что каждое действие пользователя приводит к отправке на сервер и получение от него большого объема данных в виде ViewState. Если же отказаться от использования ViewState, то для восстановления страницы на сервере необходимо либо хранить все данные в сессии, либо каждый раз извлекать их из базы данных. Еще один недостаток заключается в том, что при использовании достаточно сложного интерфейса, насыщенного большим количеством сложных компонент, время их рендеринга на сервере сильно возрастает.

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

    Веб-сервисы XML – это единица программного кода, обслуживаемая Веб-сервером и доступная в рамках стандартных промышленных технологий, таких как HTTP и XML [1]. Веб-сервисы предлагают такой уровень совместимости и взаимодействия в отношении операционных систем, платформ и языков, который ранее был просто недоступен.

    Веб-службы ASP.NET являются компонентами, методы которых можно вызывать с помощью запросов HTTP. Однако в отличие от традиционного Веб-приложения, Веб-сервисы XML можно использовать не только для того, чтобы возвращать браузеру HTML-код с целью визуализации. Скорее наоборот – Веб-сервисы XML чаще всего предоставляют функциональные возможности, аналогичные возможностям стандартной библиотеки программного кода .NET, такие как специальные вычисления, выборку данных из БД, чтение цен на акции и другие.

    Веб-службы предоставляют возможность обмениваться сообщениями в свободно связанной среде, используя такие стандартные протоколы, как HTTP, XML, XSD SOAP и WSDL. Сообщения могут быть структурированы и типизированы или иметь свободный формат. Поскольку основой Веб-служб служат стандартные протоколы, их приложения могут обмениваться данными с самыми разными реализациями, платформами и устройствами [2].

    Вместо того чтобы каждый раз ради отправки или получения данных посылать или получать всю страницу целиком, можно сделать так, чтобы клиентский код JavaScript обращался к Веб-службе, получал необходимые данные и сам же их отображал. Этот подход серьезно сокращает трафик между сервером и клиентом и снижает нагрузку на сервер, так как теперь его основными обязанностями является манипулирование данными.

    При этом, однако, многократно возрастает нагрузка на клиент. Это может привести к тому, что данный подход станет неприменим в случаях, когда у клиентов слабые компьютеры или на них установлены старые браузеры, плохо поддерживающие JavaScript.

    14.1.2. Веб-службы в ASP.NET

    Веб-службы можно создавать, используя архитектуру ASP.NET, что позволяет им использовать разнообразную функциональность платформы .NET Framework: например, проверку подлинности, кэширование и управление состоянием [3]. Поскольку основой для Веб-сервисов в управляемом коде служат ASP.NET и платформа .NET Framework, разработчики могут сосредоточиться на создании Веб-служб или на их использовании, не тратя времени на разработку инфраструктуры.

    При работе с Веб-сервисами существуют две фундаментальные роли:

  • Создание Веб-сервиса – при создании XML Веб-службы создается приложение, которое предоставляет некую функциональность клиентам этой XML Веб-службы.
  • Обращение к Веб-сервису – при обращении к Веб-сервису клиентское приложение находит Веб-службу, создает ссылку на нее и использует ее функциональные возможности. Обычно клиентом XML Веб-службы является приложение, способное отправлять, принимать и обрабатывать сообщения, отправляемые Веб-службе и получаемые от нее. Тем не менее, минимальное требование заключается в том, что клиент должен быть способен отправлять сообщения Веб-службам. Это требование относится ко всем приложениям, работающим под управлением платформы .NET Framework.
  • 14.1.3. Доступ к Веб-сервисам в управляемом коде

    ASP.NET позволяет создавать Веб-сервисы, к которым можно обращаться из клиентского сценария страниц. Страницы взаимодействуют с сервером посредством уровня связи, использующего технологию AJAX для вызова Веб-служб. Обмен данными между клиентом и сервером осуществляется асинхронно, как правило, в формате JSON.

    14.1.3.1. Взаимодействие клиент-сервер для клиентов AJAX

    На Веб- страницах с поддержкой технологии AJAX браузер отправляет первый запрос этой страницы на сервер, а затем выполняет последовательные асинхронные запросы данных к Веб-сервисам. Клиентские элементы взаимодействия представлены в форме загруженных с сервера прокси-классов и основной библиотеки клиентских сценариев. К серверным элементам взаимодействия относятся обработчики и пользовательские службы. На рис. 14.1 показаны элементы, которые участвуют во взаимодействии между клиентом и сервером.

    (рис 14.1) Взаимодействие клиент-сервер

    14.1.3.2. Клиентская архитектура технологии AJAX

    Браузер вызывают методы Веб-сервисов с помощью прокси-классов [4, 5]. Прокси-класс – это сценарий, который автоматически создается сервером и загружается в браузер во время загрузки страницы. Прокси-класс предоставляет клиентский объект, который позволяет вызвать реализованный в Веб-сервисе методы.

    Чтобы вызвать метод Веб-сервиса, клиентский сценарий вызывает соответствующие методы прокси-класса. Эти вызовы выполняются асинхронно посредством объекта XMLHTTP.

    Код в прокси-классах скрывает сложность XMLHTTP [6] и различия между браузерами. Он упрощает клиентский сценарий, необходимый для вызова Веб-сервиса.

    Существует два подхода к вызовам методов Веб-сервиса.

  • Вызов Веб-сервиса с помощью команды POST HTTP. POST -запрос состоит из основной части, содержащей данные, которые обозреватель отправляет на сервер. Этот запрос не имеет ограничений по размеру. Поэтому можно использовать POST -запрос, если размер данных превышает внутреннее ограничение по размеру для запроса GET. Клиент сериализует запрос в формат JSON и отправляет его на сервер в качестве данных POST. Сервер выполняет десериализацию данных из формата JSON в типы .NET Framework и выполняет фактический вызов службы. Во время ответа сервер сериализует возвращаемые значения и передает их обратно клиенту, который выполняет их десериализацию в объекты JavaScript для обработки.
  • Вызов Веб-служб с помощью команды GET HTTP. Этот вызов аналогичен POST -запросу, однако существует несколько отличий:
  • клиент использует строку запроса для отправки параметров на сервер;
  • GET -запрос может вызвать только метод Веб-сервиса, помеченный атрибутом ScriptMethodAttribute ;
  • размер данных ограничивается длиной URL-адреса, разрешенной браузером.
  • На рис. 14.2 показана клиентская архитектура технологии AJAX в ASP.NET.

    (рис 14.2) Клиентская архитектура AJAX

    К элементам клиентской архитектуры относятся уровень взаимодействия Веб-сервиса и загруженные прокси-классы для используемых на странице служб. Этот уровень представляет собой библиотеку типов клиентского сценария, которые позволяют клиенту взаимодействовать со службами на сервере. Они также освобождают клиентские приложения от необходимости установки и поддержания асинхронной связи между клиентом и сервером. Они инкапсулируют объект XMLHTTP браузера, который предоставляет возможность асинхронного взаимодействия, и предоставляют клиентским приложениям независимость от браузера. Ниже приведены основные элементы уровня связи Веб-служб:

  • WebRequest.Предоставляет функциональные возможности клиентского сценария для создания Веб-запроса.
  • WebRequestManager. Управляет потоком Веб-запросов, направленных объектом Sys.Net.WebRequest соответствующему объекту-исполнителю.
  • XmlHttpExecutor.Выполняет асинхронные сетевые запросы, используя поддержку XMLHTTP Веб-обозревателем.
  • JSON - сериализация. Выполняет сериализацию объектов JavaScript в формат JSON. Десериализацию можно выполнить с помощью функции eval JavaScript.
  • По умолчанию сериализация выполняется в формат JSON, однако отдельные методы Веб-сервисов и Веб- страниц ASP.NET могут возвращать значения в других форматах, например XML. Формат сериализации, используемый методом, можно указать с помощью атрибутов. Например, для службы ASMX можно задать атрибут ScriptMethodAttribute, предписывающий методу Веб-сервиса возвращать данные в формате XML, как показано в следующем примере:

    [ScriptMethod(ResponseFormat.Xml)]

    14.1.3.3. Серверная архитектура AJAX

    На рис. 14.3 показана серверная архитектура AJAX, включающая элементы, обеспечивающие взаимодействие с клиентскими приложениями.

    (рис 14.3) Серверная архитектура AJAX

    Элементы серверной архитектуры включают уровень связи Веб-служб, обработчик HTTP-данных и классы сериализации, пользовательские службы, методы страниц и службы приложений. На рисунке показаны следующие отдельные элементы:

  • Пользовательские Веб-службы. Предоставляют функциональную возможность службы, реализуемую пользователями, и возвращают соответствующий ответ клиентскому приложению. Пользовательскими Веб-службами могут быть службы ASP.NET или WCF. Уровень связи Веб-служб автоматически создает прокси-классы клиентского сценария, которые могут быть вызваны асинхронно из клиентского сценария.
  • Методы страниц. Этот компонент позволяет вызывать метод страницы ASP.NET так, как если бы он был методом Веб-службы. Методы страниц должны быть определены на странице, с которой осуществляется вызов метода страницы.
  • Служба проверки подлинности. Служба проверки подлинности создает прокси-класс проверки подлинности, который позволяет пользователю осуществлять вход и выход из системы с помощью клиентского сценария JavaScript.
  • Служба ролей. Служба ролей создает прокси-класс ролей, который позволяет осуществлять доступ из клиентского сценария JavaScript к сведениям о ролях для текущего пользователя, прошедшего проверку подлинности.
  • Служба профилей. Служба профилей создает прокси-класс профилей, который позволяет клиентскому сценарию JavaScript получать и задавать свойства для пользователя, связанного с текущим запросом.
  • JSON - сериализация. Серверный компонент JSON - сериализации позволяет выполнять настраиваемую сериализацию и десериализацию общих типов .NET Framework в формат JSON и из него.
  • XML- сериализация. Уровень связи Веб-служб поддерживает XML- сериализацию для SOAP-запросов к Веб-службам и возвращения типов XML по JSON -запросам к Веб-службам.
  • 14.1.4. Создание Веб-сервиса

    Веб-сервисы создаются в управляемом коде с помощью платформы ASP.NET. Сначала создается файл с расширением ASMX [7]. Этот файл содержит класс, помеченный атрибутом WebService, который служит для обозначения открытого класса, реализующего логику Веб-сервиса. Этот класс Веб-сервиса содержит один или несколько открытых методов, подлежащих публикации в составе Веб-сервиса. Перед этими методами ставится атрибут WebMethod. Среда ASP.NET отвечает за реализацию инфраструктуры публикации этих методов в составе Веб-сервиса.

    Веб-сервисы состоят из двух частей: точка входа Веб-сервиса и код, который реализует его функциональность. В ASP.NET точкой входа для Веб-сервиса служит текстовый ASMX-файл. Он ссылается на код в предкомпилированных сборках, файл с выделенным кодом, или на код, содержащийся в самом ASMX-файле.

    При построении Веб-сервиса в управляемом коде среда ASP.NET автоматически обеспечивает инфраструктуру и обрабатывает запросы и ответы Веб-сервиса, в том числе занимаясь разбором и формированием сообщений SOAP.

    14.1.4.1. Директива обработки WebService

    В начале ASMX- страницы находится директива обработки WebService, атрибуты которой содержат сведения о реализации Веб-сервиса. Эта директива предоставляет среде ASP.NET необходимые сведения, например, о том, какой класс реализует функциональность. Ниже приведен пример директивы обработки WebService:

    <%@ WebService Language="c#" Codebehind="Service1.asmx.cs"
        Class="WebService1.Service1" %>

    Атрибут Language указывает, какой язык программирования использовался для разработки сервиса. Веб-сервис можно разрабатывать на любом языке, совместимом с .NET – например, на Visual Basic .NET или на Visual C#.

    При создании сервиса в управляемом коде с использованием Visual Studio .NET ее реализация находится в файле с выделенным кодом. Файл с выделенным кодом связывается с ASMX- страницей посредством атрибута Codebehind.

    При использовании шаблона сервиса ASP.NET атрибут Class указывает, какой класс в файле с выделенным кодом реализует функциональные возможности сервиса.

    Клиенты Веб-сервиса – это компоненты, службы и приложения, ссылающиеся на сервис и использующие его. Стоит отметить, что это не обязательно клиентские приложения – во многих случаях клиентами служб могут быть другие Веб-приложения или даже другие службы.

    14.1.5. Выполнение HTTP-запросов из клиентского сценария AJAX

    Прежде чем обсуждать, каким образом можно использовать прокси-классы для вызова Веб-методов, рассмотрим, как можно решить ту же задачу при помощи класса WebRequest. Этим может потребоваться, если необходимо управлять уровнем связи или выполнять проверку данных, отправляемых на или с сервера, или если заранее не известны параметры самого сервиса.

    В следующем примере показаны способы использования объекта WebRequest для реализации Веб-запросов GET и POST при подключении к указанным URL-адресам (конечные точки HTTP):

    var resultElement;
    function pageLoad()
    {
        resultElement = $get("ResultId");
    }
    // Эта функция вызывает GET запрос.
    function GetWebRequest()
    {
        // Создаем объект WebRequest.
        var wRequest = new Sys.Net.WebRequest();
        // Указываем адрес.      
        wRequest.set_url("getTarget.htm");
        alert("Target Url: getTarget.htm");
        // Указываем команду.
        wRequest.set_httpVerb("GET");
        // Указываем-callback функцию.
        wRequest.add_completed(OnWebRequestCompleted);
        // Очищаем содержимое элемента, который будет использоваться для вывода результата.
        resultElement.innerHTML = "";
        // Вызываем запрос.
        wRequest.invoke();  
    }
    // Эта функция вызывает POST запрос.
    function PostWebRequest()
    {
        // Создаем объект WebRequest.
        var wRequest = new Sys.Net.WebRequest();
        // Указываем адрес.      
        wRequest.set_url("postTarget.aspx");
        alert("Target Url: postTarget.aspx");
        // Указываем команду.
        wRequest.set_httpVerb("POST");
        // Указываем обработчик запроса.
        wRequest.add_completed(OnWebRequestCompleted);
        // Указываем тело POST.
        var requestBody = "Message=Привет, Мир!";
        wRequest.set_body(requestBody);
        wRequest.get_headers()["Content-Length"] = requestBody.length;
        // Очищаем содержимое элемента, который будет использоваться для вывода результата.   
        resultElement.innerHTML = "";
        // Вызываем запрос.
        wRequest.invoke();              
    }
    // Эта функция вызывается асинхронно, когда завершится запрос.
    function OnWebRequestCompleted(executor, eventArgs) 
    {    
        if(executor.get_responseAvailable()) 
        {
            // Отобразим статус запроса. 
           resultElement.innerHTML +=
              "Status: [" + executor.get_statusCode() + " " + 
                        executor.get_statusText() + "]" + "<br/>";
            // Отобразим заголовки запроса.
           resultElement.innerHTML += "Headers: ";
           resultElement.innerHTML += executor.getAllResponseHeaders() + "<br/>";
            // Отобразим тело запроса.
           resultElement.innerHTML += "Body:";
          if(document.all)
            resultElement.innerText += executor.get_responseData();
          else
            resultElement.textContent += executor.get_responseData();
        }
    }
    if (typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В табл. 11.1 перечислены основные классы, связанные с Веб-сервисами, которые можно вызвать из клиентского сценария.

    Пространства имен для клиента
    Имя Описание
    Пространство имен Sys.Net Содержит классы, управляющие взаимодействием между клиентскими приложениями AJAX в ASP.NET и Веб-службами на сервере. Пространство имен Sys.Net является частью Microsoft AJAX (библиотека).
    Пространство имен Sys.Serialization Содержит классы, связанные с сериализацией данных для клиентских приложений AJAX в ASP.NET.
    Пространство имен Sys.Services Содержит типы, которые обеспечивают доступ сценариев клиентских приложений AJAX в ASP.NET к службе проверки подлинности, службе профилей и другим службам приложения ASP.NET. Пространство имен Sys.Services является частью Microsoft AJAX (библиотека).

    В табл. 11.2 перечислены основные классы, связанные с Веб-сервисами, которые можно вызвать на сервере.

    Пространства имен для сервера
    Имя Описание
    System.Web.Script.Serialization Содержит классы, обеспечивающие сериализацию в формат JSON и десериализацию из этого формата для управляемых типов. Также обеспечивает расширенные возможности для настройки сериализации.

    14.1.6. Предоставление доступа к Веб-службам в клиентском сценарии

    Более удобный способ вызова Веб-сервисов заключается в использовании прокси-классов [8]. Как уже отмечалось, ASP.NET автоматически создаются прокси-классы JavaScript для Веб-служб. Эти прокси-классы являются производными от класса Sys.Net.WebServiceProxy. Чтобы вызвать метод Веб-сервиса, следует вызвать соответствующий метод прокси-класса JavaScript.

    Из сценария можно предоставить доступ к Веб-службе, которая представлена файлом с расширением ASMX и класс которой определен с использованием атрибута ScriptServiceAttribute. Отдельные методы, вызываемые из сценария, должны быть дополнены атрибутом WebMethodAttribute.

    В следующем примере показан порядок использования этих атрибутов в коде Веб-службы:

    [ScriptService]
    public class SimpleWebService : System.Web.Services.WebService
    {
    [WebMethod]
    public string EchoInput(String input)
    {
        // Здесь расположен код метода.
    }
    }

    Чтобы разрешить вызов Веб-служб из сценария, следует зарегистрировать обработчик HTTP ScriptHandlerFactory в файле Web.config приложения. Этот обработчик используется для обработки вызовов Веб-служб из сценария. В следующем примере показан элемент файла Web.config, определяющий добавление обработчика:

    <system.web>
      <httpHandlers>
        <remove verb="*" path="*.asmx"/>
        <add verb="*" path="*.asmx"
          type="System.Web.Script.Services.ScriptHandlerFactory"
           validate="false"/>
      </httpHandlers>
    <system.web>

    Вызовы Веб-служб, выполненные не из сценария ASP.NET AJAX, обработчик ScriptHandlerFactory делегирует используемому по умолчанию обработчику, в котором вместо формата JSON используется SOAP. Делегирование выполняется автоматически. Какие-либо дополнительные действия следует выполнять только в том случае, если требуется запретить использование протокола SOAP в Веб-службах. В этом случае в файле Web.config необходимо определить следующие параметры конфигурации:

    <system.web>
      <webServices>
        <protocols>
          <clear/>
        </protocols>
      </webServices>
    </system.web>

    14.1.6.1. Предоставление доступа к сервисам из клиентского сценария на Веб-странице ASP.NET

    Чтобы разрешить вызов Веб-служб (ASMX) из клиентского сценария на Веб- странице ASP.NET, необходимо добавить на страницу элемент управления ScriptManager. Чтобы определить ссылку на Веб-службу, необходимо добавить дочерний элемент asp:ServiceReference к элементу управления ScriptManager. После этого необходимо установить URL-адрес Веб-службы в качестве значения атрибута ссылки на сервер path. Объект ServiceReference определяет необходимость создания прокси-класса JavaScript для вызова указанной Веб-службы в ASP.NET.

    Добавим в начало страницы первым элементом формы объект ScriptManager:

    <asp:ScriptManager ID="Scriptmanager1" runat="server">
    <Services>
        <asp:ServiceReference Path="~/SimpleWebService.asmx" />
    </Services>
    </asp:ScriptManager>

    Объект ServiceReference может ссылаться на Веб-службу только в том же домене, в котором находится страница. Путь Веб-службы может быть относительным, относительным к приложению, относительным к домену или абсолютным. При использовании абсолютного пути следует убедиться, что он указан в том же домене.

    При отображении страницы, содержащей этот элемент управления ScriptManager, для Веб-службы SimpleWebService.asmx создается прокси-класс JavaScript. В прокси-классе реализуются методы, соответствующие методам Веб-службы SimpleWebService.asmx. Страница также содержит прокси-классы JavaScript, соответствующие типам данных сервера, которые используются в качестве входных параметров или возвращаемых значений методов Веб-служб. Это позволяет написать клиентский сценарий, инициализирующий эти параметры, и передать их вызову метода.

    Свойство InlineScript объекта ServiceReference определяет порядок включения прокси-класса JavaScript на странице. Если для свойства InlineScript установлено значение false (используется по умолчанию), получение сценария прокси-класса выполняется с помощью отдельного запроса. Этот параметр рекомендуется использовать в том случае, если несколько страниц ссылаются на одну службу и включено кэширование.

    Если для свойства InlineScript установлено значение true, сценарий прокси-класса включается на страницу в качестве встроенного блока сценария. Это позволяет повысить производительность за счет сокращения числа сетевых запросов. Это особенно актуально при наличии на странице большого числа ссылок на службы и отсутствии ссылок на эту службу на других страницах. Если для свойства InlineScript установлено значение true, необходимо использовать относительный путь. Путь, задаваемый относительно домена, должен содержать ссылку на то же Веб-приложение.

    Вызов метода Веб-сервисы из сценария осуществляется асинхронным способом. Чтобы получить возвращаемое значение или определить факт возврата запроса, следует определить функцию обратного вызова при успешном выполнении запроса. Функция обратного вызова вызывается при успешном выполнении запроса и содержит значение (если есть), возвращаемое в результате вызова метода Веб-сервиса. Также можно определить функцию обратного вызова при неудачном завершении запроса, используемую для обработки ошибок. Кроме того, можно передать в функции обратного вызова сведения о контексте пользователя. Если метод Веб-сервиса возвращает сложный тип, функция обратного вызова при успешном выполнении запроса принимает возвращаемое значение в форме объекта JavaScript, соответствующего типу сервера.

    14.1.6.2. Примеры

    Рассмотрим большинство указанных выше аспектов использования Веб-сервисов на примерах.

    В первом примере показан метод Веб-сервиса, возвращающий сложный тип. В рамках Веб-сервиса определен пользовательский сериализуемый тип ColorObject, а также метод GetDefaultColor, который возвращает некоторый объект с цветом. Клиент после полной отрисовки страницы вызывает сервис, получает значение цвета и сообщение, а также выводит сообщение от сервиса на экран, выделяя его переданным в формате RGB цветом.

    В первой части примера показана страница, на которой выполняется вызов сервиса с использованием клиентского сценария. Она имеет ссылку на файл . asmx, который содержит описание сервиса, и на файл HandleColor.js, который содержит JavaScript -код, вызывающий веб-сервисы:

    <%@ Page Language="C#" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html  >
        <head id="Head1" runat="server">
            <title>Получение составного типа данных</title>
            <style type="text/css">
                body {  font: 11pt Trebuchet MS;
                        font-color: #000000;
                        padding-top: 72px;
                        text-align: center }
    
                .text { font: 8pt Trebuchet MS }
            </style>
        </head>
        <body>
            <h2>Получение составного типа данных</h2>
            <form id="form1" runat="server">
                <asp:ScriptManager runat="server" ID="scriptManager">
                    <Services>
                        <asp:ServiceReference  Path="HandleColor.asmx" />
                    </Services>
                    <Scripts>
                        <asp:ScriptReference Path="HandleColor.js" />
                    </Scripts>
                </asp:ScriptManager>  
                <table style="font-size:12px">
                    <tr>
                        <td>Цвет по умолчанию для Web-сервиса:</td>
                        <td>
                         <button id="Button1" 
                            onclick="GetDefaultColor(); return false;">Получить цвет по умолчанию</button>
                        </td>
                    </tr>
                </table> 
               <hr />
                <!—Отображение текущего цвета.-->
                <p>
                    <span style="background-color:Yellow">Цвет:</span>
                    <span id="ResultId"></span>
                </p>
            </form>
        </body>
    </html>

    В следующей части примера показан клиентский сценарий страницы HandleColor.js, который используется для вызова службы:

    // Эта функция извлекает значение по умолчанию для цвета
    function GetDefaultColor()  
    {
        // Вызываем нужный Веб-сервис
        Samples.AspNet.HandleColor.GetDefaultColor(SucceededCallback);  
    }
    // Это функция обратного вызова, обрабатывающая результат вызова сервиса
    function SucceededCallback(result)
    {
        // Извлечение данных, полученных от сервиса
        var message = result.message;
        var rgb = result.rgb;
        var timeStamp = result.timeStamp;
        // Преобразование RGB массива в строку
        var serverColor = rgb[0]+ rgb[1] + rgb[2];
        // Отображение результата
        var displayResult = document.getElementById("ResultId");
        displayResult.style.color = "yellow";
        displayResult.style.fontWeight = "bold";
        if (document.all) 
            displayResult.innerText = message + " " + timeStamp;
        else
           // Firefox
           displayResult.textContent = message + " " + timeStamp;
        displayResult.style.backgroundColor = "#" + serverColor;
    }
    if (typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В следующей части примера показана вызываемая на странице служба HandleColor.asmx, которая реализуется классом Samples.AspNet.HandleColor:

    <%@ WebService Language="C#" Class="Samples.AspNet.HandleColor" %>
    using System;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Script.Services;
    using System.Web.Script.Serialization;
    namespace Samples.AspNet
    {
        // Определяем объект "Цвет" для общения с клиентом
        public class ColorObject
        {
            public string message = "Цвет по умолчанию – синий.";
            public string[] rgb = new string[] { "00", "00", "FF" }; 
            public string timeStamp;
        }
        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [GenerateScriptType(typeof(ColorObject))]
        [ScriptService]
        public class HandleColor : System.Web.Services.WebService
        {
            [WebMethod]
            public ColorObject GetDefaultColor()
            {
                // Создаем новый объект.
                ColorObject co = new ColorObject();
                // Указывает текущее время.
                co.timeStamp = DateTime.Now.ToString();
                return co;         
            }
        }
    }

    Результат работы разработанной страницы представлен на рис. 14.4.

    (рис 14.4) Работа Веб-сервиса, возвращающего составной тип данных

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

    В первой части примера показана страница, на которой выполняется вызов сервиса с использованием клиентского сценария. Она имеет ссылку на файл HandleColor.asmx, который содержит описание сервиса, и на файл HandleColor.js, который содержит JavaScript -код, вызывающий веб-сервисы:

    <%@ Page Language="C#" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
        <head id="Head1" runat="server">
            <title>Получение составного типа данных</title>
            <style type="text/css">
                body
                {
                    font: 11pt Trebuchet MS;
                    font-color: #000000;
                    padding-top: 72px;
                    text-align: center;
                }
                .text
                {
                    font: 8pt Trebuchet MS;
                }
            </style>
        </head>
        <body>
            <h2>Получение составного типа данных</h2>
            <form id="form1" runat="server">
                <asp:ScriptManager runat="server" ID="scriptManager">
                    <Services>
                        <asp:ServiceReference Path="HandleColor.asmx" />
                    </Services>
                    <Scripts>
                        <asp:ScriptReference Path="HandleColor.js" />
                    </Scripts>
                </asp:ScriptManager>
                <table style="font-size: 12px;">
                    <tr align="center">
                        <td class="text">
                            Изменить цвет:
                        </td>
                        <td>
                            <select id="ColorSelectID" onchange="OnChangeDefaultColor(this);" runat="server">
                            </select>
                        </td>
                    </tr>
                </table>
                <hr />
                <!—Отображение текущего цвета.-->
                <span style="background-color: Yellow">Цвет:</span>
                <span id="ResultId"></span>
            </form>
        </body>
    </html>

    В следующей части примера показан клиентский сценарий страницы HandleColor.js, который используется для вызова службы:

    // Цвет по умолчанию для сервиса.
    var defaultRgb;
    // Элемент, отвечающий за отображение результата.
    var displayResult;
    // Получаем список доступных цветов и цвет по молчанию.
    function GetServerColors() {
        // Получаем цвет по умолчанию.
        Samples.AspNet.HandleColor.GetDefaultColor(
            SucceededCallback, FailedCallback);
        // Получаем список доступных цветов.
        Samples.AspNet.HandleColor.GetColorList(
            SucceededCallback, FailedCallback);
    }
    // Эта функция отправляет на сервер выбранный пользователем объект цвет в элементе select
    function OnChangeDefaultColor(comboObject) {
        // Создаем экземпляр объекта.
        var color = new Samples.AspNet.ColorObject();
        // Поучаем выбранный пользователем цвет
        var selectionIndex = comboObject.selectedIndex;
        var selectedColor = comboObject.options[selectionIndex].text;
        // Получаем RGB представление цвета.
        var selectionValue = comboObject.value;
        // И превращаем его в массив.
        var colorArray = selectionValue.split(",");
        // Присваиваем значения полей объекта
        color.message = "Новый цвет по умолчанию " + selectedColor + ".";
        color.rgb = colorArray;
        // Вызываем сетод Веб-сервиса для изменения цвета.
        Samples.AspNet.HandleColor.ChangeDefaultColor(
            color, SucceededCallback, FailedCallback);
    }
    // Эта фунуция обрабатывает результат работы методов сервиса
    function SucceededCallback(result, userContext, methodName) {
        switch (methodName) {
            case ("GetColorList"):
                {
                    // Получаем элемент select.
                    var selectObject = document.getElementById("ColorSelectID");
                    var i = 0;
                    // Пробегаемся по списку для заполнению select данными
                    for (var j=0; j < result.length; j++) {
                        var item = result[j];
                        var option = new Option(item.name, item.rgb);
                        selectObject.options[i] = option;
                        // Устанавливаем значение по умолчанию.
                        if (item == defaultRgb[0] + defaultRgb[1] + defaultRgb[2])
                            selectObject.options[i].selected = true;
                        i++;
                    }
                    break;
                }
            default:
                {
                    var message = result.message;
                    defaultRgb = result.rgb;
                    var timeStamp = result.timeStamp;
                    var serverColor = defaultRgb[0] + defaultRgb[1] + defaultRgb[2];
                    displayResult.style.color = "yellow";
                    displayResult.style.fontWeight = "bold";
                    if (document.all)
                        displayResult.innerText = message + " " + timeStamp;
                    else
                    // Firefox
                        displayResult.textContent = message + " " + timeStamp;
    
                    displayResult.style.backgroundColor = "#" + serverColor;
                    break;
                }
        }
    }
    // Функция обратного вызова, используемая в случае, если Веб-сервис отработал с ошибками
    function FailedCallback(error, userContext, methodName) {
        if (error !== null) {
            displayResult.innerHTML = "Произошла ошибка: " + error.get_message();
        }
    }
    // Получаем список цветов и цвет по умолчанию
    function pageLoad() {
        displayResult = document.getElementById("ResultId");
        GetServerColors();
    }
    if (typeof (Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В следующей части примера показана вызываемая на странице служба HandleColor.asmx, которая реализуется классом Samples.AspNet.HandleColor:

    <%@ WebService Language="C#" Class="Samples.AspNet.HandleColor" %>
    using System;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Script.Services;
    using System.Web.Script.Serialization;
    using System.Collections.Generic;
    
    namespace Samples.AspNet
    {
        // Определяем объект цвет для "общения с клиентом"
        public class ColorObject
        {
            public string message;
            public string[] rgb;
            public string timeStamp;
            public ColorObject()
            {
                this.message = "Цвет по умолчанию – красный.";
                this.rgb = new string[] { "FF", "00", "00" };
                this.timeStamp = DateTime.Now.ToString();
            }
        }
        // вспомогательный класс для отображения списка цветов
        public class ColorHelper
        {
            public string name;
            public string rgb;
            public ColorHelper()
            {
            }
        }
        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [GenerateScriptType(typeof(ColorObject))]
        [GenerateScriptType(typeof(ColorHelper))]
        [ScriptService]
        public class HandleColor : System.Web.Services.WebService
        {
            [WebMethod]
            public ColorObject GetDefaultColor()
            {
                // Возвращаем новый объект.
                ColorObject co = new ColorObject();
                return co;
            }
            [WebMethod]
            public ColorObject ChangeDefaultColor(ColorObject color)
            {
                // Создаем новый объект.
                ColorObject co = new ColorObject();
                // Присваиваем переданные значения.
                co.message = color.message;
                co.rgb = color.rgb;
                // Указываем текущее время.
                co.timeStamp = DateTime.Now.ToString();
                return co;
            }
            [WebMethod]
            public List<ColorHelper> GetColorList()
            {
    
                //Создаем таблицу цветов.
                List<ColorHelper> result = new List<ColorHelper>();
                //Добавляем несколько вариантов цветов.
                result.Add(new ColorHelper() { rgb = "00,00,FF", name = "Синий" });
                result.Add(new ColorHelper() { rgb = "FF,00,00", name = "Красный" });
                result.Add(new ColorHelper() { rgb = "00,FF,00", name = "Зеленый" });
                result.Add(new ColorHelper() { rgb = "00,00,00", name = "Черный" });
                return result;
            }
        }
    }

    Результат работы сервиса и страницы представлен на рис. 14.5.

    (рис 14.5) Работа веб-сервиса, возвращающего список доступных цветов

    Представленный подход позволяет улучшить производительность приложения, снизить нагрузку на сервере и упростить работу пользователя с сайтом. При этом, очевидно, что для отображения получаемых от Веб-сервиса данных, стоит использовать не серверные компоненты ASP.NET, которые, как правило, не предназначены для обработки таких данных и не имеют соответствующего интерфейса, а рассмотренные ранее JavaScript -библиотеки, например ExtJS.

    В результате получается, что ASP.NET служит для отображения страницы и манипуляции с данными, а за отображение отвечает JavaScript. Происходит уход от компонентной разработки, понижается понятность, структурированность и повторяемость кода. Для решения этих проблем разработан ряд технологий, такие как ExrJS.Direct, Jasp и другие. Рассмотрим одну из этих технологий.

    14.1.7. Ключевые термины

    ASP.NET, .NET Framework, Веб-служба, Веб-сервис, AJAX, JSON, Сериализация, XmlHttpExecutor, WebRequestManager, WebRequest, JavaScript.

    14.2. Технология Jasp

    14.2.1. Введение

    Jasp является библиотекой для разработки Веб-приложений с использованием платформы Microsoft .NET Framework [9, 10]. Основным отличием Jasp от ASP.NET является ориентация библиотеки на клиентскую сторону, чтобы программисту было проще создавать "богатый" пользовательский интерфейс в первую очередь средствами языка JavaScript, CSS и в последнюю очередь HTML, сгенерированным на стороне сервера (как это часто бывает при разработке на ASP.NET ). Такие приложения зачастую состоят из небольшого количества страниц (чаще всего одной), и все необходимое подгружают по мере использования без перезагрузки страницы. Примером таких приложений могут п ослужить сервисы: GMail, Google Maps, Pusk.ru и другие.

    Примечание: скачать библиотеку Jasp, а также примеры и документацию можно на http://code.google.com/p/jaspproject/ или www.jaspproject.ru.

    Основной недостаток ASP.NET заключается в сложности разработки клиентских компонент (использующих JavaScript ).

    Рассмотрим простой пример: предположим, на странице необходимо вводить дату, и в целях экономии места на странице было решено сделать текстовое поле с выпадающим вниз календарем. Есть два пути решения: создать наследника UserControl, и наследника WebControl. У каждого из вариантов есть плюсы и минусы. В первом случае несомненным плюсом является возможность создания разметки, скриптов и стилей прям в ASCX-файле, но при этом данный компонент нельзя использовать в других проектах (только если копировать код из одного проекта в другой). Во втором случае нет ASCX-файла, а все дочерние компоненты приходится создавать программно. С другой стороны такой компонент можно использовать повторно.

    В обоих случаях, при реализации клиентской части компонента возникает ряд проблем:

  • всегда необходимо передавать ClietnID компонента в JavaScript -код, что конечно возможно, но не всегда удобно;
  • приходится следить за загрузкой связанных с компонентом ресурсов: изображений, стилей и вспомогательных JavaScript -файлов;
  • вместо отсутствующей клиентской части компонент есть простой набор вызовов и несколько объявлений JavaScript -функций.
  • Обобщим все возможные проблемы, при создании компонент на классическом ASP.NET:

  • Различные трудности, возникающие при связи серверной и клиентской части компонента, в результате которых зачастую на клиентской стороне получается не полноценный объект с набором свойств и методов, а набор строк JavaScript -кода, с которым, нельзя работать как с одним объектом.
  • Огромный размер ViewState. Есть плюсы его использования, но чаще всего (особенно при использовании красивых и функциональных сторонних компонент ), он достигает таких больших размеров, что из-за него страдает производительность, даже если его хранить на сервере.
  • Использование ASP.NET Ajax зачастую приводит к непредсказуемым результатам. ViewState пересылается при каждом, пусть и асинхроном PostBack'e, даже там, где это не нужно, что в свою очередь не очень хорошо сказывается на производительности;
  • Ресурсы , загружаемые с помощью WebResource.axd. Порой на одной странице их скапливается довольно много, тем самым, осложняя жизнь браузеру. К тому же не все браузеры способны одновременно загружать несколько ресурсов на страницу.
  • При большой вложенности компонент получаем огромные ClientID, что плохо сказывается на результирующем размере HTML разметки, а иногда приводит к полной неработоспособности страницы.
  • Нет возможности полноценного наследования компонент (включая клиентскую сторону или HTML разметку).
  • При создании WebControl'ов создание дочерних компонент приходится делать вручную.
  • При создании UserControl'ов, их нельзя использовать в других проектах.
  • Технология Jasp пытается решить все указанные проблемы. Компонент Jasp одновременно является как серверным (разработка в ключе классического ASP.NET ), так и клиентским ( JavaScript - компонентом ). Каждый компонент можно использовать и в других проектах (даже имеющий разметку и собственные ресурсы ) без всяких изменений. Присутствует возможность наследования, как серверной части, так и клиентских частей: JavaSctpt, CSS и разметки. Все ресурсы загружаются автоматически и по мере использования (синхронно или асинхронно). При первой загрузке страницы загружаются только необходимые ресурсы, оптимизирова нные и сгруппированные в несколько файлов. Jasp можно использовать со всеми распространенными JavaScript -библиотеками: jQuery, ExtJS, Prototype и т.п.

    14.2.2. "Hello World" на Jasp

    Создание нового приложения на Jasp ничем не отличается от создания обычного ASP.NET приложения. Можно даже не создавать новый WebSite/WebApplication, а использовать существующий. Главное, чтобы был добавлен Reference на сборку Jasp и присутствовали минимальные необходимые настройки в web.config для работы Jasp:

    <configuration>
      <configSections>
        <section name="jaspSettings" type="Jasp.Configuration.SettingsSection"/>
      </configSections>
      <jaspSettings  />
      <system.web>
        <httpHandlers>
          <add verb="*" path="*.jasp" type="Jasp.Web.HttpHandler, Jasp"/>
        </httpHandlers>
      </system.web>
      <system.webServer>
        <handlers>
          <add name="JaspCommonHandler" path="*.jasp" verb="*" type="Jasp.Web.HttpHandler, Jasp" 
           resourceType="Unspecified" preCondition="integratedMode"/>
        </handlers>
      </system.webServer>
    </configuration>
    Примечание: для работы сайта, использующего Jasp, под управлением IIS необходимо подключить ISAPI-фильтр на расширение .jasp.

    Для создания страницы необходимо создать класс, унаследованный от класса Jasp.Web.UI.Page. Имя класса должно совпадать с именем страницы.

    Создадим страницу, выводящую "Hello world!".

    using Jasp.Web.UI;
    using Jasp.Web.UI.HtmlControls;
    namespace Jasp.Examples
    {
        public class HelloWorld : Page
        {
            protected override void OnInit()
            {
                Body.Controls.Add(new Literal("Hello world!"));
            }
        }
    }

    Чтобы зайти на страницу, вводим в браузере /HelloWorld.jasp. Пока не будем углубляться в детали сгенерированного HTML, а просто убедимся, что все работает – в браузере увидим "Hello world!", и при загрузке страницы не произойдет JavaScript -ошибок.

    14.2.3. Создание страницы с разметкой, скриптом и стилями

    Рассмотрим пример создания страницы, содержащую разметку, скрипты и стили.

    Для примера, создадим страницу, текст которой будет анимирован с помощью эффектов jQuery. Для начала, добавим в проект JavaScript -файл jquery-1_2_6_min.js с библиотекой jQuery (в любое место проекта). Не забудем установить Build action в embedded resource (подробнее подключение ресурсов описывается ниже). Данный файл будет являться общим ресурсом для всех страниц или компонент.

    Далее создадим класс для страницы, назовем его Example1:

    using Jasp.Resources;
    using Jasp.Web.UI;
    namespace Jasp.Examples
    {
        [Resources("jquery-1_2_6_min.js")]
        public class Example1 : Page
        {
        }
    }

    Добавим классу атрибут ResourcesAttribute с указанием имени файла с jQuery. Теперь, при загрузке страницы, Jasp будет добавлять этот файл к скриптам страницы.

    Теперь создадим CSS файл Example1.css со стилями для текста страницы:

    .text-style
    {
      color:red;
      font-weight:bold;
      font-size:large;
    }

    Также создадим файл Example1.htm с разметкой, содержащий текст, который будет анимироваться:

    <html>
    <head>
      <title>Пример 1</title>
    </head>
    <body>
      <span id="text">Анимированный текст!</span>
    </body>
    </html>

    Теперь создадим JavaScript -файл Example1.js с кодом, который будет анимировать текст:

    {
      // функция, вызываемая при инициализации контрола
      $init: function(){
        // указатель на текущий контрол
        var self = this;
        // дожидаемся инициализации jQuery
        jQuery(document).ready(function(){
          // запускаем анимацию
          self.animateText();
        });
      },
      // функция для анимации текста
      animateText: function(){
        // указатель на текущий контрол
        var self = this;
        // производим анимацию    
        jQuery(self.$text.$dom).toggleClass('text-style').toggle(1000, function(){
          self.animateText();
        });
      }
    }

    После загрузки страницы, мы увидим мигающий текст.

    Рассмотрим, каким образом Jasp подгружает CSS и JavaScript файлы.

    14.2.4. Ресурсы в Jasp

    При загрузке страницы, Jasp определяет, какие ресурсы используются на странице: это может быть HTML-разметка, один или несколько CSS или JavaScript файлов. Jasp группирует все CSS и JavaScript файлы в один CSS и один JavaScript файл и добавляет специальные ссылки в HTML разметку, по которым они подгружаются браузером:

    <script type="text/javascript" src="?$scripts=7_5_2_3_4_6_v=-1720007920"> </script> 
    <link rel="stylesheet" type="text/css" href="?$styles=v=-1720007920" />

    По-хорошему, у всех страниц сайта должна быть одна базовая страница (аналог MasterPage ), содержащая одинаковый набор базовых стилей и скриптов. Таким образом, данные ресурсы "закэшируются" и для других страниц сайта.

    Страницы могут отличаться набором компонент, которые на них расположены. При загрузке страницы Jasp также определяет, какие ресурсы используют все компоненты, расположенные на странице. HTML каждого компонента подставляется в соответствующее место в результирующий HTML, а все CSS и JavaScript файлы, по аналогии с ресурсами страницы, объединятся в один CSS и один JavaScript файл.

    <script type="text/javascript" src="?$pageScripts=2_$r=v=-1720007920"> </script> 
    <link rel="stylesheet" type="text/css" href="?$pageStyles=2_$r=v=-1720007920" />

    У каждой страницы или компонента могут быть как личные ресурсы, которые подгружаются автоматически и могут использоваться только данным компонентом, или его наследниками, так и общие, которые могут использоваться сразу несколькими компонентами. Личные ресурсы должны называться также как имя класса компонента (без учета расширения). Естественно, ресурсы компонент, которые изначально не используются на странице, загружены не будут. Их загрузка произойдет автоматически при первом использовании.

    14.2.4.1. CSS ресурсы

    Зачастую в проектах бывает один или пару CSS файлов, в которые без разбора добавляются все стили из всего проекта, даже если стиль используется один раз для определенного элемента. Такой файл получается довольно большим и трудно поддерживаемым. В Jasp можно создать общий файл, куда поместить основные стили, а стили, специфичные для определенных компонент или страниц, помещать в личные CSS-файлы компонент.

    Необходимо отметить, что Jasp подменяет клиентские идентификаторы компонент – поэтому стили с селекторами через идентификаторы работать не будут.

    14.2.4.2. JavaScript ресурсы

    JavaScript ресурс – это текстовый файл с расширением js, содержащий JavaScript -код компонента. Данный файл должен определять один единственный объект – экземпляр компонента. В простейшем случае это пустой объект:

    {
    }

    Если в объекте определена функция $init, то она будет вызвана сразу же после вызова клиентского конструктора (конструкторы создаются автоматически и подменять или модифицировать их нельзя):

    {  $init: function(){
        // код инициализации
      }
    }

    Также в контексте данного объекта могут быть определены и другие функции компонента.

    14.2.4.3. HTML ресурсы

    HTML ресурс – это текстовый файл с расширение html или htm, содержащий HTML разметку компоненты или страницы. Данный файл должен удовлетворять синтаксическим требованиям XML, но при этом, может не являться XHTML-документом. Также в этом файле могут встречаться вставки для создания экземпляров дочерних компонент.

    14.2.4.4. Бинарные ресурсы

    Также Jasp поддерживает в качестве ресурсов изображения и другие бинарные файлы. При первом старте сайта, такие файлы будут "распакованы" из всех сборок сайта по умолчанию в папку \deploy\<имя сборки> в корне сайта. В HTML, CSS и JavaScript можно использовать специальную конструкцию ~<имя сборки>/<путь к ресурсу>, например в CSS можно написать:

    body {  
        background-image: url('~Jasp.Examples/Files/Images/background.gif')
    }

    В результирующий CSS попадет отформатированная ссылка на ресурс, содержащая абсолютный путь до файла (если сайт работает в виртуальной директории, то будет подставлено и имя директории):

    body {  
        background-image: url('/deploy/Jasp.Examples/Files/Images/background.gif')
    }
    Примечание: Все вышеупомянутые типы ресурсов должны быть настроены как embedded resource, быть в кодировке UTF-8 и не содержать в названии более одной точки.

    При разработке, нет необходимости каждый раз после изменения файла делать перекомпиляцию проекта – Jasp сам умеет следить за изменениями в ресурсах и автоматически подхватывать их.

    Конечно, же, никто не мешает использовать обычный подход к ресурсам, т.е. просто хранить CSS/ JavaScript /HTML и т.п. файлы, но тогда теряется ряд преимуществ, которые предоставляет Jasp: автоматическая загрузка стилей, скриптов по мере использования; минимизация скриптов и разметки; GZIP для страниц, стилей и разметки; возможность использования ресурсов и компонент, использующих их, из других проектов.

    Зачастую, разные проекты на нужды одной компании имеют похожий интерфейс. Используя Jasp, можно с легкостью создать библиотеку компонент и ресурсов, содержащую базовые страницы ( MasterPage с layout 'ом по умолчанию, страницы ошибок, 404, 500 и т.п.), компоненты, изображения, стили, скрипты, и другие общие для проектов ресурсы. Например, используя библиотеку ExtJS сразу в нескольких проектах, достаточно сложно поддерживать одинаковую версию ExtJS библиотеки (включая все изображения, скины, плагины и bugfix'ы), т.к. это огромный набор файлов. Используя Jasp, можно с легкостью создать сборку, содержащую все необходимые ресурсы. Это решит проблему синхронизации версий всех файлов между проектами, а также возможные проблемы обновления при выходе новой версии библиотеки. Кроме того, как уже говорилось ранее, Jasp оптимизирует всю работу с ресурсами, начиная от автоматической загрузки, кончая сжатием и клиентской оптимизацией.

    14.2.5. Страницы и компоненты

    14.2.5.1. Создание страниц

    Для создания страницы, необходимо создать класс, наследующий от класса Jasp.Web.UI.Page, после чего, Jasp будет обрабатывать запрос к одноименной странице. Вложенность страниц в папки производится через пространства имен, т.е. достаточно создать папку в проекте, после чего все классы, создаваемые в папке, будут находиться в пространстве имен новой вложенности (по умолчанию в Visual Studio), также как и путь к странице. Тут необходимо учесть, что за базовое пространство имен (от которых начинается нулевой уровень вложенности страниц ), берется имя сборки. Например, для сборки с именем Jasp.Examples, страница с классом Jasp.Examples.Example1 будет обрабатываться по запросу /Example1.jasp, а с классом Jasp.Examples.OldExamples.Example2 по адресу /OldExamples/Example2.jasp. Естественно, имена страниц не чувствительны к регистру.

    14.2.5.2. Создание компонент

    Для создания компонент, необходимо создать класс, наследованный от класса Jasp.Web.UI.UserControl. В Jasp страница является обычным компонентом, так что все функции и возможности, доступные компоненту, как на серверной, так и на клиентской стороне доступны также и странице. Далее, все действия, описываемые для компонент, справедливы также и для страниц.

    Каждому компоненту, за исключением страницы, можно создать сокращенное наименование ( alias, алиас) с помощью атрибута AliasAttribute, чтобы не указывать зачастую длинные полные имена, и использовать его в JavaScript -коде или разметке.

    Как говорилось ранее, в Jasp каждому компоненту можно прикреплять ресурсы, которые автоматически будут загружаться, при его использовании. Для автоматической подгрузки общих ресурсов компонента, достаточно добавить атрибут ResourcesAttribute, в котором перечислить названия общих JavaScript или CSS ресурсов.

    Также важно отметить, что на клиентской стороне Jasp не создает лишних глобальных переменных – вся работа происходит внутри одного глобального объекта Jasp. Это позволяет избежать конфликтов с другими библиотеками (в целях оптимизации, Jasp все же создает сокращенные глобальные "алиасы" для компонент, которые имеют формат $j<системный номер компонента>, но вероятность того, что данные "алиасы" могут вызвать конфликт с другими JavaScript -библиотеками крайне мала).

    Создать экземпляр компонента можно следующими методами:

  • программно, на серверной стороне;
  • через разметку, на серверной стороне;
  • синхронно или асинхронно на клиентской стороне.
  • При использовании первых двух вариантов, ресурсы компонент загружаются при загрузке страницы. При использовании последнего варианта, для всех ресурсов компонента идет один запрос к серверу, который возвращает необходимые данные и ресурсы (скрипты, разметку и стили). В Jasp у каждого компонента есть коллекция с дочерними компонентами как на серверной стороне (свойство Controls ), так и на клиентской (массив $controls ). Также на клиентской стороне у родительского компонента присутствуют свойства с идентификаторами каждого дочернего компонента (начиная со специального символа $ ).

    14.2.5.2.1. Создание экземпляров компонент программно

    Для создания компонента программно, необходимо создать новый экземпляр класса и добавить его с помощью метода Add в коллекцию Controls родительского компонента или страницы. Правильнее всего, это делать в переопределенном методе OnInit:

    protected override void OnInit()
    {
      Controls.Add(new MyUserControl());
    }

    14.2.5.2.2. Создание экземпляров компонент через разметку

    Для создания компонент в разметке достаточно создать тег, с полным именем компонента (включая пространство имен) и указанием атрибутов, которые он поддерживает:

    <jasp.examples.controls.myusercontrol id="control1" title="Контрол 1" />

    14.2.5.2.3. Создание экземпляров компонент на JavaScript

    Для создания компонент на JavaScript, достаточно создать новый экземпляр с помощью оператора new, и передать необходимые параметры первым аргументом:

    {
        $init: function() {
            var control = new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1'
            });
            // производим необходимые действия с контролом
        }
    }

    Если в аргументе задано свойство callback (типа function ), то компонент будет создан асинхронно, и callback будет вызван после создания компонента на сервере:

    {
        $init: function() {
            new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1',
                callback: function(control) {
                    // производим необходимые действия с контролом
                }
            });
        }
    }

    Если в аргументе задано свойство arg (типа array ), то при создании компонента будет вызван серверный конструктор с заданными параметрами из arg (поддерживается перегрузка только по числу параметров, по типу параметров перегрузка не поддерживается). По своей сути arg – это аргумент конструктора. При этом на сервере все дочерние компоненты, которые будут созданы при запросе (из разметки или программно), будут возвращены на клиент и также будут являться дочерними для создаваемого клиентского компонента. Вызываемый конструктор должен быть помечен атрибутом JsonRpcMethodAttribute.

    Например, у нас есть следующий компонент:

    public class MyUserControl : UserControl
    {
        private int _x, _y;
        public MyUserControl()
        {
        }
        [JsonRpcMethod]
        public MyUserControl(int x, int y)
        {
            _x = x;
            _y = y;
        }
        protected override void OnLoad()
        {
            Title = string.Format("Сумма {0}", _x + _y);
        }
        public string Title
        {
            get { return (string)Data["title"]; }
            set { Data["title"] = value; }
        }
        ///// <summary>
        ///// Данная реализация свойства работает с компонентом title, определенным в разметке
        ///// </summary>
        //public string Title
        //{
        //    get { return ((HtmlControl)GetById("title")).InnerText; }
        //    set { ((HtmlControl)GetById("title")).InnerText = value; }
        //}
    }
    Примечание: в последней версии библиотеке атрибут JsonRpcMethodAttribute был удален. Теперь все публичные методы доступны из JavaScript -кода.

    Одновременно создание компонента на сервере и на клиенте производится следующим образом:

    {
        $init: function() {
            var control = new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1',
                arg: [3, 2]
            });
            // производим необходимые действия с контролом
        }
    }

    Как видно из примера, на сервере происходит сложение двух чисел, переданных в клиентский конструктор через параметр arg. Результат записывается в свойство Title, которое может передавать результат на клиентскую сторону в виде JSON -объекта через свойство Data либо через произвольный HTML элемент. Например, через конструктор удобно передавать идентификаторы либо другие объекты, характеризующие какие-либо сущности, далее загружать необходимые данные на сервере и возвращать их на клиентскую сторону вместе с компонент.

    Если arg не определен, то произойдет только клиентское создание компонент, при котором дочерние компоненты, определенные на стороне сервера, созданы не будут. Например, компонент для ввода даты вряд ли потребует вызов серверного конструктора.

    14.2.6. Передача данных на клиентскую сторону

    Для передачи данных на клиентскую сторону, на сервере можно использовать словарь Data, который есть у каждого компонента. Все значения, записанные в этот словарь, будут доступны на клиентской стороне в виде JSON -объекта в свойстве $data.

    Например, записав при загрузке текущую дату:

    protected override void OnLoad()
    {
      Data["serverTime"] = DateTime.Now;
    }

    До нее можно с легкостью добраться на клиентской стороне:

    {
        $init: function() {
            alert(this.$data.serverTime);
        }
    }

    Данную методику можно использовать только в момент загрузки компонента, как при загрузке страницы, так и при его отложенной загрузке через JavaScript.

    Если на клиентской стороне необходимо получить/отправить данные в произвольный момент, можно воспользоваться удаленным вызовом функции (RPC). Все методы компонента, помеченные атрибутом RpcMethodAttribute, можно вызывать удаленно.

    Пример таких методов представлен ниже. Остается отметить, что такие методы, по сути, являются методами Веб-сервисов, для которых созданы прокси-классы в JavaScript:

    public class MyUserControl : UserControl
    {
        [JsonRpcMethod]
        public Person GetPersonById(int personId)
        {
            // возвращаем Person
        }
        [JsonRpcMethod]
        public void SavePerson(Person person)
        {
            // сохраняем Person
        }
    }

    Перегрузка функций поддерживается только по количеству параметров. По типу параметров перегрузка не поддерживается. В качестве параметров или возвращаемого значения могут быть произвольные объекты, которые впоследствии будут отформатированы в JSON -объекты.

    14.2.7. Наследование компонент и страниц

    Jasp поддерживает наследование компонент как на серверной стороне, так и на клиентской. У компонента наследуются все ресурсы от базового. С помощью наследования, можно, например, делать страницы, содержащие базовую разметку (по аналогии с MasterPage в ASP.NET ), но также и отдельные компоненты, которые содержат часть базовой разметки и могут быть использованы для производных компонент.

    Для наследования компонент, достаточно унаследовать серверный класс. При этом все ресурсы компонента (разметка, стили и скрипты) будут дополнять или замещать ресурсы базового компонента.

    14.2.7.1. Наследование разметки

    По умолчанию, HTML разметка копируется от базового компонента. Но благодаря специальному тегу <replacement> можно заменять или удалять базовые компоненты. Например, у нас есть базовая разметка:

    <body>
      <div id="div1">Первоначальный текст  </div>
    </body>

    И производная разметка:

    <replacement id="div1">
      <div id="div2">Переопределенный текст</div>
      И другой текст  
    </replacement>

    При копировании разметки из базового компонента, вместо элемента с идентификатором div1 будет подставлена замена из тега replacement с одноименным идентификатором.

    14.2.7.2. Наследование стилей

    При добавлении CSS ресурса к наследуемому компоненту, Jasp будет добавлять его сразу после CSS ресурса родительского компонента. Тем самым, если создавать одноименные стили – они будут переопределяться. Данная практика наследования хорошо работает со стилями страницы, но при работе с компонент возникают проблемы – стили затираются и для родительского компонента, который может быть размещен на этой же странице, что неявно изменит его внешний вид. Решение для таких случаев – это создание стилей с другими именами.

    14.2.7.3. Наследование JavaScript

    Наследование JavaScript в Jasp реализовано через прототипы. Оно очень похоже на классическое наследование в объектно-ориентированном программировании (ООП). При определении новых методов, они будут переопределять родительские. В новом методе можно вызвать базовый метод. Например, JavaScript базового компонента:

    {
        hi: function() {
            alert('Базовый привет!');
        }
    }

    И производного:

    {
        hi: function() {
            alert('Привет!');
            BaseFooControl.prototype.call(this);
        }
    }

    Для библиотек, которые поддерживают наследование через прототипы (например, ExtJS) можно задать базовый класс для компонента из библиотеки с помощью атрибута ClientBaseAttribute:

    [ClientBase("Ext.Window")]
    public class FooControl : UserControl
    {
        //....
        public FooControl(int fooId)
        {
            // подгружаем необходимые данные
        }
    }

    Клиентский код:

    {
        onFooButtonClick: function() {
            // Создаем FooControl 
            // происходит вызов серверного конструктора – в нем можно подгрузить необходимые данные
            // и вызов $init – в нем инициализировать окно, подставить загруженные данные
            var fooControl = new FooControl({
                arg: [1] // параметры конструктора
            });
            // Показываем окно (метод show наследован из ExtJs)
            fooControl.show();
        }
        // ...
    }

    Благодаря этому, компонент на Jasp будет являться одновременно и компонентом ExtJS (либо другой библиотеки). Чтобы не было конфликтов с другими библиотеками, каждое свойство или функция на клиентской стороне начинается с $. Так, например, удается избежать конфликтов идентификаторов ( id ) или часто распространенных имен функций ( init ).

    14.2.8. Конфигурирование Jasp

    Jasp конфигурируется с помощью собственной секции в файле web.config.

    Также необходимо добавить HttpHadler и WebServer Hadler, чтобы ASP.NET начал обрабатывать запросы Jasp. Если параметры не определены, Jasp конфигурируется в зависимости от конфигурации сборки. Если сборка Jasp собрана в Debug режиме, параметры оптимизированы для отладки, если сборка собрана в Release режиме, то параметры оптимизированы для производительности. В табл. 11.3 перечислены параметры конфигурации, которые поддерживает Jasp.

    Параметры конфигурации Jasp
    Название Описание Значение в Release Значение в Debug Тип
    deployPath Путь, относительный от корня сайта, куда "распаковывать" бинарные ресурсы сборки. deploy deploy String
    resourceFilter Полное имя класса, который фильтрует все текстовые ресурсы, обрабатываемые Jasp. Jasp.Resources.ResourceFilter, Jasp Jasp.Resources.ResourceFilter, Jasp String
    gzipEnabled Включен ли GZIP. True False Boolean
    watchResources Включено ли слежение за изменением текстовых ресурсов. False True Boolean
    compressJavaScript Включено ли сжатие JavaScript (удаление комментариев, лишних пробелов и т.п.). True False Boolean
    clientCaching Включена ли отправка HTTP заголовков кэширования. True False Boolean
    indentHtml Включено ли форматирование HTML. False True Boolean
    exceptionFormatter Полное имя класса, занимающегося форматирование исключения в JSON -объект. Jasp.Web.ExceptionFormatter, Jasp Jasp.Web.ExceptionFormatter, Jasp String

    Параметр resourceFilter определяет класс, экземпляр которого будет заниматься фильтрацией текстовых ресурсов. Если нам нужно как-то отформатировать текстовый ресурс, например, удалить лишние пробелы или подменить ссылки, то можно создать класс, наследованный от класса Jasp.Resources.ResourceFilter и переопределять метод Filter(string name, StringBuilder data). Отфильтрованный ресурс впоследствии кэшируется. Для примера, переопределив этот параметр, можно производить замену путей в CSS-файлах библиотеки ExtJS автоматически, чтобы при обновлении этих файлов ничего не забыть.

    Параметр exceptionFormatter используется для указания класса, экземпляр которого форматирует исключение при RPC-вызовах в JSON -объект. Если вам необходимо при возникновении исключения выводить дополнительные данные, например лог nHibernate, то это без труда можно сделать, переопределив метод Format класса Jasp.Web.ExceptionFormatter.

    Узнав, что все ресурсы в Jasp должны быть embedded resource, многие думают, что для вступления в силу изменений в HTML, CSS или в JavaScript необходимо перекомпиляция проекта. За это отвечает параметр watchResources. Если он установлен в True, то Jasp следит за ресурсами, и при изменении автоматически их "подхватывает".

    14.2.9. Ключевые термины

    Jasp, .NET Framework, ASP.NET, jQuery, Ресурсы, Страницы, Компоненты, JSON, Наследование.

    14.3. Краткие итоги

    Веб-службы можно создавать, используя архитектуру ASP.NET, что позволяет им использовать разнообразную функциональность платформы.

    При работе с Веб-сервисами существуют две фундаментальные роли:

  • создание Веб-сервиса ;
  • обращение к Веб-сервису.
  • ASP.NET позволяет создавать Веб-сервисы, к которым можно обращаться из клиентского сценария страниц.

    Страницы взаимодействуют с сервером посредством уровня связи, использующего технологию AJAX для вызова Веб-служб.

    Обмен данными между клиентом и сервером осуществляется асинхронно, как правило, в формате JSON.

    Существует два подхода к вызовам методов Веб-сервиса:

  • с помощью команды POST HTTP ;
  • с помощью команды GET HTTP.
  • Основные элементы уровня связи Веб-служб:

  • WebRequest;
  • WebRequestManager ;
  • XmlHttpExecutor ;
  • JSON - сериализация.
  • Элементы серверной архитектуры AJAX включают:

  • пользовательские Веб-службы ;
  • методы страниц ;
  • службу проверки подлинности;
  • службу ролей;
  • службу профилей;
  • JSON - сериализацию ;
  • XML- сериализацию.
  • Веб-сервисы создаются в управляемом коде с помощью платформы ASP.NET:

  • при помощи класса WebRequest;
  • с помощью использования прокси-классов.
  • Jasp является библиотекой для разработки Веб-приложений с использованием платформы Microsoft .NET Framework.

    Основным отличием Jasp от ASP.NET является ориентация библиотеки на клиентскую сторону, чтобы программисту было проще создавать "богатый" пользовательский интерфейс в первую очередь средствами языка JavaScript, CSS и в последнюю очередь HTML, сгенерированным на стороне сервера.

    При загрузке страницы, Jasp определяет, какие ресурсы используются на странице:

  • CSS ресурсы ;
  • JavaScript ресурсы ;
  • HTML ресурсы ;
  • бинарные ресурсы.
  • Для создания страницы, необходимо создать класс, наследующий от класса Jasp.Web.UI.Page.

    Для создания компонент, необходимо создать класс, наследованный от класса Jasp.Web.UI.UserControl.

    Экземпляры компонент могут создаваться:

  • программно;
  • через разметку;
  • на JavaScript.
  • Для передачи данных на клиентскую сторону, на сервере можно использовать словарь Data, который есть у каждого компонента.

    Jasp поддерживает наследование компонент, как на серверной стороне, так и на клиентской.

    Jasp конфигурируется с помощью собственной секции в файле web.config.

    Страницы:

    Презентацию к данной лекции Вы можете скачать здесь.

    14.1. Веб-сервисы

    14.1.1. Введение

    На предыдущих лекциях была рассмотрена технология ASP.NET AJAX, которая позволяла существенно улучшить производительность Веб-приложений и в то же время улучшала работу пользователя с сайтом:

  • Сокращение времени работы серверного кода – использование UpdatePanel, за счет чего метод Page.Render отрисовывает не всю странице целиком, а только определенные части. Более того, при этом уменьшается и объем данных, передаваемый от сервера клиенту (опять же за счет того, что передается не вся страница ).
  • Пользователь не наблюдает процесс перезагрузки всей страницы при каждой обратной передаче, что делает пользовательский интерфейс страницы более естественным.
  • Среди недостатков подхода ASP.NET AJAX стоит отметить тот факт, что каждое действие пользователя приводит к отправке на сервер и получение от него большого объема данных в виде ViewState. Если же отказаться от использования ViewState, то для восстановления страницы на сервере необходимо либо хранить все данные в сессии, либо каждый раз извлекать их из базы данных. Еще один недостаток заключается в том, что при использовании достаточно сложного интерфейса, насыщенного большим количеством сложных компонент, время их рендеринга на сервере сильно возрастает.

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

    Веб-сервисы XML – это единица программного кода, обслуживаемая Веб-сервером и доступная в рамках стандартных промышленных технологий, таких как HTTP и XML [1]. Веб-сервисы предлагают такой уровень совместимости и взаимодействия в отношении операционных систем, платформ и языков, который ранее был просто недоступен.

    Веб-службы ASP.NET являются компонентами, методы которых можно вызывать с помощью запросов HTTP. Однако в отличие от традиционного Веб-приложения, Веб-сервисы XML можно использовать не только для того, чтобы возвращать браузеру HTML-код с целью визуализации. Скорее наоборот – Веб-сервисы XML чаще всего предоставляют функциональные возможности, аналогичные возможностям стандартной библиотеки программного кода .NET, такие как специальные вычисления, выборку данных из БД, чтение цен на акции и другие.

    Веб-службы предоставляют возможность обмениваться сообщениями в свободно связанной среде, используя такие стандартные протоколы, как HTTP, XML, XSD SOAP и WSDL. Сообщения могут быть структурированы и типизированы или иметь свободный формат. Поскольку основой Веб-служб служат стандартные протоколы, их приложения могут обмениваться данными с самыми разными реализациями, платформами и устройствами [2].

    Вместо того чтобы каждый раз ради отправки или получения данных посылать или получать всю страницу целиком, можно сделать так, чтобы клиентский код JavaScript обращался к Веб-службе, получал необходимые данные и сам же их отображал. Этот подход серьезно сокращает трафик между сервером и клиентом и снижает нагрузку на сервер, так как теперь его основными обязанностями является манипулирование данными.

    При этом, однако, многократно возрастает нагрузка на клиент. Это может привести к тому, что данный подход станет неприменим в случаях, когда у клиентов слабые компьютеры или на них установлены старые браузеры, плохо поддерживающие JavaScript.

    14.1.2. Веб-службы в ASP.NET

    Веб-службы можно создавать, используя архитектуру ASP.NET, что позволяет им использовать разнообразную функциональность платформы .NET Framework: например, проверку подлинности, кэширование и управление состоянием [3]. Поскольку основой для Веб-сервисов в управляемом коде служат ASP.NET и платформа .NET Framework, разработчики могут сосредоточиться на создании Веб-служб или на их использовании, не тратя времени на разработку инфраструктуры.

    При работе с Веб-сервисами существуют две фундаментальные роли:

  • Создание Веб-сервиса – при создании XML Веб-службы создается приложение, которое предоставляет некую функциональность клиентам этой XML Веб-службы.
  • Обращение к Веб-сервису – при обращении к Веб-сервису клиентское приложение находит Веб-службу, создает ссылку на нее и использует ее функциональные возможности. Обычно клиентом XML Веб-службы является приложение, способное отправлять, принимать и обрабатывать сообщения, отправляемые Веб-службе и получаемые от нее. Тем не менее, минимальное требование заключается в том, что клиент должен быть способен отправлять сообщения Веб-службам. Это требование относится ко всем приложениям, работающим под управлением платформы .NET Framework.
  • 14.1.3. Доступ к Веб-сервисам в управляемом коде

    ASP.NET позволяет создавать Веб-сервисы, к которым можно обращаться из клиентского сценария страниц. Страницы взаимодействуют с сервером посредством уровня связи, использующего технологию AJAX для вызова Веб-служб. Обмен данными между клиентом и сервером осуществляется асинхронно, как правило, в формате JSON.

    14.1.3.1. Взаимодействие клиент-сервер для клиентов AJAX

    На Веб- страницах с поддержкой технологии AJAX браузер отправляет первый запрос этой страницы на сервер, а затем выполняет последовательные асинхронные запросы данных к Веб-сервисам. Клиентские элементы взаимодействия представлены в форме загруженных с сервера прокси-классов и основной библиотеки клиентских сценариев. К серверным элементам взаимодействия относятся обработчики и пользовательские службы. На рис. 14.1 показаны элементы, которые участвуют во взаимодействии между клиентом и сервером.

    (рис 14.1) Взаимодействие клиент-сервер

    14.1.3.2. Клиентская архитектура технологии AJAX

    Браузер вызывают методы Веб-сервисов с помощью прокси-классов [4, 5]. Прокси-класс – это сценарий, который автоматически создается сервером и загружается в браузер во время загрузки страницы. Прокси-класс предоставляет клиентский объект, который позволяет вызвать реализованный в Веб-сервисе методы.

    Чтобы вызвать метод Веб-сервиса, клиентский сценарий вызывает соответствующие методы прокси-класса. Эти вызовы выполняются асинхронно посредством объекта XMLHTTP.

    Код в прокси-классах скрывает сложность XMLHTTP [6] и различия между браузерами. Он упрощает клиентский сценарий, необходимый для вызова Веб-сервиса.

    Существует два подхода к вызовам методов Веб-сервиса.

  • Вызов Веб-сервиса с помощью команды POST HTTP. POST -запрос состоит из основной части, содержащей данные, которые обозреватель отправляет на сервер. Этот запрос не имеет ограничений по размеру. Поэтому можно использовать POST -запрос, если размер данных превышает внутреннее ограничение по размеру для запроса GET. Клиент сериализует запрос в формат JSON и отправляет его на сервер в качестве данных POST. Сервер выполняет десериализацию данных из формата JSON в типы .NET Framework и выполняет фактический вызов службы. Во время ответа сервер сериализует возвращаемые значения и передает их обратно клиенту, который выполняет их десериализацию в объекты JavaScript для обработки.
  • Вызов Веб-служб с помощью команды GET HTTP. Этот вызов аналогичен POST -запросу, однако существует несколько отличий:
  • клиент использует строку запроса для отправки параметров на сервер;
  • GET -запрос может вызвать только метод Веб-сервиса, помеченный атрибутом ScriptMethodAttribute ;
  • размер данных ограничивается длиной URL-адреса, разрешенной браузером.
  • На рис. 14.2 показана клиентская архитектура технологии AJAX в ASP.NET.

    (рис 14.2) Клиентская архитектура AJAX

    К элементам клиентской архитектуры относятся уровень взаимодействия Веб-сервиса и загруженные прокси-классы для используемых на странице служб. Этот уровень представляет собой библиотеку типов клиентского сценария, которые позволяют клиенту взаимодействовать со службами на сервере. Они также освобождают клиентские приложения от необходимости установки и поддержания асинхронной связи между клиентом и сервером. Они инкапсулируют объект XMLHTTP браузера, который предоставляет возможность асинхронного взаимодействия, и предоставляют клиентским приложениям независимость от браузера. Ниже приведены основные элементы уровня связи Веб-служб:

  • WebRequest.Предоставляет функциональные возможности клиентского сценария для создания Веб-запроса.
  • WebRequestManager. Управляет потоком Веб-запросов, направленных объектом Sys.Net.WebRequest соответствующему объекту-исполнителю.
  • XmlHttpExecutor.Выполняет асинхронные сетевые запросы, используя поддержку XMLHTTP Веб-обозревателем.
  • JSON - сериализация. Выполняет сериализацию объектов JavaScript в формат JSON. Десериализацию можно выполнить с помощью функции eval JavaScript.
  • По умолчанию сериализация выполняется в формат JSON, однако отдельные методы Веб-сервисов и Веб- страниц ASP.NET могут возвращать значения в других форматах, например XML. Формат сериализации, используемый методом, можно указать с помощью атрибутов. Например, для службы ASMX можно задать атрибут ScriptMethodAttribute, предписывающий методу Веб-сервиса возвращать данные в формате XML, как показано в следующем примере:

    [ScriptMethod(ResponseFormat.Xml)]

    14.1.3.3. Серверная архитектура AJAX

    На рис. 14.3 показана серверная архитектура AJAX, включающая элементы, обеспечивающие взаимодействие с клиентскими приложениями.

    (рис 14.3) Серверная архитектура AJAX

    Элементы серверной архитектуры включают уровень связи Веб-служб, обработчик HTTP-данных и классы сериализации, пользовательские службы, методы страниц и службы приложений. На рисунке показаны следующие отдельные элементы:

  • Пользовательские Веб-службы. Предоставляют функциональную возможность службы, реализуемую пользователями, и возвращают соответствующий ответ клиентскому приложению. Пользовательскими Веб-службами могут быть службы ASP.NET или WCF. Уровень связи Веб-служб автоматически создает прокси-классы клиентского сценария, которые могут быть вызваны асинхронно из клиентского сценария.
  • Методы страниц. Этот компонент позволяет вызывать метод страницы ASP.NET так, как если бы он был методом Веб-службы. Методы страниц должны быть определены на странице, с которой осуществляется вызов метода страницы.
  • Служба проверки подлинности. Служба проверки подлинности создает прокси-класс проверки подлинности, который позволяет пользователю осуществлять вход и выход из системы с помощью клиентского сценария JavaScript.
  • Служба ролей. Служба ролей создает прокси-класс ролей, который позволяет осуществлять доступ из клиентского сценария JavaScript к сведениям о ролях для текущего пользователя, прошедшего проверку подлинности.
  • Служба профилей. Служба профилей создает прокси-класс профилей, который позволяет клиентскому сценарию JavaScript получать и задавать свойства для пользователя, связанного с текущим запросом.
  • JSON - сериализация. Серверный компонент JSON - сериализации позволяет выполнять настраиваемую сериализацию и десериализацию общих типов .NET Framework в формат JSON и из него.
  • XML- сериализация. Уровень связи Веб-служб поддерживает XML- сериализацию для SOAP-запросов к Веб-службам и возвращения типов XML по JSON -запросам к Веб-службам.
  • 14.1.4. Создание Веб-сервиса

    Веб-сервисы создаются в управляемом коде с помощью платформы ASP.NET. Сначала создается файл с расширением ASMX [7]. Этот файл содержит класс, помеченный атрибутом WebService, который служит для обозначения открытого класса, реализующего логику Веб-сервиса. Этот класс Веб-сервиса содержит один или несколько открытых методов, подлежащих публикации в составе Веб-сервиса. Перед этими методами ставится атрибут WebMethod. Среда ASP.NET отвечает за реализацию инфраструктуры публикации этих методов в составе Веб-сервиса.

    Веб-сервисы состоят из двух частей: точка входа Веб-сервиса и код, который реализует его функциональность. В ASP.NET точкой входа для Веб-сервиса служит текстовый ASMX-файл. Он ссылается на код в предкомпилированных сборках, файл с выделенным кодом, или на код, содержащийся в самом ASMX-файле.

    При построении Веб-сервиса в управляемом коде среда ASP.NET автоматически обеспечивает инфраструктуру и обрабатывает запросы и ответы Веб-сервиса, в том числе занимаясь разбором и формированием сообщений SOAP.

    14.1.4.1. Директива обработки WebService

    В начале ASMX- страницы находится директива обработки WebService, атрибуты которой содержат сведения о реализации Веб-сервиса. Эта директива предоставляет среде ASP.NET необходимые сведения, например, о том, какой класс реализует функциональность. Ниже приведен пример директивы обработки WebService:

    <%@ WebService Language="c#" Codebehind="Service1.asmx.cs"
        Class="WebService1.Service1" %>

    Атрибут Language указывает, какой язык программирования использовался для разработки сервиса. Веб-сервис можно разрабатывать на любом языке, совместимом с .NET – например, на Visual Basic .NET или на Visual C#.

    При создании сервиса в управляемом коде с использованием Visual Studio .NET ее реализация находится в файле с выделенным кодом. Файл с выделенным кодом связывается с ASMX- страницей посредством атрибута Codebehind.

    При использовании шаблона сервиса ASP.NET атрибут Class указывает, какой класс в файле с выделенным кодом реализует функциональные возможности сервиса.

    Клиенты Веб-сервиса – это компоненты, службы и приложения, ссылающиеся на сервис и использующие его. Стоит отметить, что это не обязательно клиентские приложения – во многих случаях клиентами служб могут быть другие Веб-приложения или даже другие службы.

    14.1.5. Выполнение HTTP-запросов из клиентского сценария AJAX

    Прежде чем обсуждать, каким образом можно использовать прокси-классы для вызова Веб-методов, рассмотрим, как можно решить ту же задачу при помощи класса WebRequest. Этим может потребоваться, если необходимо управлять уровнем связи или выполнять проверку данных, отправляемых на или с сервера, или если заранее не известны параметры самого сервиса.

    В следующем примере показаны способы использования объекта WebRequest для реализации Веб-запросов GET и POST при подключении к указанным URL-адресам (конечные точки HTTP):

    var resultElement;
    function pageLoad()
    {
        resultElement = $get("ResultId");
    }
    // Эта функция вызывает GET запрос.
    function GetWebRequest()
    {
        // Создаем объект WebRequest.
        var wRequest = new Sys.Net.WebRequest();
        // Указываем адрес.      
        wRequest.set_url("getTarget.htm");
        alert("Target Url: getTarget.htm");
        // Указываем команду.
        wRequest.set_httpVerb("GET");
        // Указываем-callback функцию.
        wRequest.add_completed(OnWebRequestCompleted);
        // Очищаем содержимое элемента, который будет использоваться для вывода результата.
        resultElement.innerHTML = "";
        // Вызываем запрос.
        wRequest.invoke();  
    }
    // Эта функция вызывает POST запрос.
    function PostWebRequest()
    {
        // Создаем объект WebRequest.
        var wRequest = new Sys.Net.WebRequest();
        // Указываем адрес.      
        wRequest.set_url("postTarget.aspx");
        alert("Target Url: postTarget.aspx");
        // Указываем команду.
        wRequest.set_httpVerb("POST");
        // Указываем обработчик запроса.
        wRequest.add_completed(OnWebRequestCompleted);
        // Указываем тело POST.
        var requestBody = "Message=Привет, Мир!";
        wRequest.set_body(requestBody);
        wRequest.get_headers()["Content-Length"] = requestBody.length;
        // Очищаем содержимое элемента, который будет использоваться для вывода результата.   
        resultElement.innerHTML = "";
        // Вызываем запрос.
        wRequest.invoke();              
    }
    // Эта функция вызывается асинхронно, когда завершится запрос.
    function OnWebRequestCompleted(executor, eventArgs) 
    {    
        if(executor.get_responseAvailable()) 
        {
            // Отобразим статус запроса. 
           resultElement.innerHTML +=
              "Status: [" + executor.get_statusCode() + " " + 
                        executor.get_statusText() + "]" + "<br/>";
            // Отобразим заголовки запроса.
           resultElement.innerHTML += "Headers: ";
           resultElement.innerHTML += executor.getAllResponseHeaders() + "<br/>";
            // Отобразим тело запроса.
           resultElement.innerHTML += "Body:";
          if(document.all)
            resultElement.innerText += executor.get_responseData();
          else
            resultElement.textContent += executor.get_responseData();
        }
    }
    if (typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В табл. 11.1 перечислены основные классы, связанные с Веб-сервисами, которые можно вызвать из клиентского сценария.

    Пространства имен для клиента
    Имя Описание
    Пространство имен Sys.Net Содержит классы, управляющие взаимодействием между клиентскими приложениями AJAX в ASP.NET и Веб-службами на сервере. Пространство имен Sys.Net является частью Microsoft AJAX (библиотека).
    Пространство имен Sys.Serialization Содержит классы, связанные с сериализацией данных для клиентских приложений AJAX в ASP.NET.
    Пространство имен Sys.Services Содержит типы, которые обеспечивают доступ сценариев клиентских приложений AJAX в ASP.NET к службе проверки подлинности, службе профилей и другим службам приложения ASP.NET. Пространство имен Sys.Services является частью Microsoft AJAX (библиотека).

    В табл. 11.2 перечислены основные классы, связанные с Веб-сервисами, которые можно вызвать на сервере.

    Пространства имен для сервера
    Имя Описание
    System.Web.Script.Serialization Содержит классы, обеспечивающие сериализацию в формат JSON и десериализацию из этого формата для управляемых типов. Также обеспечивает расширенные возможности для настройки сериализации.

    14.1.6. Предоставление доступа к Веб-службам в клиентском сценарии

    Более удобный способ вызова Веб-сервисов заключается в использовании прокси-классов [8]. Как уже отмечалось, ASP.NET автоматически создаются прокси-классы JavaScript для Веб-служб. Эти прокси-классы являются производными от класса Sys.Net.WebServiceProxy. Чтобы вызвать метод Веб-сервиса, следует вызвать соответствующий метод прокси-класса JavaScript.

    Из сценария можно предоставить доступ к Веб-службе, которая представлена файлом с расширением ASMX и класс которой определен с использованием атрибута ScriptServiceAttribute. Отдельные методы, вызываемые из сценария, должны быть дополнены атрибутом WebMethodAttribute.

    В следующем примере показан порядок использования этих атрибутов в коде Веб-службы:

    [ScriptService]
    public class SimpleWebService : System.Web.Services.WebService
    {
    [WebMethod]
    public string EchoInput(String input)
    {
        // Здесь расположен код метода.
    }
    }

    Чтобы разрешить вызов Веб-служб из сценария, следует зарегистрировать обработчик HTTP ScriptHandlerFactory в файле Web.config приложения. Этот обработчик используется для обработки вызовов Веб-служб из сценария. В следующем примере показан элемент файла Web.config, определяющий добавление обработчика:

    <system.web>
      <httpHandlers>
        <remove verb="*" path="*.asmx"/>
        <add verb="*" path="*.asmx"
          type="System.Web.Script.Services.ScriptHandlerFactory"
           validate="false"/>
      </httpHandlers>
    <system.web>

    Вызовы Веб-служб, выполненные не из сценария ASP.NET AJAX, обработчик ScriptHandlerFactory делегирует используемому по умолчанию обработчику, в котором вместо формата JSON используется SOAP. Делегирование выполняется автоматически. Какие-либо дополнительные действия следует выполнять только в том случае, если требуется запретить использование протокола SOAP в Веб-службах. В этом случае в файле Web.config необходимо определить следующие параметры конфигурации:

    <system.web>
      <webServices>
        <protocols>
          <clear/>
        </protocols>
      </webServices>
    </system.web>

    14.1.6.1. Предоставление доступа к сервисам из клиентского сценария на Веб-странице ASP.NET

    Чтобы разрешить вызов Веб-служб (ASMX) из клиентского сценария на Веб- странице ASP.NET, необходимо добавить на страницу элемент управления ScriptManager. Чтобы определить ссылку на Веб-службу, необходимо добавить дочерний элемент asp:ServiceReference к элементу управления ScriptManager. После этого необходимо установить URL-адрес Веб-службы в качестве значения атрибута ссылки на сервер path. Объект ServiceReference определяет необходимость создания прокси-класса JavaScript для вызова указанной Веб-службы в ASP.NET.

    Добавим в начало страницы первым элементом формы объект ScriptManager:

    <asp:ScriptManager ID="Scriptmanager1" runat="server">
    <Services>
        <asp:ServiceReference Path="~/SimpleWebService.asmx" />
    </Services>
    </asp:ScriptManager>

    Объект ServiceReference может ссылаться на Веб-службу только в том же домене, в котором находится страница. Путь Веб-службы может быть относительным, относительным к приложению, относительным к домену или абсолютным. При использовании абсолютного пути следует убедиться, что он указан в том же домене.

    При отображении страницы, содержащей этот элемент управления ScriptManager, для Веб-службы SimpleWebService.asmx создается прокси-класс JavaScript. В прокси-классе реализуются методы, соответствующие методам Веб-службы SimpleWebService.asmx. Страница также содержит прокси-классы JavaScript, соответствующие типам данных сервера, которые используются в качестве входных параметров или возвращаемых значений методов Веб-служб. Это позволяет написать клиентский сценарий, инициализирующий эти параметры, и передать их вызову метода.

    Свойство InlineScript объекта ServiceReference определяет порядок включения прокси-класса JavaScript на странице. Если для свойства InlineScript установлено значение false (используется по умолчанию), получение сценария прокси-класса выполняется с помощью отдельного запроса. Этот параметр рекомендуется использовать в том случае, если несколько страниц ссылаются на одну службу и включено кэширование.

    Если для свойства InlineScript установлено значение true, сценарий прокси-класса включается на страницу в качестве встроенного блока сценария. Это позволяет повысить производительность за счет сокращения числа сетевых запросов. Это особенно актуально при наличии на странице большого числа ссылок на службы и отсутствии ссылок на эту службу на других страницах. Если для свойства InlineScript установлено значение true, необходимо использовать относительный путь. Путь, задаваемый относительно домена, должен содержать ссылку на то же Веб-приложение.

    Вызов метода Веб-сервисы из сценария осуществляется асинхронным способом. Чтобы получить возвращаемое значение или определить факт возврата запроса, следует определить функцию обратного вызова при успешном выполнении запроса. Функция обратного вызова вызывается при успешном выполнении запроса и содержит значение (если есть), возвращаемое в результате вызова метода Веб-сервиса. Также можно определить функцию обратного вызова при неудачном завершении запроса, используемую для обработки ошибок. Кроме того, можно передать в функции обратного вызова сведения о контексте пользователя. Если метод Веб-сервиса возвращает сложный тип, функция обратного вызова при успешном выполнении запроса принимает возвращаемое значение в форме объекта JavaScript, соответствующего типу сервера.

    14.1.6.2. Примеры

    Рассмотрим большинство указанных выше аспектов использования Веб-сервисов на примерах.

    В первом примере показан метод Веб-сервиса, возвращающий сложный тип. В рамках Веб-сервиса определен пользовательский сериализуемый тип ColorObject, а также метод GetDefaultColor, который возвращает некоторый объект с цветом. Клиент после полной отрисовки страницы вызывает сервис, получает значение цвета и сообщение, а также выводит сообщение от сервиса на экран, выделяя его переданным в формате RGB цветом.

    В первой части примера показана страница, на которой выполняется вызов сервиса с использованием клиентского сценария. Она имеет ссылку на файл . asmx, который содержит описание сервиса, и на файл HandleColor.js, который содержит JavaScript -код, вызывающий веб-сервисы:

    <%@ Page Language="C#" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html  >
        <head id="Head1" runat="server">
            <title>Получение составного типа данных</title>
            <style type="text/css">
                body {  font: 11pt Trebuchet MS;
                        font-color: #000000;
                        padding-top: 72px;
                        text-align: center }
    
                .text { font: 8pt Trebuchet MS }
            </style>
        </head>
        <body>
            <h2>Получение составного типа данных</h2>
            <form id="form1" runat="server">
                <asp:ScriptManager runat="server" ID="scriptManager">
                    <Services>
                        <asp:ServiceReference  Path="HandleColor.asmx" />
                    </Services>
                    <Scripts>
                        <asp:ScriptReference Path="HandleColor.js" />
                    </Scripts>
                </asp:ScriptManager>  
                <table style="font-size:12px">
                    <tr>
                        <td>Цвет по умолчанию для Web-сервиса:</td>
                        <td>
                         <button id="Button1" 
                            onclick="GetDefaultColor(); return false;">Получить цвет по умолчанию</button>
                        </td>
                    </tr>
                </table> 
               <hr />
                <!—Отображение текущего цвета.-->
                <p>
                    <span style="background-color:Yellow">Цвет:</span>
                    <span id="ResultId"></span>
                </p>
            </form>
        </body>
    </html>

    В следующей части примера показан клиентский сценарий страницы HandleColor.js, который используется для вызова службы:

    // Эта функция извлекает значение по умолчанию для цвета
    function GetDefaultColor()  
    {
        // Вызываем нужный Веб-сервис
        Samples.AspNet.HandleColor.GetDefaultColor(SucceededCallback);  
    }
    // Это функция обратного вызова, обрабатывающая результат вызова сервиса
    function SucceededCallback(result)
    {
        // Извлечение данных, полученных от сервиса
        var message = result.message;
        var rgb = result.rgb;
        var timeStamp = result.timeStamp;
        // Преобразование RGB массива в строку
        var serverColor = rgb[0]+ rgb[1] + rgb[2];
        // Отображение результата
        var displayResult = document.getElementById("ResultId");
        displayResult.style.color = "yellow";
        displayResult.style.fontWeight = "bold";
        if (document.all) 
            displayResult.innerText = message + " " + timeStamp;
        else
           // Firefox
           displayResult.textContent = message + " " + timeStamp;
        displayResult.style.backgroundColor = "#" + serverColor;
    }
    if (typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В следующей части примера показана вызываемая на странице служба HandleColor.asmx, которая реализуется классом Samples.AspNet.HandleColor:

    <%@ WebService Language="C#" Class="Samples.AspNet.HandleColor" %>
    using System;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Script.Services;
    using System.Web.Script.Serialization;
    namespace Samples.AspNet
    {
        // Определяем объект "Цвет" для общения с клиентом
        public class ColorObject
        {
            public string message = "Цвет по умолчанию – синий.";
            public string[] rgb = new string[] { "00", "00", "FF" }; 
            public string timeStamp;
        }
        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [GenerateScriptType(typeof(ColorObject))]
        [ScriptService]
        public class HandleColor : System.Web.Services.WebService
        {
            [WebMethod]
            public ColorObject GetDefaultColor()
            {
                // Создаем новый объект.
                ColorObject co = new ColorObject();
                // Указывает текущее время.
                co.timeStamp = DateTime.Now.ToString();
                return co;         
            }
        }
    }

    Результат работы разработанной страницы представлен на рис. 14.4.

    (рис 14.4) Работа Веб-сервиса, возвращающего составной тип данных

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

    В первой части примера показана страница, на которой выполняется вызов сервиса с использованием клиентского сценария. Она имеет ссылку на файл HandleColor.asmx, который содержит описание сервиса, и на файл HandleColor.js, который содержит JavaScript -код, вызывающий веб-сервисы:

    <%@ Page Language="C#" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
        <head id="Head1" runat="server">
            <title>Получение составного типа данных</title>
            <style type="text/css">
                body
                {
                    font: 11pt Trebuchet MS;
                    font-color: #000000;
                    padding-top: 72px;
                    text-align: center;
                }
                .text
                {
                    font: 8pt Trebuchet MS;
                }
            </style>
        </head>
        <body>
            <h2>Получение составного типа данных</h2>
            <form id="form1" runat="server">
                <asp:ScriptManager runat="server" ID="scriptManager">
                    <Services>
                        <asp:ServiceReference Path="HandleColor.asmx" />
                    </Services>
                    <Scripts>
                        <asp:ScriptReference Path="HandleColor.js" />
                    </Scripts>
                </asp:ScriptManager>
                <table style="font-size: 12px;">
                    <tr align="center">
                        <td class="text">
                            Изменить цвет:
                        </td>
                        <td>
                            <select id="ColorSelectID" onchange="OnChangeDefaultColor(this);" runat="server">
                            </select>
                        </td>
                    </tr>
                </table>
                <hr />
                <!—Отображение текущего цвета.-->
                <span style="background-color: Yellow">Цвет:</span>
                <span id="ResultId"></span>
            </form>
        </body>
    </html>

    В следующей части примера показан клиентский сценарий страницы HandleColor.js, который используется для вызова службы:

    // Цвет по умолчанию для сервиса.
    var defaultRgb;
    // Элемент, отвечающий за отображение результата.
    var displayResult;
    // Получаем список доступных цветов и цвет по молчанию.
    function GetServerColors() {
        // Получаем цвет по умолчанию.
        Samples.AspNet.HandleColor.GetDefaultColor(
            SucceededCallback, FailedCallback);
        // Получаем список доступных цветов.
        Samples.AspNet.HandleColor.GetColorList(
            SucceededCallback, FailedCallback);
    }
    // Эта функция отправляет на сервер выбранный пользователем объект цвет в элементе select
    function OnChangeDefaultColor(comboObject) {
        // Создаем экземпляр объекта.
        var color = new Samples.AspNet.ColorObject();
        // Поучаем выбранный пользователем цвет
        var selectionIndex = comboObject.selectedIndex;
        var selectedColor = comboObject.options[selectionIndex].text;
        // Получаем RGB представление цвета.
        var selectionValue = comboObject.value;
        // И превращаем его в массив.
        var colorArray = selectionValue.split(",");
        // Присваиваем значения полей объекта
        color.message = "Новый цвет по умолчанию " + selectedColor + ".";
        color.rgb = colorArray;
        // Вызываем сетод Веб-сервиса для изменения цвета.
        Samples.AspNet.HandleColor.ChangeDefaultColor(
            color, SucceededCallback, FailedCallback);
    }
    // Эта фунуция обрабатывает результат работы методов сервиса
    function SucceededCallback(result, userContext, methodName) {
        switch (methodName) {
            case ("GetColorList"):
                {
                    // Получаем элемент select.
                    var selectObject = document.getElementById("ColorSelectID");
                    var i = 0;
                    // Пробегаемся по списку для заполнению select данными
                    for (var j=0; j < result.length; j++) {
                        var item = result[j];
                        var option = new Option(item.name, item.rgb);
                        selectObject.options[i] = option;
                        // Устанавливаем значение по умолчанию.
                        if (item == defaultRgb[0] + defaultRgb[1] + defaultRgb[2])
                            selectObject.options[i].selected = true;
                        i++;
                    }
                    break;
                }
            default:
                {
                    var message = result.message;
                    defaultRgb = result.rgb;
                    var timeStamp = result.timeStamp;
                    var serverColor = defaultRgb[0] + defaultRgb[1] + defaultRgb[2];
                    displayResult.style.color = "yellow";
                    displayResult.style.fontWeight = "bold";
                    if (document.all)
                        displayResult.innerText = message + " " + timeStamp;
                    else
                    // Firefox
                        displayResult.textContent = message + " " + timeStamp;
    
                    displayResult.style.backgroundColor = "#" + serverColor;
                    break;
                }
        }
    }
    // Функция обратного вызова, используемая в случае, если Веб-сервис отработал с ошибками
    function FailedCallback(error, userContext, methodName) {
        if (error !== null) {
            displayResult.innerHTML = "Произошла ошибка: " + error.get_message();
        }
    }
    // Получаем список цветов и цвет по умолчанию
    function pageLoad() {
        displayResult = document.getElementById("ResultId");
        GetServerColors();
    }
    if (typeof (Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

    В следующей части примера показана вызываемая на странице служба HandleColor.asmx, которая реализуется классом Samples.AspNet.HandleColor:

    <%@ WebService Language="C#" Class="Samples.AspNet.HandleColor" %>
    using System;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Script.Services;
    using System.Web.Script.Serialization;
    using System.Collections.Generic;
    
    namespace Samples.AspNet
    {
        // Определяем объект цвет для "общения с клиентом"
        public class ColorObject
        {
            public string message;
            public string[] rgb;
            public string timeStamp;
            public ColorObject()
            {
                this.message = "Цвет по умолчанию – красный.";
                this.rgb = new string[] { "FF", "00", "00" };
                this.timeStamp = DateTime.Now.ToString();
            }
        }
        // вспомогательный класс для отображения списка цветов
        public class ColorHelper
        {
            public string name;
            public string rgb;
            public ColorHelper()
            {
            }
        }
        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [GenerateScriptType(typeof(ColorObject))]
        [GenerateScriptType(typeof(ColorHelper))]
        [ScriptService]
        public class HandleColor : System.Web.Services.WebService
        {
            [WebMethod]
            public ColorObject GetDefaultColor()
            {
                // Возвращаем новый объект.
                ColorObject co = new ColorObject();
                return co;
            }
            [WebMethod]
            public ColorObject ChangeDefaultColor(ColorObject color)
            {
                // Создаем новый объект.
                ColorObject co = new ColorObject();
                // Присваиваем переданные значения.
                co.message = color.message;
                co.rgb = color.rgb;
                // Указываем текущее время.
                co.timeStamp = DateTime.Now.ToString();
                return co;
            }
            [WebMethod]
            public List<ColorHelper> GetColorList()
            {
    
                //Создаем таблицу цветов.
                List<ColorHelper> result = new List<ColorHelper>();
                //Добавляем несколько вариантов цветов.
                result.Add(new ColorHelper() { rgb = "00,00,FF", name = "Синий" });
                result.Add(new ColorHelper() { rgb = "FF,00,00", name = "Красный" });
                result.Add(new ColorHelper() { rgb = "00,FF,00", name = "Зеленый" });
                result.Add(new ColorHelper() { rgb = "00,00,00", name = "Черный" });
                return result;
            }
        }
    }

    Результат работы сервиса и страницы представлен на рис. 14.5.

    (рис 14.5) Работа веб-сервиса, возвращающего список доступных цветов

    Представленный подход позволяет улучшить производительность приложения, снизить нагрузку на сервере и упростить работу пользователя с сайтом. При этом, очевидно, что для отображения получаемых от Веб-сервиса данных, стоит использовать не серверные компоненты ASP.NET, которые, как правило, не предназначены для обработки таких данных и не имеют соответствующего интерфейса, а рассмотренные ранее JavaScript -библиотеки, например ExtJS.

    В результате получается, что ASP.NET служит для отображения страницы и манипуляции с данными, а за отображение отвечает JavaScript. Происходит уход от компонентной разработки, понижается понятность, структурированность и повторяемость кода. Для решения этих проблем разработан ряд технологий, такие как ExrJS.Direct, Jasp и другие. Рассмотрим одну из этих технологий.

    14.1.7. Ключевые термины

    ASP.NET, .NET Framework, Веб-служба, Веб-сервис, AJAX, JSON, Сериализация, XmlHttpExecutor, WebRequestManager, WebRequest, JavaScript.

    14.2. Технология Jasp

    14.2.1. Введение

    Jasp является библиотекой для разработки Веб-приложений с использованием платформы Microsoft .NET Framework [9, 10]. Основным отличием Jasp от ASP.NET является ориентация библиотеки на клиентскую сторону, чтобы программисту было проще создавать "богатый" пользовательский интерфейс в первую очередь средствами языка JavaScript, CSS и в последнюю очередь HTML, сгенерированным на стороне сервера (как это часто бывает при разработке на ASP.NET ). Такие приложения зачастую состоят из небольшого количества страниц (чаще всего одной), и все необходимое подгружают по мере использования без перезагрузки страницы. Примером таких приложений могут п ослужить сервисы: GMail, Google Maps, Pusk.ru и другие.

    Примечание: скачать библиотеку Jasp, а также примеры и документацию можно на http://code.google.com/p/jaspproject/ или www.jaspproject.ru.

    Основной недостаток ASP.NET заключается в сложности разработки клиентских компонент (использующих JavaScript ).

    Рассмотрим простой пример: предположим, на странице необходимо вводить дату, и в целях экономии места на странице было решено сделать текстовое поле с выпадающим вниз календарем. Есть два пути решения: создать наследника UserControl, и наследника WebControl. У каждого из вариантов есть плюсы и минусы. В первом случае несомненным плюсом является возможность создания разметки, скриптов и стилей прям в ASCX-файле, но при этом данный компонент нельзя использовать в других проектах (только если копировать код из одного проекта в другой). Во втором случае нет ASCX-файла, а все дочерние компоненты приходится создавать программно. С другой стороны такой компонент можно использовать повторно.

    В обоих случаях, при реализации клиентской части компонента возникает ряд проблем:

  • всегда необходимо передавать ClietnID компонента в JavaScript -код, что конечно возможно, но не всегда удобно;
  • приходится следить за загрузкой связанных с компонентом ресурсов: изображений, стилей и вспомогательных JavaScript -файлов;
  • вместо отсутствующей клиентской части компонент есть простой набор вызовов и несколько объявлений JavaScript -функций.
  • Обобщим все возможные проблемы, при создании компонент на классическом ASP.NET:

  • Различные трудности, возникающие при связи серверной и клиентской части компонента, в результате которых зачастую на клиентской стороне получается не полноценный объект с набором свойств и методов, а набор строк JavaScript -кода, с которым, нельзя работать как с одним объектом.
  • Огромный размер ViewState. Есть плюсы его использования, но чаще всего (особенно при использовании красивых и функциональных сторонних компонент ), он достигает таких больших размеров, что из-за него страдает производительность, даже если его хранить на сервере.
  • Использование ASP.NET Ajax зачастую приводит к непредсказуемым результатам. ViewState пересылается при каждом, пусть и асинхроном PostBack'e, даже там, где это не нужно, что в свою очередь не очень хорошо сказывается на производительности;
  • Ресурсы , загружаемые с помощью WebResource.axd. Порой на одной странице их скапливается довольно много, тем самым, осложняя жизнь браузеру. К тому же не все браузеры способны одновременно загружать несколько ресурсов на страницу.
  • При большой вложенности компонент получаем огромные ClientID, что плохо сказывается на результирующем размере HTML разметки, а иногда приводит к полной неработоспособности страницы.
  • Нет возможности полноценного наследования компонент (включая клиентскую сторону или HTML разметку).
  • При создании WebControl'ов создание дочерних компонент приходится делать вручную.
  • При создании UserControl'ов, их нельзя использовать в других проектах.
  • Технология Jasp пытается решить все указанные проблемы. Компонент Jasp одновременно является как серверным (разработка в ключе классического ASP.NET ), так и клиентским ( JavaScript - компонентом ). Каждый компонент можно использовать и в других проектах (даже имеющий разметку и собственные ресурсы ) без всяких изменений. Присутствует возможность наследования, как серверной части, так и клиентских частей: JavaSctpt, CSS и разметки. Все ресурсы загружаются автоматически и по мере использования (синхронно или асинхронно). При первой загрузке страницы загружаются только необходимые ресурсы, оптимизирова нные и сгруппированные в несколько файлов. Jasp можно использовать со всеми распространенными JavaScript -библиотеками: jQuery, ExtJS, Prototype и т.п.

    14.2.2. "Hello World" на Jasp

    Создание нового приложения на Jasp ничем не отличается от создания обычного ASP.NET приложения. Можно даже не создавать новый WebSite/WebApplication, а использовать существующий. Главное, чтобы был добавлен Reference на сборку Jasp и присутствовали минимальные необходимые настройки в web.config для работы Jasp:

    <configuration>
      <configSections>
        <section name="jaspSettings" type="Jasp.Configuration.SettingsSection"/>
      </configSections>
      <jaspSettings  />
      <system.web>
        <httpHandlers>
          <add verb="*" path="*.jasp" type="Jasp.Web.HttpHandler, Jasp"/>
        </httpHandlers>
      </system.web>
      <system.webServer>
        <handlers>
          <add name="JaspCommonHandler" path="*.jasp" verb="*" type="Jasp.Web.HttpHandler, Jasp" 
           resourceType="Unspecified" preCondition="integratedMode"/>
        </handlers>
      </system.webServer>
    </configuration>
    Примечание: для работы сайта, использующего Jasp, под управлением IIS необходимо подключить ISAPI-фильтр на расширение .jasp.

    Для создания страницы необходимо создать класс, унаследованный от класса Jasp.Web.UI.Page. Имя класса должно совпадать с именем страницы.

    Создадим страницу, выводящую "Hello world!".

    using Jasp.Web.UI;
    using Jasp.Web.UI.HtmlControls;
    namespace Jasp.Examples
    {
        public class HelloWorld : Page
        {
            protected override void OnInit()
            {
                Body.Controls.Add(new Literal("Hello world!"));
            }
        }
    }

    Чтобы зайти на страницу, вводим в браузере /HelloWorld.jasp. Пока не будем углубляться в детали сгенерированного HTML, а просто убедимся, что все работает – в браузере увидим "Hello world!", и при загрузке страницы не произойдет JavaScript -ошибок.

    14.2.3. Создание страницы с разметкой, скриптом и стилями

    Рассмотрим пример создания страницы, содержащую разметку, скрипты и стили.

    Для примера, создадим страницу, текст которой будет анимирован с помощью эффектов jQuery. Для начала, добавим в проект JavaScript -файл jquery-1_2_6_min.js с библиотекой jQuery (в любое место проекта). Не забудем установить Build action в embedded resource (подробнее подключение ресурсов описывается ниже). Данный файл будет являться общим ресурсом для всех страниц или компонент.

    Далее создадим класс для страницы, назовем его Example1:

    using Jasp.Resources;
    using Jasp.Web.UI;
    namespace Jasp.Examples
    {
        [Resources("jquery-1_2_6_min.js")]
        public class Example1 : Page
        {
        }
    }

    Добавим классу атрибут ResourcesAttribute с указанием имени файла с jQuery. Теперь, при загрузке страницы, Jasp будет добавлять этот файл к скриптам страницы.

    Теперь создадим CSS файл Example1.css со стилями для текста страницы:

    .text-style
    {
      color:red;
      font-weight:bold;
      font-size:large;
    }

    Также создадим файл Example1.htm с разметкой, содержащий текст, который будет анимироваться:

    <html>
    <head>
      <title>Пример 1</title>
    </head>
    <body>
      <span id="text">Анимированный текст!</span>
    </body>
    </html>

    Теперь создадим JavaScript -файл Example1.js с кодом, который будет анимировать текст:

    {
      // функция, вызываемая при инициализации контрола
      $init: function(){
        // указатель на текущий контрол
        var self = this;
        // дожидаемся инициализации jQuery
        jQuery(document).ready(function(){
          // запускаем анимацию
          self.animateText();
        });
      },
      // функция для анимации текста
      animateText: function(){
        // указатель на текущий контрол
        var self = this;
        // производим анимацию    
        jQuery(self.$text.$dom).toggleClass('text-style').toggle(1000, function(){
          self.animateText();
        });
      }
    }

    После загрузки страницы, мы увидим мигающий текст.

    Рассмотрим, каким образом Jasp подгружает CSS и JavaScript файлы.

    14.2.4. Ресурсы в Jasp

    При загрузке страницы, Jasp определяет, какие ресурсы используются на странице: это может быть HTML-разметка, один или несколько CSS или JavaScript файлов. Jasp группирует все CSS и JavaScript файлы в один CSS и один JavaScript файл и добавляет специальные ссылки в HTML разметку, по которым они подгружаются браузером:

    <script type="text/javascript" src="?$scripts=7_5_2_3_4_6_v=-1720007920"> </script> 
    <link rel="stylesheet" type="text/css" href="?$styles=v=-1720007920" />

    По-хорошему, у всех страниц сайта должна быть одна базовая страница (аналог MasterPage ), содержащая одинаковый набор базовых стилей и скриптов. Таким образом, данные ресурсы "закэшируются" и для других страниц сайта.

    Страницы могут отличаться набором компонент, которые на них расположены. При загрузке страницы Jasp также определяет, какие ресурсы используют все компоненты, расположенные на странице. HTML каждого компонента подставляется в соответствующее место в результирующий HTML, а все CSS и JavaScript файлы, по аналогии с ресурсами страницы, объединятся в один CSS и один JavaScript файл.

    <script type="text/javascript" src="?$pageScripts=2_$r=v=-1720007920"> </script> 
    <link rel="stylesheet" type="text/css" href="?$pageStyles=2_$r=v=-1720007920" />

    У каждой страницы или компонента могут быть как личные ресурсы, которые подгружаются автоматически и могут использоваться только данным компонентом, или его наследниками, так и общие, которые могут использоваться сразу несколькими компонентами. Личные ресурсы должны называться также как имя класса компонента (без учета расширения). Естественно, ресурсы компонент, которые изначально не используются на странице, загружены не будут. Их загрузка произойдет автоматически при первом использовании.

    14.2.4.1. CSS ресурсы

    Зачастую в проектах бывает один или пару CSS файлов, в которые без разбора добавляются все стили из всего проекта, даже если стиль используется один раз для определенного элемента. Такой файл получается довольно большим и трудно поддерживаемым. В Jasp можно создать общий файл, куда поместить основные стили, а стили, специфичные для определенных компонент или страниц, помещать в личные CSS-файлы компонент.

    Необходимо отметить, что Jasp подменяет клиентские идентификаторы компонент – поэтому стили с селекторами через идентификаторы работать не будут.

    14.2.4.2. JavaScript ресурсы

    JavaScript ресурс – это текстовый файл с расширением js, содержащий JavaScript -код компонента. Данный файл должен определять один единственный объект – экземпляр компонента. В простейшем случае это пустой объект:

    {
    }

    Если в объекте определена функция $init, то она будет вызвана сразу же после вызова клиентского конструктора (конструкторы создаются автоматически и подменять или модифицировать их нельзя):

    {  $init: function(){
        // код инициализации
      }
    }

    Также в контексте данного объекта могут быть определены и другие функции компонента.

    14.2.4.3. HTML ресурсы

    HTML ресурс – это текстовый файл с расширение html или htm, содержащий HTML разметку компоненты или страницы. Данный файл должен удовлетворять синтаксическим требованиям XML, но при этом, может не являться XHTML-документом. Также в этом файле могут встречаться вставки для создания экземпляров дочерних компонент.

    14.2.4.4. Бинарные ресурсы

    Также Jasp поддерживает в качестве ресурсов изображения и другие бинарные файлы. При первом старте сайта, такие файлы будут "распакованы" из всех сборок сайта по умолчанию в папку \deploy\<имя сборки> в корне сайта. В HTML, CSS и JavaScript можно использовать специальную конструкцию ~<имя сборки>/<путь к ресурсу>, например в CSS можно написать:

    body {  
        background-image: url('~Jasp.Examples/Files/Images/background.gif')
    }

    В результирующий CSS попадет отформатированная ссылка на ресурс, содержащая абсолютный путь до файла (если сайт работает в виртуальной директории, то будет подставлено и имя директории):

    body {  
        background-image: url('/deploy/Jasp.Examples/Files/Images/background.gif')
    }
    Примечание: Все вышеупомянутые типы ресурсов должны быть настроены как embedded resource, быть в кодировке UTF-8 и не содержать в названии более одной точки.

    При разработке, нет необходимости каждый раз после изменения файла делать перекомпиляцию проекта – Jasp сам умеет следить за изменениями в ресурсах и автоматически подхватывать их.

    Конечно, же, никто не мешает использовать обычный подход к ресурсам, т.е. просто хранить CSS/ JavaScript /HTML и т.п. файлы, но тогда теряется ряд преимуществ, которые предоставляет Jasp: автоматическая загрузка стилей, скриптов по мере использования; минимизация скриптов и разметки; GZIP для страниц, стилей и разметки; возможность использования ресурсов и компонент, использующих их, из других проектов.

    Зачастую, разные проекты на нужды одной компании имеют похожий интерфейс. Используя Jasp, можно с легкостью создать библиотеку компонент и ресурсов, содержащую базовые страницы ( MasterPage с layout 'ом по умолчанию, страницы ошибок, 404, 500 и т.п.), компоненты, изображения, стили, скрипты, и другие общие для проектов ресурсы. Например, используя библиотеку ExtJS сразу в нескольких проектах, достаточно сложно поддерживать одинаковую версию ExtJS библиотеки (включая все изображения, скины, плагины и bugfix'ы), т.к. это огромный набор файлов. Используя Jasp, можно с легкостью создать сборку, содержащую все необходимые ресурсы. Это решит проблему синхронизации версий всех файлов между проектами, а также возможные проблемы обновления при выходе новой версии библиотеки. Кроме того, как уже говорилось ранее, Jasp оптимизирует всю работу с ресурсами, начиная от автоматической загрузки, кончая сжатием и клиентской оптимизацией.

    14.2.5. Страницы и компоненты

    14.2.5.1. Создание страниц

    Для создания страницы, необходимо создать класс, наследующий от класса Jasp.Web.UI.Page, после чего, Jasp будет обрабатывать запрос к одноименной странице. Вложенность страниц в папки производится через пространства имен, т.е. достаточно создать папку в проекте, после чего все классы, создаваемые в папке, будут находиться в пространстве имен новой вложенности (по умолчанию в Visual Studio), также как и путь к странице. Тут необходимо учесть, что за базовое пространство имен (от которых начинается нулевой уровень вложенности страниц ), берется имя сборки. Например, для сборки с именем Jasp.Examples, страница с классом Jasp.Examples.Example1 будет обрабатываться по запросу /Example1.jasp, а с классом Jasp.Examples.OldExamples.Example2 по адресу /OldExamples/Example2.jasp. Естественно, имена страниц не чувствительны к регистру.

    14.2.5.2. Создание компонент

    Для создания компонент, необходимо создать класс, наследованный от класса Jasp.Web.UI.UserControl. В Jasp страница является обычным компонентом, так что все функции и возможности, доступные компоненту, как на серверной, так и на клиентской стороне доступны также и странице. Далее, все действия, описываемые для компонент, справедливы также и для страниц.

    Каждому компоненту, за исключением страницы, можно создать сокращенное наименование ( alias, алиас) с помощью атрибута AliasAttribute, чтобы не указывать зачастую длинные полные имена, и использовать его в JavaScript -коде или разметке.

    Как говорилось ранее, в Jasp каждому компоненту можно прикреплять ресурсы, которые автоматически будут загружаться, при его использовании. Для автоматической подгрузки общих ресурсов компонента, достаточно добавить атрибут ResourcesAttribute, в котором перечислить названия общих JavaScript или CSS ресурсов.

    Также важно отметить, что на клиентской стороне Jasp не создает лишних глобальных переменных – вся работа происходит внутри одного глобального объекта Jasp. Это позволяет избежать конфликтов с другими библиотеками (в целях оптимизации, Jasp все же создает сокращенные глобальные "алиасы" для компонент, которые имеют формат $j<системный номер компонента>, но вероятность того, что данные "алиасы" могут вызвать конфликт с другими JavaScript -библиотеками крайне мала).

    Создать экземпляр компонента можно следующими методами:

  • программно, на серверной стороне;
  • через разметку, на серверной стороне;
  • синхронно или асинхронно на клиентской стороне.
  • При использовании первых двух вариантов, ресурсы компонент загружаются при загрузке страницы. При использовании последнего варианта, для всех ресурсов компонента идет один запрос к серверу, который возвращает необходимые данные и ресурсы (скрипты, разметку и стили). В Jasp у каждого компонента есть коллекция с дочерними компонентами как на серверной стороне (свойство Controls ), так и на клиентской (массив $controls ). Также на клиентской стороне у родительского компонента присутствуют свойства с идентификаторами каждого дочернего компонента (начиная со специального символа $ ).

    14.2.5.2.1. Создание экземпляров компонент программно

    Для создания компонента программно, необходимо создать новый экземпляр класса и добавить его с помощью метода Add в коллекцию Controls родительского компонента или страницы. Правильнее всего, это делать в переопределенном методе OnInit:

    protected override void OnInit()
    {
      Controls.Add(new MyUserControl());
    }

    14.2.5.2.2. Создание экземпляров компонент через разметку

    Для создания компонент в разметке достаточно создать тег, с полным именем компонента (включая пространство имен) и указанием атрибутов, которые он поддерживает:

    <jasp.examples.controls.myusercontrol id="control1" title="Контрол 1" />

    14.2.5.2.3. Создание экземпляров компонент на JavaScript

    Для создания компонент на JavaScript, достаточно создать новый экземпляр с помощью оператора new, и передать необходимые параметры первым аргументом:

    {
        $init: function() {
            var control = new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1'
            });
            // производим необходимые действия с контролом
        }
    }

    Если в аргументе задано свойство callback (типа function ), то компонент будет создан асинхронно, и callback будет вызван после создания компонента на сервере:

    {
        $init: function() {
            new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1',
                callback: function(control) {
                    // производим необходимые действия с контролом
                }
            });
        }
    }

    Если в аргументе задано свойство arg (типа array ), то при создании компонента будет вызван серверный конструктор с заданными параметрами из arg (поддерживается перегрузка только по числу параметров, по типу параметров перегрузка не поддерживается). По своей сути arg – это аргумент конструктора. При этом на сервере все дочерние компоненты, которые будут созданы при запросе (из разметки или программно), будут возвращены на клиент и также будут являться дочерними для создаваемого клиентского компонента. Вызываемый конструктор должен быть помечен атрибутом JsonRpcMethodAttribute.

    Например, у нас есть следующий компонент:

    public class MyUserControl : UserControl
    {
        private int _x, _y;
        public MyUserControl()
        {
        }
        [JsonRpcMethod]
        public MyUserControl(int x, int y)
        {
            _x = x;
            _y = y;
        }
        protected override void OnLoad()
        {
            Title = string.Format("Сумма {0}", _x + _y);
        }
        public string Title
        {
            get { return (string)Data["title"]; }
            set { Data["title"] = value; }
        }
        ///// <summary>
        ///// Данная реализация свойства работает с компонентом title, определенным в разметке
        ///// </summary>
        //public string Title
        //{
        //    get { return ((HtmlControl)GetById("title")).InnerText; }
        //    set { ((HtmlControl)GetById("title")).InnerText = value; }
        //}
    }
    Примечание: в последней версии библиотеке атрибут JsonRpcMethodAttribute был удален. Теперь все публичные методы доступны из JavaScript -кода.

    Одновременно создание компонента на сервере и на клиенте производится следующим образом:

    {
        $init: function() {
            var control = new Jasp.Examples.Controls.MyUserControl({
                title: 'Контрол 1',
                arg: [3, 2]
            });
            // производим необходимые действия с контролом
        }
    }

    Как видно из примера, на сервере происходит сложение двух чисел, переданных в клиентский конструктор через параметр arg. Результат записывается в свойство Title, которое может передавать результат на клиентскую сторону в виде JSON -объекта через свойство Data либо через произвольный HTML элемент. Например, через конструктор удобно передавать идентификаторы либо другие объекты, характеризующие какие-либо сущности, далее загружать необходимые данные на сервере и возвращать их на клиентскую сторону вместе с компонент.

    Если arg не определен, то произойдет только клиентское создание компонент, при котором дочерние компоненты, определенные на стороне сервера, созданы не будут. Например, компонент для ввода даты вряд ли потребует вызов серверного конструктора.

    14.2.6. Передача данных на клиентскую сторону

    Для передачи данных на клиентскую сторону, на сервере можно использовать словарь Data, который есть у каждого компонента. Все значения, записанные в этот словарь, будут доступны на клиентской стороне в виде JSON -объекта в свойстве $data.

    Например, записав при загрузке текущую дату:

    protected override void OnLoad()
    {
      Data["serverTime"] = DateTime.Now;
    }

    До нее можно с легкостью добраться на клиентской стороне:

    {
        $init: function() {
            alert(this.$data.serverTime);
        }
    }

    Данную методику можно использовать только в момент загрузки компонента, как при загрузке страницы, так и при его отложенной загрузке через JavaScript.

    Если на клиентской стороне необходимо получить/отправить данные в произвольный момент, можно воспользоваться удаленным вызовом функции (RPC). Все методы компонента, помеченные атрибутом RpcMethodAttribute, можно вызывать удаленно.

    Пример таких методов представлен ниже. Остается отметить, что такие методы, по сути, являются методами Веб-сервисов, для которых созданы прокси-классы в JavaScript:

    public class MyUserControl : UserControl
    {
        [JsonRpcMethod]
        public Person GetPersonById(int personId)
        {
            // возвращаем Person
        }
        [JsonRpcMethod]
        public void SavePerson(Person person)
        {
            // сохраняем Person
        }
    }

    Перегрузка функций поддерживается только по количеству параметров. По типу параметров перегрузка не поддерживается. В качестве параметров или возвращаемого значения могут быть произвольные объекты, которые впоследствии будут отформатированы в JSON -объекты.

    14.2.7. Наследование компонент и страниц

    Jasp поддерживает наследование компонент как на серверной стороне, так и на клиентской. У компонента наследуются все ресурсы от базового. С помощью наследования, можно, например, делать страницы, содержащие базовую разметку (по аналогии с MasterPage в ASP.NET ), но также и отдельные компоненты, которые содержат часть базовой разметки и могут быть использованы для производных компонент.

    Для наследования компонент, достаточно унаследовать серверный класс. При этом все ресурсы компонента (разметка, стили и скрипты) будут дополнять или замещать ресурсы базового компонента.

    14.2.7.1. Наследование разметки

    По умолчанию, HTML разметка копируется от базового компонента. Но благодаря специальному тегу <replacement> можно заменять или удалять базовые компоненты. Например, у нас есть базовая разметка:

    <body>
      <div id="div1">Первоначальный текст  </div>
    </body>

    И производная разметка:

    <replacement id="div1">
      <div id="div2">Переопределенный текст</div>
      И другой текст  
    </replacement>

    При копировании разметки из базового компонента, вместо элемента с идентификатором div1 будет подставлена замена из тега replacement с одноименным идентификатором.

    14.2.7.2. Наследование стилей

    При добавлении CSS ресурса к наследуемому компоненту, Jasp будет добавлять его сразу после CSS ресурса родительского компонента. Тем самым, если создавать одноименные стили – они будут переопределяться. Данная практика наследования хорошо работает со стилями страницы, но при работе с компонент возникают проблемы – стили затираются и для родительского компонента, который может быть размещен на этой же странице, что неявно изменит его внешний вид. Решение для таких случаев – это создание стилей с другими именами.

    14.2.7.3. Наследование JavaScript

    Наследование JavaScript в Jasp реализовано через прототипы. Оно очень похоже на классическое наследование в объектно-ориентированном программировании (ООП). При определении новых методов, они будут переопределять родительские. В новом методе можно вызвать базовый метод. Например, JavaScript базового компонента:

    {
        hi: function() {
            alert('Базовый привет!');
        }
    }

    И производного:

    {
        hi: function() {
            alert('Привет!');
            BaseFooControl.prototype.call(this);
        }
    }

    Для библиотек, которые поддерживают наследование через прототипы (например, ExtJS) можно задать базовый класс для компонента из библиотеки с помощью атрибута ClientBaseAttribute:

    [ClientBase("Ext.Window")]
    public class FooControl : UserControl
    {
        //....
        public FooControl(int fooId)
        {
            // подгружаем необходимые данные
        }
    }

    Клиентский код:

    {
        onFooButtonClick: function() {
            // Создаем FooControl 
            // происходит вызов серверного конструктора – в нем можно подгрузить необходимые данные
            // и вызов $init – в нем инициализировать окно, подставить загруженные данные
            var fooControl = new FooControl({
                arg: [1] // параметры конструктора
            });
            // Показываем окно (метод show наследован из ExtJs)
            fooControl.show();
        }
        // ...
    }

    Благодаря этому, компонент на Jasp будет являться одновременно и компонентом ExtJS (либо другой библиотеки). Чтобы не было конфликтов с другими библиотеками, каждое свойство или функция на клиентской стороне начинается с $. Так, например, удается избежать конфликтов идентификаторов ( id ) или часто распространенных имен функций ( init ).

    14.2.8. Конфигурирование Jasp

    Jasp конфигурируется с помощью собственной секции в файле web.config.

    Также необходимо добавить HttpHadler и WebServer Hadler, чтобы ASP.NET начал обрабатывать запросы Jasp. Если параметры не определены, Jasp конфигурируется в зависимости от конфигурации сборки. Если сборка Jasp собрана в Debug режиме, параметры оптимизированы для отладки, если сборка собрана в Release режиме, то параметры оптимизированы для производительности. В табл. 11.3 перечислены параметры конфигурации, которые поддерживает Jasp.

    Параметры конфигурации Jasp
    Название Описание Значение в Release Значение в Debug Тип
    deployPath Путь, относительный от корня сайта, куда "распаковывать" бинарные ресурсы сборки. deploy deploy String
    resourceFilter Полное имя класса, который фильтрует все текстовые ресурсы, обрабатываемые Jasp. Jasp.Resources.ResourceFilter, Jasp Jasp.Resources.ResourceFilter, Jasp String
    gzipEnabled Включен ли GZIP. True False Boolean
    watchResources Включено ли слежение за изменением текстовых ресурсов. False True Boolean
    compressJavaScript Включено ли сжатие JavaScript (удаление комментариев, лишних пробелов и т.п.). True False Boolean
    clientCaching Включена ли отправка HTTP заголовков кэширования. True False Boolean
    indentHtml Включено ли форматирование HTML. False True Boolean
    exceptionFormatter Полное имя класса, занимающегося форматирование исключения в JSON -объект. Jasp.Web.ExceptionFormatter, Jasp Jasp.Web.ExceptionFormatter, Jasp String

    Параметр resourceFilter определяет класс, экземпляр которого будет заниматься фильтрацией текстовых ресурсов. Если нам нужно как-то отформатировать текстовый ресурс, например, удалить лишние пробелы или подменить ссылки, то можно создать класс, наследованный от класса Jasp.Resources.ResourceFilter и переопределять метод Filter(string name, StringBuilder data). Отфильтрованный ресурс впоследствии кэшируется. Для примера, переопределив этот параметр, можно производить замену путей в CSS-файлах библиотеки ExtJS автоматически, чтобы при обновлении этих файлов ничего не забыть.

    Параметр exceptionFormatter используется для указания класса, экземпляр которого форматирует исключение при RPC-вызовах в JSON -объект. Если вам необходимо при возникновении исключения выводить дополнительные данные, например лог nHibernate, то это без труда можно сделать, переопределив метод Format класса Jasp.Web.ExceptionFormatter.

    Узнав, что все ресурсы в Jasp должны быть embedded resource, многие думают, что для вступления в силу изменений в HTML, CSS или в JavaScript необходимо перекомпиляция проекта. За это отвечает параметр watchResources. Если он установлен в True, то Jasp следит за ресурсами, и при изменении автоматически их "подхватывает".

    14.2.9. Ключевые термины

    Jasp, .NET Framework, ASP.NET, jQuery, Ресурсы, Страницы, Компоненты, JSON, Наследование.

    14.3. Краткие итоги

    Веб-службы можно создавать, используя архитектуру ASP.NET, что позволяет им использовать разнообразную функциональность платформы.

    При работе с Веб-сервисами существуют две фундаментальные роли:

  • создание Веб-сервиса ;
  • обращение к Веб-сервису.
  • ASP.NET позволяет создавать Веб-сервисы, к которым можно обращаться из клиентского сценария страниц.

    Страницы взаимодействуют с сервером посредством уровня связи, использующего технологию AJAX для вызова Веб-служб.

    Обмен данными между клиентом и сервером осуществляется асинхронно, как правило, в формате JSON.

    Существует два подхода к вызовам методов Веб-сервиса:

  • с помощью команды POST HTTP ;
  • с помощью команды GET HTTP.
  • Основные элементы уровня связи Веб-служб:

  • WebRequest;
  • WebRequestManager ;
  • XmlHttpExecutor ;
  • JSON - сериализация.
  • Элементы серверной архитектуры AJAX включают:

  • пользовательские Веб-службы ;
  • методы страниц ;
  • службу проверки подлинности;
  • службу ролей;
  • службу профилей;
  • JSON - сериализацию ;
  • XML- сериализацию.
  • Веб-сервисы создаются в управляемом коде с помощью платформы ASP.NET:

  • при помощи класса WebRequest;
  • с помощью использования прокси-классов.
  • Jasp является библиотекой для разработки Веб-приложений с использованием платформы Microsoft .NET Framework.

    Основным отличием Jasp от ASP.NET является ориентация библиотеки на клиентскую сторону, чтобы программисту было проще создавать "богатый" пользовательский интерфейс в первую очередь средствами языка JavaScript, CSS и в последнюю очередь HTML, сгенерированным на стороне сервера.

    При загрузке страницы, Jasp определяет, какие ресурсы используются на странице:

  • CSS ресурсы ;
  • JavaScript ресурсы ;
  • HTML ресурсы ;
  • бинарные ресурсы.
  • Для создания страницы, необходимо создать класс, наследующий от класса Jasp.Web.UI.Page.

    Для создания компонент, необходимо создать класс, наследованный от класса Jasp.Web.UI.UserControl.

    Экземпляры компонент могут создаваться:

  • программно;
  • через разметку;
  • на JavaScript.
  • Для передачи данных на клиентскую сторону, на сервере можно использовать словарь Data, который есть у каждого компонента.

    Jasp поддерживает наследование компонент, как на серверной стороне, так и на клиентской.

    Jasp конфигурируется с помощью собственной секции в файле web.config.

    Вернуться к учебному плану