Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Как добавить индикатор выполнения (Progress Bar) в клиентское приложение вашего Web сервиса
Автор: Matt Powell, Корпорация Microsoft (http://www.microsoft.com)
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net Alliance (http://www.uneta.org)
20 ноября, 2002
Применяется к:
  • Web Services
  • Обзор:
    Matt Powell показывает, как перехватить поток сообщения Web сервиса, используя SOAP расширения, чтобы обеспечить решение таких проблем, как реализация поддержки индикатора выполнения (progress bar) для клиентского приложения Web сервиса.
    Содерджание:
  • Введение
  • Постановка задачи
  • Решение задачи
  • Заключение
  • Введение

    Иногда обработка и передача данных для сообщения Web сервиса могут занимать много времени, особенно если данные большие и пропускная способность ограничена. Хорошо было бы в удобных для пользователя приложениях обеспечить индикатор выполнения, который будет показывать пользователям, как происходит передача сообщения. Проблема в том, что чтобы обновлять индикатор выполнения, вам надо иметь некоторого сорта инкрементную нотификацию о том, как обстоят дела с запросом. Однако основная поддержка Microsoft® .NET Framework для Web сервисов уведомляет приложение только тогда, когда запрос завершен. Фокус в том, чтобы влезть в обработку запроса и получить инкрементные нотификации, необходимые для обновления индикатора выполнения. Сделать это мы можем, используя возможности потоковой передачи данных SOAP расширений.

    Постановка задачи

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

    Если вы хорошо знакомы с отправлением HTTP запросов и считыванием HTTP ответов с помощью методов HttpWebRequest.GetRequestStream и HttpWebResponse.GetResponseStream, тогда вы знаете возможности потоковой передачи данных запроса и ответа в базовой поддержке, используемой Web методами. Например, базовые TCP соединения, которые переносятся полезным грузом HTTP и SOAP, являются потоковыми соединениями.

    Проблема в том, что, если весь механизм предоставляется через простой вызов метода объекта, намного проще отправить SOAP сообщение и прочитать ответ. Эту простоту использования .NET Framework обеспечивает путем создания прокси класса, который предоставляет отдельные методы вашему приложению. Перед отправкой по проводам поддержка Web сервиса сериализует параметры в SOAP запрос. Аналогичным образом, ответ десериализуется из SOAP потока в информацию ответа на вызов метода. Нет смысла просто сериализовать и десериализовать порцию данных. (Означает ли это, что некоторые параметры существуют, а другие нет?)

    Решение задачи

    Выход в том, чтобы внедриться в запрос и ответ на нижнем уровне. Со стороны сервера Microsoft® ASP.NET предоставляет изобилие интерфейсов для проникновения в механизм обработки запроса. Со стороны клиента, однако, мы ограничены. К счастью, встроенная поддержка для отправки и получения SOAP запросов для вызовов Web методов предлагает эквивалентные поддержке, предоставляемой на сервере для внедрения в SOAP запросы и ответы, функциональные возможности. Правильно, решение – использовать SOAP расширение.

    Написание SOAP расширения на клиенте аналогично написанию его на сервере. Единственным отличием является порядок действий. На сервере получаемый запрос десериализуется и затем перед отправкой обратно к клиенту сериализуется ответ. На клиенте запрос сначала должен быть сериализован для отправки, а затем полученный ответ должен быть десериализован. В нашем конкретном случае мы хотим отлавливать запрос так, как будто он был возвращен, как можно более близко к проводам.

    Расширение SOAP – это просто класс, наследуемый от класса System.Web.Services.Protocols.SoapExtension. Есть некоторое количество методов инициализации, которые вы должны переопределить в вашем классе, но, в отличие от переопределения по умолчанию, для наших целей практически все они могут быть проигнорированы. В основном, мы рассматриваем только два переопределяемых метода класса SoapExtension: ChainStream и ProcessMessage.

    ChainStream – это механизм, используемый для добавления вашего собственного потока в "цепь" потоков. Это обеспечивает вашему SOAP расширению возможность видеть и потенциально изменять входящие и исходящие данные. ChainStream вызывается дважды за запрос: первый раз - во время создания потока запроса, и второй раз - во время создания потока ответа. Для обновления индикатора выполнения при получении ответного сообщения, мы, в частности, заинтересованы в проникновении в поток ответа.

    Переопределение ProcessMessage в вашем SOAP расширении будет вызываться на четырех этапах в течение обработки одного SOAP запроса и ответа. Стадии BeforeSerialize и AfterSerialize имеют место при сериализации запроса, стадии BeforeDeserialize и AfterDeserialize – при десериализации ответа. Нас интересует ответ. Мы хотим отслеживать данные по мере их прохождения по сети, а не после того как они уже буферизованы и десериализованы. Поэтому основную часть нашей работы мы будем делать на этапе BeforeDeserialize.

    Одним из необычных требований нашего SOAP расширения является то, что ему понадобится взаимодействовать с пользовательским интерфейсом нашего приложения. В частности, ему понадобится обновлять индикатор выполнения, что должно происходить в определенном потоке, который обладает описателем базового окна элемента управления. В обычных многопоточных Microsoft® Windows приложениях, чтобы запустить делегат в потоке элемента управления, вы просто используете функцию-делегат и метод Invoke элемента управления, и они выполняют работу по взаимодействию с элементом управления. В нашем случае SOAP расширение реализовывается в классе, отдельном от пользовательского интерфейса, поэтому нам нужен способ передать ссылку на конкретный экземпляр элемента управления в класс нашего SOAP расширения.

    Чтобы сделать это, я создал специальный класс, унаследованный от прокси класса Web сервиса, который был создан через Add Web Reference возможность Microsoft® Visual Studio® .NET. Вы можете получить экземпляр прокси класса, ассоциированного с запросом, сначала преобразовывая переданный в функцию ProcessMessage параметр SoapMessage в SoapClientMessage, и затем обращаясь к свойству Client. Я просто добавляю в созданный мною класс (унаследованный от исходного прокси класса Web сервиса) public член, который сохраняет любую необходимую мне информацию. В данном случае, в качестве public члена я добавил ссылку на объект индикатора обработки. Тот же механизм я использую для передачи из приложения такой информации, как размер ожидаемых мною данных и функция-делегат, которую необходимо использовать для связи с индикатором выполнения. Далее приведен код моего специального прокси класса:

    internal class ProgressClient : localhost.Service1
    {
        public ProgressBar Progress;
        public int TransferSize;
        public Form1.UpdateDelegate ProgressDelegate;
    }
    

    Вот код моего класса SoapExtension:

    public class ProgressExtension : SoapExtension
    {
        // Holds the original stream
        private Stream m_oldStream;
        // The new stream
        private Stream m_newStream;
        // The buffer for reading from the old stream
        // and writing to the new stream
        private byte[] m_bufferIn;
        // The progress bar we will be incrementing
        private ProgressBar m_Progress;
        // The size of each read
        private int m_readSize;
        // The delegate we will invoke for updating the
        // progress bar.
        private Form1.UpdateDelegate m_progressDelegate;
        // Used to keep track of which stream we are trying
        // to chain into
        private bool m_isAfterSerialization;
        public override void ProcessMessage(SoapMessage message)
        {
            switch(message.Stage)
            {
                case SoapMessageStage.AfterSerialize:
                    // To let us know that the next ChainStream call
                    // will let us hook in where we want.
                    m_isAfterSerialization = true;
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    // This is where we stream through the data
                    SoapClientMessage clientMessage 
                        = (SoapClientMessage)message;
                    if (clientMessage.Client is ProgressClient)
                    {
                        ProgressClient proxy 
                            = (ProgressClient)clientMessage.Client;
                        m_Progress = proxy.Progress;
                        // Read 1/100th of the request at a time.
                        // This will give the progress bar 100 
                        // notifications.
                        m_readSize = proxy.TransferSize / 100;
                        m_progressDelegate = proxy.ProgressDelegate;
                    }
                    while (true)
                    {
                        try
                        {
                            int bytesRead 
                                = m_oldStream.Read(m_bufferIn, 
                                0, 
                                m_readSize);
                            if (bytesRead == 0) 
                            {
                                // end of message...rewind the
                                // memory stream so it is ready
                                // to be read during deserial.
                                m_newStream.Seek(0, 
                                    System.IO.SeekOrigin.Begin);
                                return;
                            }
                            m_newStream.Write(m_bufferIn, 
                                0, 
                                bytesRead);
                            // Update the progress bar
                            m_Progress.Invoke(m_progressDelegate);
                        }
                        catch
                        {
                            // rewind the memory stream
                            m_newStream.Seek(0, 
                                System.IO.SeekOrigin.Begin);
                            return;
                        }
                    }
            }
        }
    
        public override Stream ChainStream(Stream stream)
        {
            if (m_isAfterSerialization)
            {
                m_oldStream = stream;
                m_newStream = new MemoryStream();
                m_bufferIn = new Byte[8192];
                return m_newStream;
            }
            return stream;
        }
        // We don't have an initializer to be shared across streams
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
    
        public override object GetInitializer(
            LogicalMethodInfo methodInfo, 
            SoapExtensionAttribute attribute)
        {
            return null;
        }
    
        public override void Initialize(object initializer) 
        {m_isAfterSerialization = false;}
    }
    

    Чтобы SOAP расширение вызывалось для клиентского приложения, класс SoapExtension должен быть соответственно конфигурирован. Для Microsoft® Windows® Form приложения требуется модификация файла конфигурации. Для моего приложения WebServiceProgress.exe файл конфигурации WebServiceProgress.exe.config выглядит следующим образом:

    <configuration>
      <system.web>
        <webServices>
          <soapExtensionTypes>
            <add 
        type="WebServiceProgress.ProgressExtension, WebServiceProgress"
        priority="1" group="0" />
          </soapExtensionTypes>
        </webServices>
     </system.web>
    </configuration>
    

    В атрибуте type элемента add показан тип моего класса SoapExtension и сборки, в которой он находится. Значение атрибута priority установлено равным 1, а значение атрибута group – 0, для того чтобы SoapExtension имел наивысший уровень приоритета. Мы хотим, чтобы считываемый нами поток был как можно более близок к «проводам», поэтому важно, чтобы между потоком SoapExtension и сетью не было других потоков. Следовательно, мы устанавливаем максимальный приоритет. Но все еще остается вероятность того, что в цепочку перед нашим расширением может быть вставлено SOAP расширение. Если это произойдет, тогда поток, из которого мы читаем, будет состоять только из буферизованных данных, которые не создадут нам точной картины о пропускной способности нашей сети.

    Последний участок кода, который нужен для всего этого – функция delegate в главном классе для моего приложения, связанного с формой. Это функция, которая и будет обновлять элемент управления индикатор выполнения. Она должны быть объявлена как делегат и вызвана с помощью метода Invoke, чтобы могла запуститься в том же потоке, где находится подкачка сообщений окна, в котором живет элемент управления. Сначала вы должны объявить тип делегата:

    public delegate void UpdateDelegate();
    

    Затем вы должны создать функцию с той же сигнатурой, которая обычно осуществляет взаимодействие. В моем случае, свойство Maximum индикатора выполнения установлен во время разработки на 100. в классе моего SOAP расширения я считываю 1/100 ожидаемых данных в единицу времени. По завершении каждого считывания, индикатор выполнения может увеличиться на 1, таким образом, он достигнет своего максимума с окончанием последнего считывания. Код для функции обновления моего индикатора выполнения просто выглядит следующим образом:

    private void ProgressBarUpdate()
    {
        progressBar1.Increment(1);
    }
    

    Другой потенциальной проблемой является то, что многие инструментальные средства Web сервисов буферизуют ответ на сервере до тех пор, пока не завершиться код Web сервиса. Это, конечно, происходит в том случае, если вы имеете дело с сервером, написанным с использованием ASP.NET Web методами. Если вы надеетесь обеспечить функциональность индикатора выполнения, который отслеживает в коде Web сервиса процесс генерирования элементов большого массива, тогда забудьте об этом. Если, однако, вы хотите, чтобы ваш индикатор выполнения отслеживал длительный процесс передачи исключительно большого массива по сети , тогда вам повезло. Это решения как нельзя лучше подойдет для удовлетворения ваших требований.

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

    Заключение

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

    Вы могли бы также рассмотреть другие причины использования потокового подхода к данным Web сервиса. Например, когда данные слишком велики и не могут быть запросто буферизованы, когда вы хотите регистрировать входящие и исходящие данные или когда имеют смысл нарастающие действия с данными (как при осуществлении сжатия или шифрования). Во всех других случаях SOAP расширения предоставляют контроль, который может понадобиться для ваших приложений Web сервисов.

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

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

       
       
         
      VBNet рекомендует