Методика создания интерактивных приложений Silverlight

Работа вне браузера и с web-сервисами

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

Работа вне браузера означает:

  • Приложение установлено локально, оффлайн, работает вне браузера
  • При этом, устанавливается только с веб страницы. Но, однажды будучи установленным, запускается через ярлык в меню пуск
  • Поддерживает API: сетевые, локальные, детектирование наличия/отсутствия сетевого соединения
  • Устанавливается по нажатию правой клавиши:

    (рис 3.1) Локальная установка Silverlight приложения

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

    (рис 3.2) Удаление Silverlight приложения

    По умолчанию создаваемое вами Silverlight приложения нельзя установить вне браузера. Чтобы добиться этого, необходимо в свойствах проекта Visual Studio указать работу вне браузера. Можно также указать различные опции, такие как ярлыки и название.

    (рис 3.3) Создание Silverlight приложения с поддержкой внебраузерной работы(рис 3.4) Создание Silverlight приложения с поддержкой внебраузерной работы. Активация(рис 3.5) Создание Silverlight приложения с поддержкой внебраузерной работы. Параметры приложения

    Программирование приложения для работы вне браузера

    Инсталляцией можно управлять изнутри кода .NET. Имеются следующие API:

    Application.Current.Install();
  • Отображается диалог для установки приложения. Если приложение уже установлено бросается исключение.
    bool IsRunningOutOfBrowser = Application.Current.IsRunningOutOfBrowser;
  • запущено приложение или нет
    System.Windows.InstallState state = Application.Current.InstallState;
  • показывает состояние инсталляции Установлено ( Installed ), неустановленно ( NotInstalled ), устанавливается ( Installing ), установка не удалась ( InstallFailed ).
    Application.Current.InstallStateChanged += (sender, e) => UpdateUI();
  • вызов метода UpdateUI, при изменении состояния инсталляции
  • Установки внебраузерного приложения в проекте хранятся в xml файле OutOfBrowserSettings.xml. Ниже приведен пример такого файла.

    <OutOfBrowserSettings ShortName="SilverlightApplication1 Application" 
           EnableGPUAcceleration="False" ShowInstallMenuItem="True">
      <OutOfBrowserSettings.Blurb>
        SilverlightApplication1 Application on your desktop; at home, at work or on the go.
      </OutOfBrowserSettings.Blurb>
      <OutOfBrowserSettings.WindowSettings>
        <WindowSettings Title="SilverlightApplication1 Application" />
      </OutOfBrowserSettings.WindowSettings>
      <OutOfBrowserSettings.Icons />
    </OutOfBrowserSettings>

    Доступ к ним из кода .NET можно осуществить так:

    System.Windows.OutOfBrowserSettings settings = Deployment.Current.OutOfBrowserSettings;

    После установки приложения установки копируются в файл манифеста AppManifest.xml.

    Установленное локальное приложение может иметь различные версии с серверным. В Silverlight можно контролировать версии и загружать обновления с помощью кода .NET:

    Application.Current.CheckAndDownloadUpdateAsync();
  • проверка и загрузка обновлений в асинхронном режиме
    Application.Current.CheckAndDownloadUpdateCompleted += 
         new CheckAndDownloadUpdateCompletedEventHandler(Current_CheckAndDownloadUpdateCompleted);
  • метод CheckAndDownloadUpdateCompleted (object sender, CheckAndDownloadUpdateCompletedEventArgs e), вызываемый после окончания загрузки и обновления локального приложения
  • Работающее в настоящий момент приложение обновлено не будет. Надо перезапустить приложение заново.

    При работе с сетью (загрузка обновления, доступ к базе данных, веб сервису и т.д.) надо всегда проверять установлена ли сеть и работоспособна ли она. Также необходимо знать изменилось ли состояние сети за время работы приложения.

    bool isNetworkAvailable = System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();

    В данном случае выполняется проверка состояния сети.

    NetworkChange.NetworkAddressChanged += 
         new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);

    Здесь вызывается метод NetworkChange_NetworkAddressChanged(object sender, EventArgs e), если состояние сети изменилось.

    Изолированное хранилище

    Внебраузерные приложения Silverlight всегда работают в изолированном хранилище. Это означает, что данные изолированы от пользователя в виртуальной файловой системе, которая может быть как один файл в корневой директории, так и дерево каталогов и файлов. Изолированное хранилище - это абстракция, а не реальное хранилище. Любой вид данных может храниться в этом хранилище. Хранилище может располагаться на клиенте, но приложение так же может использовать изолированное хранилище на сервере. Однако, Silverlight не поддерживает "бродячий" профиль (roaming profile), который может "путешествовать" с пользователем.

    Размер изолированного хранилища, доступного для приложения, называется "группы квот". Размер группы по-умолчанию 1 Мб. Приложение может увеличить этот объем с помощью метода System.IO.IsolatedStorage.IsolatedStorageFile.IncreaseQuotaTo(newvalue).

    Работа Silverlight с базами данных

    Ключевым аспектом любого бизнес-приложения и многих других приложений является возможность переноса информации из базы данных в пользовательский интерфейс. Подключение осуществляется с помощью сущностей Entity. В качестве промежуточного транспортного слоя можно использовать классы служб RIA (подробнее в разделе 8 "Создание бизнес-приложений Silverlight с использованием .NET RIA сервисов"), либо классы Windows Communication Foundation.

    Подключение к базе данных с помощью классов Domain Service

    В данном разделе используем базу данных-пример AdventureWorks для MS SQL Server 2008. Непосредственное подключение к базе данных мы не рассматриваем, т.к. нежелательно иметь прямой доступ к базе данных из Интернета. Ведь доступ рассматривается в контексте Silverlight, т.е. веб-технологии. Лучшим образом организовать доступ к СУБД посредством ASP.NET класса Domain Service и Entity, который размещен на том же сайте, где и приложение Silverlight.

    Воспользуемся шаблоном Silverlight Application при создании Silverlight приложения имеющего доступ к данным базы данных. Будет создано решение с двумя проектами SilverlightApplication1 и SilverlightApplication1.Web. Эта процедура описана в разделе 0 "Дизайн и разработка приложений Silverlight в Visual Studio 2008". Затем в веб-проекте SilverlightApplication1.Web создадим модель ADO.NET Entity Data Model, как показано на рис. 3.6.

    (рис 3.6) Создание ADO.NET Entity Data Model

    Выбираем шаблон генерации модели на основе существующей базы данных. Выбираем имеющуюся СУБД (параметр Connection String), выбираем все нужные таблицы, представления, функции и процедуры. И запускаем процедуру генерации модели. Все данные действия показаны на рис. 3.7, рис. 3.8, рис. 3.9.

    (рис 3.7) Генерация модели данных на основе существующей базы данных(рис 3.8) Выбор источника данных для модели(рис 3.9) Выбор объектов базы данных для модели

    Автоматически будет сгенерирован класс прокси-класс Model1.Designer.cs, который содержит все выбранные элементы, как отдельные классы и свойства. После окончания генерации будет видно окно с визуальным представлением сгенерированной модели (рис. 3.10 и рис. 3.11).

    (рис 3.10) Сущности модели данных(рис 3.11) Свойства сущности модели данных

    Теперь необходимо в том же веб-проекте SilverlightApplication1.Web создать прокси-класс Domain Service (рис. 3.12). Но перед этим необходимо скомпилировать приложение SilverlightApplication1.Web. Иначе нельзя будет увидеть сущности.

    (рис 3.12) Добавление класса Domain Service

    Далее выбираем все необходимые сущности (рис. 3.13), которые войдут в прокси-класс Domain Service DomainService1.

    (рис 3.13) Добавление класса Domain Service. Выбор сущностей

    Теперь переходим к проекту SilverlightApplication1. Добавляем элемент управления DataGrid на главную страницу MainPage.xaml.

    <data:DataGrid x:Name="dataGrid1"/>

    Данные можно загрузить с помощью процедурного языка, как показано на рис. 3.14 и на листинге кода программы:

    using SilverlightApplication1.Web;
    using System.Windows.Ria.Data;
    
    namespace SilverlightApplication1
    {
      public partial class MainPage : UserControl
      {
        private SilverlightApplication1.Web.DomainService1 domainService = 
                                           new DomainService1();
        public MainPage()
        {
            
          InitializeComponent();
          System.Windows.Ria.Data.LoadOperation<Product> loadOp = 
                        this.domainService.Load<Product>
                       (this.domainService.GetProductQuery());
          this.dataGrid1.DataContext = loadOp.Entities;
        }
      }
    }
    (рис 3.14) Добавление ссылки на сервис Domain Service

    Теперь можно запускать отладку и увидеть таблицу Product.

    (рис 3.15) Пример приложения, имеющего доступ к базе данных

    Подключение к базе данных с помощью классов Windows Communication Foundation

    Для создания приложения, имеющего доступ к базе данных с помощью технологии Windows Communication Foundation необходимо выполнить шаги, показанные на рис. 3.6, рис. 3.7, рис. 3.8, рис. 3.9, рис. 3.10 и рис. 3.11. Создав модель данных на этих этапах можно приступить к созданию веб-сервиса для того, чтобы информация была доступна приложению Silverlight, работающему со стороны клиента. В данном разделе используется модель WCF/SOAP и шаблон Silverlight-Enabled WCF Service. Щелкнем правой кнопкой мыши по веб-проекту BusinessApplication1.Web и выберем Add… New Item, выбрав Silverlight-Enabled WCF Service. Новый сервис будет называться ProductServiceWPF.svc. Если открыть ProductServiceWPF.svc.cs, можно найти метод-пустышку DoWork, который переименуем в GetProducts и изменим так, чтобы он получал список товаров Product из базы данных AdventureWorks.

    (рис 3.16) Создание WCF сервиса для Silverlight

    Код изменим следующим образом:

    namespace BusinessApplication1.Web
    {
     [ServiceContract(Namespace = "")]
     [AspNetCompatibilityRequirements
       (RequirementsMode = 
        AspNetCompatibilityRequirementsMode.Allowed)]
      public class ProductServiceWPF
      {
       [OperationContract]
       public List<Product> GetProducts()
        {
          AdventureWorksLT2008Entities1 dataEntity = 
                new AdventureWorksLT2008Entities1();
          return dataEntity.Product.Where
                (prod => prod.ListPrice > 3000).ToList();
        }
      }
    }

    Первая строка списка GetProducts() создает экземпляр dataEntity, который мы определили ранее. Вторая строка запрашивает экземпляр данных об объектах Product, но поскольку их слишком много, то добавим оператор where, использующий выражение prod => prod.ListPrice > 3000 для того, чтобы уменьшить число возвращаемых элементов. Чтобы привести возвращаемую сущность к списку List, вызываем свойство ToList(), конвертируя результат в List<Product>.

    Теперь переходим в проект BusinessApplication1 и на странице MainPage.xaml добавляем элемент управления DataGrid по имени dataGrid1:

    <UserControl 
        xmlns:data="clr-namespace:System.Windows.Controls;
                    assembly=System.Windows.Controls.Data"  
        x:Class="SilverlightApplication7.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
      <Grid x:Name="LayoutRoot">
          <data:DataGrid x:Name="dataGrid1" />
      </Grid>
    </UserControl>

    Теперь необходимо привязать данные из веб-сервиса к данным табличной сетки. Это делается в Page.xaml.cs. Первым шагом будет добавление к этому проекту ссылки на веб-сервис. Для этого после щелчка правой кнопкой мыши по ServiceReferences и в диалоговом окне Add Service Reference по Discover увидим, созданный ранее веб-сервис (ProductServiceWPF), который появится в списке. Пространство имен останется ServiceReference1.

    (рис 3.17) Добавление ссылки на веб-сервис в приложении Silverlight

    Добавить ссылку на сервис нужно в MainPage.xaml.cs.

    ServiceReference1.ProductServiceWPFClient clnt = 
                    new SilverlightApplication7.ServiceReference1.
                        ProductServiceWPFClient();

    Хотя приложение Silverlight запускается в браузере, мы не можем осуществлять вызовы методов напрямую (нельзя блокировать браузер), нам скорее необходимо осуществлять асинхронные вызовы. Intellisense не только предлагает нам альтернативу в виде асинхронных вызовов, но и не предлагает нам синхронные методы, которые были созданы!

    (рис 3.18) Асинхронные вызовы WPF сервисов

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

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    using System.Threading;
    
    namespace BusinessApplication1
    {
      public partial class MainPage : UserControl
      {
        public MainPage()
        {
          InitializeComponent();
          ServiceReference1.ProductServiceWPFClient clnt = 
              new SilverlightApplication7.ServiceReference1.
                  ProductServiceWPFClient();
          clnt.GetProductsCompleted += 
              new EventHandler<SilverlightApplication7.
                  ServiceReference1.GetProductsCompletedEventArgs>
                  (clnt_GetProductsCompleted);
          clnt.GetProductsAsync();
        }
    
        void clnt_GetProductsCompleted(object sender, 
            SilverlightApplication7.ServiceReference1.
            GetProductsCompletedEventArgs e)
        {
          this.dataGrid1.ItemsSource = e.Result;
        }
      }
    }

    Результат выполнения будет выглядеть, как показано на рис. 3.19.

    (рис 3.19) Вызов таблицы базы данных с помощью WCF

    Когда проект будет скомпилирован, информация будет извлечена из базы данных, и результат будет ограничиваться лямбда-выражением, ограничивающим цену выше 3000. Он будет доступен посредством веб-сервиса и привязан к элементу управления DataGrid.

    Далее рассмотрим, что можно сделать с таблицами, чтобы гибко отображать данные.

    Работа с таблицами

    При работе с таблицами DataGrid по умолчанию можно осуществлять сортировку по значению одного поля. Одно нажатие на заголовке поля дает сортировку по возрастанию, два - по убыванию (рис. 3.20). Можно также изменить ширину колонки.

    (рис 3.20) Сортировка в DataGrid

    Можно ограничить количество выводимых полей, например тремя:

    <data:DataGrid x:Name="dataGrid1" 
             AutoGenerateColumns="False" >
      <data:DataGrid.Columns>
        <data:DataGridTextColumn
                  x:Name="ListPrice"
                  Header="ListPrice"
                  Binding="{Binding ListPrice}"/>
        <data:DataGridTextColumn 
                  x:Name="ModifiedDate"
                  Header="ModifiedDate"
                  Binding="{Binding ModifiedDate}"/>
        <data:DataGridTextColumn 
                  x:Name="Name" 
                  Header="Name"
                  Binding="{Binding Name}"/>
      </data:DataGrid.Columns>
    </data:DataGrid>

    Остальные сервисы, такие как paging, grouping, filtering делаются методами .NET и не рассматриваются в контексте Silverlight framework.

    Работа Silverlight с web сервисами

    Существует 2 подхода к созданию веб-сервисов. В случае подхода SOAP (Simple Object Access Protocol) сервер выставляет клиенту контракт, в котором указаны параметры, методы, которые клиент может вызвать из приложения. Вся работа осуществляется веб-сервисом, а клиенту передается результат работы сервиса. SOAP предоставляет способ коммуникации между приложениями, работающими под управлением различных операционных систем, на основе различных технологий и языков программирования. SOAP - ключевой элемент архитектуры .NET компании Microsoft, предназначенной для разработки интернет приложений. Примерами SOAP являются WCF, ASMX веб-сервисы. Веб-сервис имеет адрес URL, по которому клиент к нему подключается, используя протокол HTTP.

    Подход REST (Representational State Transfer) сфокусирован на данных. Здесь клиент запрашивает исключительно данные, а сервер является их хранителем. В REST-сервисах акцент сделан на доступ к ресурсам, а не на исполнение удаленных сервисов; в этом их кардинальное отличие от SOAP-сервисов. Если SOAP-клиенты запрашивают выполнение действия на сервере, то REST-клиенты попросту требуют сам ресурс. Например, вместо того чтобы запрашивать удаленное исполнение функции для нахождения нужного вам формуляра заказа, вы просто запрашиваете этот формуляр, примерно так же, как статичную Web-страницу. Примеры: WCF REST, ADO.NET Data Services. Например, в Интернете имеется доступный веб-сервис интернет магазина. По URL делается запрос к нему. В запросе требуется вывести список ноутбуков, имеющихся в продаже. http://shopping.yahooapis.com/ShoppingService/v3/productSearch?appid=YahooDemoquery=notebookshow_numratings=1 - запрос. Ответом будет XML файл следующего содержания:

    <?xml version="1.0" encoding="utf-8"?>
    <ProductSearch xmlns="urn:yahoo:prods" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="urn:yahoo:prods 
              http://shopping.yahooapis.com/shoppingservice/v3/productsearch.xsd">
    …
    <Refinement name="Brand" 
                   totalAvailable="26" 
                   totalValues="10">
    <RefineValue>
      <Title>Dell</Title>
      <Value>4168-Brand=Dell</Value>
      <Category>Laptops</Category>
      <NumberOfProducts>1778</NumberOfProducts>
    </RefineValue>
    …

    В ответе содержится число ноутбуков, и список, с указанием производителя, марки, количества штук в наличие. Далее

    Подключение приложения Silverlight к веб-сервису SOAP

    Для того чтобы создать приложение Silverlight, работающее с веб-сервисом SOAP, необходимо реализовать сам веб-сервис, выполняющий какие-то функции. Затем, проектируя приложение Silverlight, в Visual Studio добавляем ссылку на веб-сервис "Add Service Reference". Это делается в проекте веб сайта (хоста), на котором будет отображаться работающее приложение Silverlight. Visual Studio автоматически подключится к веб-сервису и сгенерирует прокси-класс, методы которого становятся доступными при программировании. Эти методы можно вызывать из приложения асинхронно.

    (рис 3.21) Создание веб-сервиса(рис 3.22) Добавление веб-сервиса в приложение Silverlight

    Ниже представлен код C# для веб-сервиса:

    public class Service1
    {
      [OperationContract]
      public string CourseName()
      {
        return "Методика создания интерактивных приложений с 
                     использованием технологии Silverlight";
      }
    }

    Ниже представлен код для асинхронного вызова веб-сервиса и его метода, который возвращает название данного курса.

    ServiceReference1.Service1Client client = 
        new SilverlightApplication1.ServiceReference1.Service1Client();
    client.CourseNameCompleted += 
        new EventHandler<ServiceReference1.CourseNameCompletedEventArgs>
                                                  (client_CourseNameCompleted);
    client.CourseNameAsync();
    
    void client_CourseNameCompleted(object sender, 
                           ServiceReference1.CourseNameCompletedEventArgs e)
    {
      string str = "Название курса: " + e.Result;
    }

    Особенности при работе с веб-сервисами

    Не всегда производительность веб-сервисов устраивает разработчика приложения Silverlight. При взаимодействии приложения Silverlight и веб-сервиса происходит обмен XML сообщениями. Такой формат вызывает излишний трафик. Поэтому в Silverlight 3 предусмотрен двоичный формат данных Binary XML и он является форматом по умолчанию ( <binaryMessageEncoding /> -строка файла web.config). Binary XML характеризуется компактностью и возможностью индексации содержимого. Использование двоичного формата позволяет сократить размеры пересылаемых сообщений, увеличить скорость их обработки. Формат Binary XML направлен на увеличение производительности, но не является способом сжатия. Хотя этот эффект также присутствует.

    (рис 3.23) Повышение производительности при использовании Binary XML

    Тестирование приложения (см. результаты теста на рис. 3.23) показывает, что при высокой нагрузке двоичный формат имеет значительное преимущество в количестве обрабатываемых запросов. При 20 передаваемых объектах прирост составил 24%, а при 100 объектах уже 71%.

    (рис 3.24) Уменьшение размеров сообщений при использовании формата Binary XML

    Кроме того, происходило уменьшение размеров сообщений (см. рис. 3.24). "Коэффициент сжатия" для строковых переменных составил 15%.

    Другим важным моментом является обработка исключений. Например, при возникновении необрабатываемого исключения на стороне сервиса на стороне клиента возникает ошибка соединения CommunicationException, которая сама по себе мало о чем говорит. Если в WCF включить флаг IncludeExceptionDetailsInFaults (по умолчанию false, сделать true: <serviceDebug includeExceptionDetailInFaults="true"/> ), то даже в этом случае подробности исключения не доступны на клиенте Silverlight. Это происходит потому, что приложение Silverlight является плагином для браузера. И если сервис выдает исключение, то он посылает браузеру HTTP сообщение с кодом 500, а не 200. В этом случае приложение Silverlight не получает от браузера никаких сообщений. Решение здесь таково, что в любом случае надо посылать HTTP сообщение с кодом 200. Пример "Message Inspector Sample" в Silverlight 2 доступен по адресу http://code.msdn.com/SilverlightWS. В Silverlight 3 это можно сделать проще, т.к. имеется поддержка классов Faults: System.ServiceModel.ExceptionDetail и FaultException<T>.

    Безопасность приложений Silverlight основывается на безопасности ASP.NET. Пользователь заходит на страницу *.aspx с контейнером для приложения Silverlight и аутентифицируется на ней. Полученное разрешение используется приложением Silverlight. Схема работает нормально, если используется один домен (рис. 3.25). Если злоумышленники хотят подменить приложение и выводят пользователя на другой домен и оттуда пользователь получает доступ к информации, тогда возможно нарушение безопасности (рис. 3.26). Ситуация, когда приложение из одного интернет домена работает с веб-сервисом из другого домена, называется кроcсдоменный доступ.

    (рис 3.25) Регистрация пользователя

    Регулирование кроcсдоменного доступа в технологии Silverlight происходит с помощью файла clientaccesspolicy.xml либо файла crossdomain.xml, используемого технологией Flash. Файл размещается в корне сайта, например, mydomain.com/clientaccesspolicy.xml. По умолчанию кросдоменный доступ запрещен.

    (рис 3.26) Кроcсдоменный доступ

    Вот URL такого файла http://video.msn.com/clientaccesspolicy.xml, и его содержимое:

    <?xml version="1.0" encoding="utf-8"?>
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from>
            <domain uri="*"/>
          </allow-from>
          <grant-to>
            <resource path="/" include-subpaths="true"/>
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>

    Это означает, что доступ разрешен из любого домена. И другой файл http://video.msn.com/crossdomain.xml:

    <?xml version="1.0"?>
    <cross-domain-policy>
      <allow-access-from domain="*.msn.com" /> 
      <allow-access-from domain="sand.msn-int.com" />
      <allow-access-from domain="articles.moneycentral.alpha.msn-int.com" />
      <allow-http-request-headers-from domain="*.msn.com" 
                       headers="SOAPAction"/>
    </cross-domain-policy>

    Тут видно, что для flash-приложений разрешен доступ только с определенных доменов.

    Браузерная аутентификация, т.е. аутентификация, основанная на файлах cookie, или windows аутентификация, подходит только в том случае, если веб-сервис находится в одном домене, что и приложение Silverlight, либо доступ ограничен только доверенными доменами. В остальных случаях нужно использовать Message-based аутентификацию.

    При Message-based аутентификации передача логина и пароля осуществляется в URL или в SOAP-заголовках.

    (рис 3.27) Message-based аутентификация

    Аутентификация осуществляется непосредственно из приложения Silverlight (рис. 3.27). В таком случае вредное приложение не может получить доступ к специфичной информации, имеющейся в распоряжении легального приложения. Передавать логин и пароль можно как параметр:

    [OperationContract]
    public decimal GetInfo(int accountID, string userName, string password);

    Можно использовать расширения WCF, что требует глубоких знаний WCF. Но можно использовать возможности Silverlight 3. Надо указать в файле ServiceReferences.ClientConfig приложения Silverlight:

    <bindings>
      <customBinding>
        <binding name="CustomBinding_Service1">
          <binaryMessageEncoding />
          <security authenticationMode="UserNameOverTransport"/>                    
          …
        </binding>
      </customBinding>
    </bindings>

    И в файле Web.config сайта:

    <bindings>
      <customBinding>
        <binding name="customBinding0">
          <security authenticationMode="UserNameOverTransport"/>
          <binaryMessageEncoding />
          <httpTransport />
        </binding>
      </customBinding>
    </bindings>

    Кроме того, в этом же файле надо указать:

    <behaviors>
      <serviceBehaviors>
        <behavior name="SilverlightApplication1.Web.Service1Behavior">
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"/>
          </serviceCredentials>
          …
        </behavior>
      </serviceBehaviors>
    </behaviors>

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

    ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
    client.ClientCredentials.UserName.UserName = "test";
    client.ClientCredentials.UserName.Password = "test";

    Следует помнить, что в заголовках SOAP имя и пароль будут передаваться в открытом виде, а значит, приложение должно использовать протокол HTTPS. В Silverlight 3 имеется утилита SlSvcUtil.exe, расположенная в каталоге C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools. Она позволяет более гибко настраивать безопасность приложения Silverlight.

    Ниже приведены некоторые опции для утилиты.

    -= COMMON OPTIONS =- C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools>slsvcutil.exe
    /directory:<directory> Directory to create files in (default: current directory) (Short Form: /d)
    /slsvcutilConfig:<configFile> Custom configuration file to use in place of the app config file. This can be used to register system.serviceModel extensions without altering the tool's config file
    /noLogo Suppress the copyright and banner message.
    /help Display command syntax and options for the tool. (Short Form: /?)
    -= CODE GENERATION =- Syntax: slsvcutil.exe <metadataDocumentPath>* | <url>* | <epr>
    <metadataDocumentPath> The path to a metadata document (wsdl or xsd). Standard command-line wildcards can be used in the file path.
    <url> The URL to a service endpoint that provides metadata or to a metadata document hosted online.
    <epr> The path to an XML file that contains a WS-Addressing EndpointReference for a service endpoint that supports WS-Metadata Exchange.
    Options:
    /out:<file> The filename for the generated code. Default: derived from the WSDL definition name, WSDL service name or targetNamespace of one of the schemas. (Short Form: /o)
    /config:<configFile> The filename for the generated config file. Default: ServiceReferences.ClientConfig
    /mergeConfig Merge the generated config into an existing file instead of overwriting the existing file.
    /noConfig Do not generate config
    /language:<language> The programming language to use for generating code. Provide either a language name registered in the machine.config file or provide the fully-qualified name of a class that inherits from System.CodeDom.Compiler.CodeDomProvider. Examples of language names to use are CS and VB. Default: C#. (Short Form: /l)
    /namespace:<string,string> A mapping from a WSDL or XML Schema targetNamespace to a CLR namespace. Using the '*' for the targetNamespace maps all targetNamespaces without an explicit mapping to that CLR namespace. Default: derived from the target namespace of the schema document for Data Contracts. The default namespace is used for all other generated types. (Short Form: /n)
    /messageContract Generate Message Contract types. (Short Form: /mc)
    /enableDataBinding Implement the System.ComponentModel.INotif yPropertyChanged interface on all Data Contract types to enable data binding. (Short Form: /edb)
    /internal - Generate classes that are marked as internal. Default: generate public classes. (Short Form: /i)
    /reference:<file path> Reference types in the specified assembly. When generating clients, use this option to specify assemblies that might contain types representing the metadata being imported. The assembly must be a Silverlight assembly. (Short Form: /r)
    /collectionType:<type> A fully-qualified or assembly-qualified name of the type to use as a collection data type when code is generated from schemas. (Short Form: /ct)
    /excludeType:<type> A fully-qualified or assembly-qualified type name to exclude from referenced contract types. (Short Form: /et)
    /noStdLib Do not reference standard libraries. By default mscorlib.dll and System.ServiceModel.dll are referenced.
    /serializer:Auto Automatically select the serializer. This tries to use the Data Contract serializer and uses the XmlSerializer if that fails. (Short Form: /ser)
    /serializer:DataContractSerializer Generate data types that use the Data Contract Serializer for serialization and deserialization
    /serializer:XmlSerializer Generate data types that use the XmlSerializer for serialization and Deserialization
    /wrapped Generated code will not unwrap "parameters" member of document-wrapped-literal messages.

    При создании приложения Silverlight можно использовать так называемое "дуплексное" взаимодействие. Это когда сервер вызывает методы клиента, например, для системы мониторинга клиента. Либо, например, клиент вызывает веб-сервис, с помощью которого подписывается на какое-либо событие на сервере. Когда это событие происходит веб-сервис обращается к клиенту и вызывает там какой-нибудь метод. Для реализации этого необходимо на клиенте с помощью средств WCF запустить прослушивание порта TCP. Этого может не допустить файрвол клиента. Реализовать "псевдо-дуплекс" (эмуляция) можно периодически вызывая веб-сервис и опрашивая его на предмет наличия обновлений.

    В Silverlight 3 дуплексное взаимодействие осуществляется с помощью фабрики factory, имеющейся в библиотеке System.ServiceModel.PollingDuplex, состоящей из клиентской *.dll для приложения Silverlight и серверной для веб-сервиса. Реализовать дуплекс в Silverlight 3 гораздо проще и не нужны знания WCF. Библиотеки расположены в каталогах C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Client\System.ServiceModel.PollingDuplex.dll и C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll.

    Чтобы организовать дуплекс, необходимо на веб-сервисе определить контракт с обратным вызовом:

    [ServiceContract(CallbackContract = typeof(…))]
    [OperationContract(IsOneWay = true)]

    Получить вызывающего "клиента"

    OperationContext.Current.GetCallbackChannel<…>();

    Для примера приведится приложение периодически генерирующее GUID, который после генерации передается клиенту и отображается в приложении Silverlight. Ниже приведен код веб-сервиса:

    namespace DuplexCommunication.Web
    {
      [ServiceContract(Namespace = "", CallbackContract = typeof(ICallback))]
      public class DataPushService
      {
        private ICallback client;
        private Timer timer;
    
        [OperationContract(IsOneWay = true)]
        public void Subscribe()
        {
          client = OperationContext.Current.GetCallbackChannel<ICallback>();
          timer = new Timer(
            new TimerCallback(PushUpdateToClient), null, 0, 3000);
          return;
        }
    
        private void PushUpdateToClient(object state)
        {
          try
          {
            client.Update(Guid.NewGuid().ToString());
          }
          catch (TimeoutException)
          {
            timer.Dispose();
          }
          catch (CommunicationException)
          {
            timer.Dispose();
          }
        }
      }
    
      [ServiceContract]
      public interface ICallback
      {
        [OperationContract(IsOneWay = true)]
        void Update(string message);
      }
    }

    Здесь в коде опеределен контракт и указан параметр CallbackContract = typeof(ICallback). Параметр ICallback представляет собой интерфейс, который передает одну переменную string message. Т.е. в этом примере GUID. В методе Subscribe() каждые 3 секунды генерируется новый GUID и в методе PushUpdateToClient он передается клиенту.

    На клиенте добавляется ссылка на веб-сервис, как было показано ранее. Но адрес веб-сервиса нужно указать явно "http://localhost:6652/DataPushService.svc".

    namespace DuplexCommunication
    {
      public partial class MainPage : UserControl
      {
        public MainPage()
        {
          InitializeComponent();
          DataPushServiceClient proxy = 
              new DataPushServiceClient(binding, address);
          proxy.UpdateReceived += 
              new EventHandler<UpdateReceivedEventArgs>(proxy_UpdateReceived);
          proxy.SubscribeAsync();
        }
    
        EndpointAddress address = 
            new EndpointAddress(@"http://localhost:6652/DataPushService.svc");
        CustomBinding binding = new CustomBinding(
        new PollingDuplexBindingElement(),
        new TextMessageEncodingBindingElement (MessageVersion.Soap12WSAddressing10, 
                                               Encoding.UTF8),
        new HttpTransportBindingElement());
    
        void proxy_UpdateReceived(object sender, UpdateReceivedEventArgs e)
        {
          serverResponse.Text = e.message;
        }
      }
    }

    Здесь создается прокси-класс, который "подписывается" на событие proxy.UpdateReceived, proxy.SubscribeAsync(). При возникновении этого события вызывается метод proxy_UpdateReceived, который выводит на экран то, что передает веб-сервис через прокси-класс.

    Подключение приложения Silverlight к веб-сервису REST

    Для доступа к REST сервису приложение Silverlight использует классы HttpWebRequest и WebClient. REST сервис возвращает данные в определенном формате. Если это формат XML, то на стороне сервера используются средства XmlReader / XmlWriter, Linq to XML, XmlSerializer.

    Чтобы вызвать веб-сервис REST надо:

  • Сослаться на веб-сервис Uri serviceUri = new Uri("http://fabrikam.com/service/getUser")
  • В случае, если приложение Silverlight находится в другом домене, чем веб-сервис, надо настроить кроссдоменность (см. ранее).
  • Создать экземпляр класса WebClient downloader = new WebClient()
  • Вызвать асинхронно и подписаться на событие получения ответа downloader.OpenReadCompleted += new OpenReadCompletedEventHandler(downloader_OpenReadCompleted); downloader.OpenReadAsync(serviceUri)
  • Начать обрабатывать результат System.IO.Stream responseStream = e.Result ;
  • Пример с классом WebClient:

    public void HowToMakeRequestsToHttpBasedServices()
    {
      Uri serviceUri = 
          new Uri("http://fabrikam.com/service/getUser");
      WebClient downloader = new WebClient();
      downloader.OpenReadCompleted += 
          new OpenReadCompletedEventHandler(downloader_OpenReadCompleted);
      downloader.OpenReadAsync(serviceUri);
    }
    
    void downloader_OpenReadCompleted(object sender, 
                                      OpenReadCompletedEventArgs e)
    {
      if (e.Error == null)
      {
        System.IO.Stream responseStream = e.Result;
      }
    }

    Таким образом, приложение Silverlight 3 может использовать как SOAP, так и REST веб-сервисы. И делается это довольно просто.

    Страницы:

    Работа вне браузера означает:

  • Приложение установлено локально, оффлайн, работает вне браузера
  • При этом, устанавливается только с веб страницы. Но, однажды будучи установленным, запускается через ярлык в меню пуск
  • Поддерживает API: сетевые, локальные, детектирование наличия/отсутствия сетевого соединения
  • Устанавливается по нажатию правой клавиши:

    (рис 3.1) Локальная установка Silverlight приложения

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

    (рис 3.2) Удаление Silverlight приложения

    По умолчанию создаваемое вами Silverlight приложения нельзя установить вне браузера. Чтобы добиться этого, необходимо в свойствах проекта Visual Studio указать работу вне браузера. Можно также указать различные опции, такие как ярлыки и название.

    (рис 3.3) Создание Silverlight приложения с поддержкой внебраузерной работы(рис 3.4) Создание Silverlight приложения с поддержкой внебраузерной работы. Активация(рис 3.5) Создание Silverlight приложения с поддержкой внебраузерной работы. Параметры приложения

    Программирование приложения для работы вне браузера

    Инсталляцией можно управлять изнутри кода .NET. Имеются следующие API:

    Application.Current.Install();
  • Отображается диалог для установки приложения. Если приложение уже установлено бросается исключение.
    bool IsRunningOutOfBrowser = Application.Current.IsRunningOutOfBrowser;
  • запущено приложение или нет
    System.Windows.InstallState state = Application.Current.InstallState;
  • показывает состояние инсталляции Установлено ( Installed ), неустановленно ( NotInstalled ), устанавливается ( Installing ), установка не удалась ( InstallFailed ).
    Application.Current.InstallStateChanged += (sender, e) => UpdateUI();
  • вызов метода UpdateUI, при изменении состояния инсталляции
  • Установки внебраузерного приложения в проекте хранятся в xml файле OutOfBrowserSettings.xml. Ниже приведен пример такого файла.

    <OutOfBrowserSettings ShortName="SilverlightApplication1 Application" 
           EnableGPUAcceleration="False" ShowInstallMenuItem="True">
      <OutOfBrowserSettings.Blurb>
        SilverlightApplication1 Application on your desktop; at home, at work or on the go.
      </OutOfBrowserSettings.Blurb>
      <OutOfBrowserSettings.WindowSettings>
        <WindowSettings Title="SilverlightApplication1 Application" />
      </OutOfBrowserSettings.WindowSettings>
      <OutOfBrowserSettings.Icons />
    </OutOfBrowserSettings>

    Доступ к ним из кода .NET можно осуществить так:

    System.Windows.OutOfBrowserSettings settings = Deployment.Current.OutOfBrowserSettings;

    После установки приложения установки копируются в файл манифеста AppManifest.xml.

    Установленное локальное приложение может иметь различные версии с серверным. В Silverlight можно контролировать версии и загружать обновления с помощью кода .NET:

    Application.Current.CheckAndDownloadUpdateAsync();
  • проверка и загрузка обновлений в асинхронном режиме
    Application.Current.CheckAndDownloadUpdateCompleted += 
         new CheckAndDownloadUpdateCompletedEventHandler(Current_CheckAndDownloadUpdateCompleted);
  • метод CheckAndDownloadUpdateCompleted (object sender, CheckAndDownloadUpdateCompletedEventArgs e), вызываемый после окончания загрузки и обновления локального приложения
  • Работающее в настоящий момент приложение обновлено не будет. Надо перезапустить приложение заново.

    При работе с сетью (загрузка обновления, доступ к базе данных, веб сервису и т.д.) надо всегда проверять установлена ли сеть и работоспособна ли она. Также необходимо знать изменилось ли состояние сети за время работы приложения.

    bool isNetworkAvailable = System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();

    В данном случае выполняется проверка состояния сети.

    NetworkChange.NetworkAddressChanged += 
         new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);

    Здесь вызывается метод NetworkChange_NetworkAddressChanged(object sender, EventArgs e), если состояние сети изменилось.

    Изолированное хранилище

    Внебраузерные приложения Silverlight всегда работают в изолированном хранилище. Это означает, что данные изолированы от пользователя в виртуальной файловой системе, которая может быть как один файл в корневой директории, так и дерево каталогов и файлов. Изолированное хранилище - это абстракция, а не реальное хранилище. Любой вид данных может храниться в этом хранилище. Хранилище может располагаться на клиенте, но приложение так же может использовать изолированное хранилище на сервере. Однако, Silverlight не поддерживает "бродячий" профиль (roaming profile), который может "путешествовать" с пользователем.

    Размер изолированного хранилища, доступного для приложения, называется "группы квот". Размер группы по-умолчанию 1 Мб. Приложение может увеличить этот объем с помощью метода System.IO.IsolatedStorage.IsolatedStorageFile.IncreaseQuotaTo(newvalue).

    Работа Silverlight с базами данных

    Ключевым аспектом любого бизнес-приложения и многих других приложений является возможность переноса информации из базы данных в пользовательский интерфейс. Подключение осуществляется с помощью сущностей Entity. В качестве промежуточного транспортного слоя можно использовать классы служб RIA (подробнее в разделе 8 "Создание бизнес-приложений Silverlight с использованием .NET RIA сервисов"), либо классы Windows Communication Foundation.

    Подключение к базе данных с помощью классов Domain Service

    В данном разделе используем базу данных-пример AdventureWorks для MS SQL Server 2008. Непосредственное подключение к базе данных мы не рассматриваем, т.к. нежелательно иметь прямой доступ к базе данных из Интернета. Ведь доступ рассматривается в контексте Silverlight, т.е. веб-технологии. Лучшим образом организовать доступ к СУБД посредством ASP.NET класса Domain Service и Entity, который размещен на том же сайте, где и приложение Silverlight.

    Воспользуемся шаблоном Silverlight Application при создании Silverlight приложения имеющего доступ к данным базы данных. Будет создано решение с двумя проектами SilverlightApplication1 и SilverlightApplication1.Web. Эта процедура описана в разделе 0 "Дизайн и разработка приложений Silverlight в Visual Studio 2008". Затем в веб-проекте SilverlightApplication1.Web создадим модель ADO.NET Entity Data Model, как показано на рис. 3.6.

    (рис 3.6) Создание ADO.NET Entity Data Model

    Выбираем шаблон генерации модели на основе существующей базы данных. Выбираем имеющуюся СУБД (параметр Connection String), выбираем все нужные таблицы, представления, функции и процедуры. И запускаем процедуру генерации модели. Все данные действия показаны на рис. 3.7, рис. 3.8, рис. 3.9.

    (рис 3.7) Генерация модели данных на основе существующей базы данных(рис 3.8) Выбор источника данных для модели(рис 3.9) Выбор объектов базы данных для модели

    Автоматически будет сгенерирован класс прокси-класс Model1.Designer.cs, который содержит все выбранные элементы, как отдельные классы и свойства. После окончания генерации будет видно окно с визуальным представлением сгенерированной модели (рис. 3.10 и рис. 3.11).

    (рис 3.10) Сущности модели данных(рис 3.11) Свойства сущности модели данных

    Теперь необходимо в том же веб-проекте SilverlightApplication1.Web создать прокси-класс Domain Service (рис. 3.12). Но перед этим необходимо скомпилировать приложение SilverlightApplication1.Web. Иначе нельзя будет увидеть сущности.

    (рис 3.12) Добавление класса Domain Service

    Далее выбираем все необходимые сущности (рис. 3.13), которые войдут в прокси-класс Domain Service DomainService1.

    (рис 3.13) Добавление класса Domain Service. Выбор сущностей

    Теперь переходим к проекту SilverlightApplication1. Добавляем элемент управления DataGrid на главную страницу MainPage.xaml.

    <data:DataGrid x:Name="dataGrid1"/>

    Данные можно загрузить с помощью процедурного языка, как показано на рис. 3.14 и на листинге кода программы:

    using SilverlightApplication1.Web;
    using System.Windows.Ria.Data;
    
    namespace SilverlightApplication1
    {
      public partial class MainPage : UserControl
      {
        private SilverlightApplication1.Web.DomainService1 domainService = 
                                           new DomainService1();
        public MainPage()
        {
            
          InitializeComponent();
          System.Windows.Ria.Data.LoadOperation<Product> loadOp = 
                        this.domainService.Load<Product>
                       (this.domainService.GetProductQuery());
          this.dataGrid1.DataContext = loadOp.Entities;
        }
      }
    }
    (рис 3.14) Добавление ссылки на сервис Domain Service

    Теперь можно запускать отладку и увидеть таблицу Product.

    (рис 3.15) Пример приложения, имеющего доступ к базе данных

    Подключение к базе данных с помощью классов Windows Communication Foundation

    Для создания приложения, имеющего доступ к базе данных с помощью технологии Windows Communication Foundation необходимо выполнить шаги, показанные на рис. 3.6, рис. 3.7, рис. 3.8, рис. 3.9, рис. 3.10 и рис. 3.11. Создав модель данных на этих этапах можно приступить к созданию веб-сервиса для того, чтобы информация была доступна приложению Silverlight, работающему со стороны клиента. В данном разделе используется модель WCF/SOAP и шаблон Silverlight-Enabled WCF Service. Щелкнем правой кнопкой мыши по веб-проекту BusinessApplication1.Web и выберем Add… New Item, выбрав Silverlight-Enabled WCF Service. Новый сервис будет называться ProductServiceWPF.svc. Если открыть ProductServiceWPF.svc.cs, можно найти метод-пустышку DoWork, который переименуем в GetProducts и изменим так, чтобы он получал список товаров Product из базы данных AdventureWorks.

    (рис 3.16) Создание WCF сервиса для Silverlight

    Код изменим следующим образом:

    namespace BusinessApplication1.Web
    {
     [ServiceContract(Namespace = "")]
     [AspNetCompatibilityRequirements
       (RequirementsMode = 
        AspNetCompatibilityRequirementsMode.Allowed)]
      public class ProductServiceWPF
      {
       [OperationContract]
       public List<Product> GetProducts()
        {
          AdventureWorksLT2008Entities1 dataEntity = 
                new AdventureWorksLT2008Entities1();
          return dataEntity.Product.Where
                (prod => prod.ListPrice > 3000).ToList();
        }
      }
    }

    Первая строка списка GetProducts() создает экземпляр dataEntity, который мы определили ранее. Вторая строка запрашивает экземпляр данных об объектах Product, но поскольку их слишком много, то добавим оператор where, использующий выражение prod => prod.ListPrice > 3000 для того, чтобы уменьшить число возвращаемых элементов. Чтобы привести возвращаемую сущность к списку List, вызываем свойство ToList(), конвертируя результат в List<Product>.

    Теперь переходим в проект BusinessApplication1 и на странице MainPage.xaml добавляем элемент управления DataGrid по имени dataGrid1:

    <UserControl 
        xmlns:data="clr-namespace:System.Windows.Controls;
                    assembly=System.Windows.Controls.Data"  
        x:Class="SilverlightApplication7.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
      <Grid x:Name="LayoutRoot">
          <data:DataGrid x:Name="dataGrid1" />
      </Grid>
    </UserControl>

    Теперь необходимо привязать данные из веб-сервиса к данным табличной сетки. Это делается в Page.xaml.cs. Первым шагом будет добавление к этому проекту ссылки на веб-сервис. Для этого после щелчка правой кнопкой мыши по ServiceReferences и в диалоговом окне Add Service Reference по Discover увидим, созданный ранее веб-сервис (ProductServiceWPF), который появится в списке. Пространство имен останется ServiceReference1.

    (рис 3.17) Добавление ссылки на веб-сервис в приложении Silverlight

    Добавить ссылку на сервис нужно в MainPage.xaml.cs.

    ServiceReference1.ProductServiceWPFClient clnt = 
                    new SilverlightApplication7.ServiceReference1.
                        ProductServiceWPFClient();

    Хотя приложение Silverlight запускается в браузере, мы не можем осуществлять вызовы методов напрямую (нельзя блокировать браузер), нам скорее необходимо осуществлять асинхронные вызовы. Intellisense не только предлагает нам альтернативу в виде асинхронных вызовов, но и не предлагает нам синхронные методы, которые были созданы!

    (рис 3.18) Асинхронные вызовы WPF сервисов

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

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    using System.Threading;
    
    namespace BusinessApplication1
    {
      public partial class MainPage : UserControl
      {
        public MainPage()
        {
          InitializeComponent();
          ServiceReference1.ProductServiceWPFClient clnt = 
              new SilverlightApplication7.ServiceReference1.
                  ProductServiceWPFClient();
          clnt.GetProductsCompleted += 
              new EventHandler<SilverlightApplication7.
                  ServiceReference1.GetProductsCompletedEventArgs>
                  (clnt_GetProductsCompleted);
          clnt.GetProductsAsync();
        }
    
        void clnt_GetProductsCompleted(object sender, 
            SilverlightApplication7.ServiceReference1.
            GetProductsCompletedEventArgs e)
        {
          this.dataGrid1.ItemsSource = e.Result;
        }
      }
    }

    Результат выполнения будет выглядеть, как показано на рис. 3.19.

    (рис 3.19) Вызов таблицы базы данных с помощью WCF

    Когда проект будет скомпилирован, информация будет извлечена из базы данных, и результат будет ограничиваться лямбда-выражением, ограничивающим цену выше 3000. Он будет доступен посредством веб-сервиса и привязан к элементу управления DataGrid.

    Далее рассмотрим, что можно сделать с таблицами, чтобы гибко отображать данные.

    Работа с таблицами

    При работе с таблицами DataGrid по умолчанию можно осуществлять сортировку по значению одного поля. Одно нажатие на заголовке поля дает сортировку по возрастанию, два - по убыванию (рис. 3.20). Можно также изменить ширину колонки.

    (рис 3.20) Сортировка в DataGrid

    Можно ограничить количество выводимых полей, например тремя:

    <data:DataGrid x:Name="dataGrid1" 
             AutoGenerateColumns="False" >
      <data:DataGrid.Columns>
        <data:DataGridTextColumn
                  x:Name="ListPrice"
                  Header="ListPrice"
                  Binding="{Binding ListPrice}"/>
        <data:DataGridTextColumn 
                  x:Name="ModifiedDate"
                  Header="ModifiedDate"
                  Binding="{Binding ModifiedDate}"/>
        <data:DataGridTextColumn 
                  x:Name="Name" 
                  Header="Name"
                  Binding="{Binding Name}"/>
      </data:DataGrid.Columns>
    </data:DataGrid>

    Остальные сервисы, такие как paging, grouping, filtering делаются методами .NET и не рассматриваются в контексте Silverlight framework.

    Работа Silverlight с web сервисами

    Существует 2 подхода к созданию веб-сервисов. В случае подхода SOAP (Simple Object Access Protocol) сервер выставляет клиенту контракт, в котором указаны параметры, методы, которые клиент может вызвать из приложения. Вся работа осуществляется веб-сервисом, а клиенту передается результат работы сервиса. SOAP предоставляет способ коммуникации между приложениями, работающими под управлением различных операционных систем, на основе различных технологий и языков программирования. SOAP - ключевой элемент архитектуры .NET компании Microsoft, предназначенной для разработки интернет приложений. Примерами SOAP являются WCF, ASMX веб-сервисы. Веб-сервис имеет адрес URL, по которому клиент к нему подключается, используя протокол HTTP.

    Подход REST (Representational State Transfer) сфокусирован на данных. Здесь клиент запрашивает исключительно данные, а сервер является их хранителем. В REST-сервисах акцент сделан на доступ к ресурсам, а не на исполнение удаленных сервисов; в этом их кардинальное отличие от SOAP-сервисов. Если SOAP-клиенты запрашивают выполнение действия на сервере, то REST-клиенты попросту требуют сам ресурс. Например, вместо того чтобы запрашивать удаленное исполнение функции для нахождения нужного вам формуляра заказа, вы просто запрашиваете этот формуляр, примерно так же, как статичную Web-страницу. Примеры: WCF REST, ADO.NET Data Services. Например, в Интернете имеется доступный веб-сервис интернет магазина. По URL делается запрос к нему. В запросе требуется вывести список ноутбуков, имеющихся в продаже. http://shopping.yahooapis.com/ShoppingService/v3/productSearch?appid=YahooDemoquery=notebookshow_numratings=1 - запрос. Ответом будет XML файл следующего содержания:

    <?xml version="1.0" encoding="utf-8"?>
    <ProductSearch xmlns="urn:yahoo:prods" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="urn:yahoo:prods 
              http://shopping.yahooapis.com/shoppingservice/v3/productsearch.xsd">
    …
    <Refinement name="Brand" 
                   totalAvailable="26" 
                   totalValues="10">
    <RefineValue>
      <Title>Dell</Title>
      <Value>4168-Brand=Dell</Value>
      <Category>Laptops</Category>
      <NumberOfProducts>1778</NumberOfProducts>
    </RefineValue>
    …

    В ответе содержится число ноутбуков, и список, с указанием производителя, марки, количества штук в наличие. Далее

    Подключение приложения Silverlight к веб-сервису SOAP

    Для того чтобы создать приложение Silverlight, работающее с веб-сервисом SOAP, необходимо реализовать сам веб-сервис, выполняющий какие-то функции. Затем, проектируя приложение Silverlight, в Visual Studio добавляем ссылку на веб-сервис "Add Service Reference". Это делается в проекте веб сайта (хоста), на котором будет отображаться работающее приложение Silverlight. Visual Studio автоматически подключится к веб-сервису и сгенерирует прокси-класс, методы которого становятся доступными при программировании. Эти методы можно вызывать из приложения асинхронно.

    (рис 3.21) Создание веб-сервиса(рис 3.22) Добавление веб-сервиса в приложение Silverlight

    Ниже представлен код C# для веб-сервиса:

    public class Service1
    {
      [OperationContract]
      public string CourseName()
      {
        return "Методика создания интерактивных приложений с 
                     использованием технологии Silverlight";
      }
    }

    Ниже представлен код для асинхронного вызова веб-сервиса и его метода, который возвращает название данного курса.

    ServiceReference1.Service1Client client = 
        new SilverlightApplication1.ServiceReference1.Service1Client();
    client.CourseNameCompleted += 
        new EventHandler<ServiceReference1.CourseNameCompletedEventArgs>
                                                  (client_CourseNameCompleted);
    client.CourseNameAsync();
    
    void client_CourseNameCompleted(object sender, 
                           ServiceReference1.CourseNameCompletedEventArgs e)
    {
      string str = "Название курса: " + e.Result;
    }

    Особенности при работе с веб-сервисами

    Не всегда производительность веб-сервисов устраивает разработчика приложения Silverlight. При взаимодействии приложения Silverlight и веб-сервиса происходит обмен XML сообщениями. Такой формат вызывает излишний трафик. Поэтому в Silverlight 3 предусмотрен двоичный формат данных Binary XML и он является форматом по умолчанию ( <binaryMessageEncoding /> -строка файла web.config). Binary XML характеризуется компактностью и возможностью индексации содержимого. Использование двоичного формата позволяет сократить размеры пересылаемых сообщений, увеличить скорость их обработки. Формат Binary XML направлен на увеличение производительности, но не является способом сжатия. Хотя этот эффект также присутствует.

    (рис 3.23) Повышение производительности при использовании Binary XML

    Тестирование приложения (см. результаты теста на рис. 3.23) показывает, что при высокой нагрузке двоичный формат имеет значительное преимущество в количестве обрабатываемых запросов. При 20 передаваемых объектах прирост составил 24%, а при 100 объектах уже 71%.

    (рис 3.24) Уменьшение размеров сообщений при использовании формата Binary XML

    Кроме того, происходило уменьшение размеров сообщений (см. рис. 3.24). "Коэффициент сжатия" для строковых переменных составил 15%.

    Другим важным моментом является обработка исключений. Например, при возникновении необрабатываемого исключения на стороне сервиса на стороне клиента возникает ошибка соединения CommunicationException, которая сама по себе мало о чем говорит. Если в WCF включить флаг IncludeExceptionDetailsInFaults (по умолчанию false, сделать true: <serviceDebug includeExceptionDetailInFaults="true"/> ), то даже в этом случае подробности исключения не доступны на клиенте Silverlight. Это происходит потому, что приложение Silverlight является плагином для браузера. И если сервис выдает исключение, то он посылает браузеру HTTP сообщение с кодом 500, а не 200. В этом случае приложение Silverlight не получает от браузера никаких сообщений. Решение здесь таково, что в любом случае надо посылать HTTP сообщение с кодом 200. Пример "Message Inspector Sample" в Silverlight 2 доступен по адресу http://code.msdn.com/SilverlightWS. В Silverlight 3 это можно сделать проще, т.к. имеется поддержка классов Faults: System.ServiceModel.ExceptionDetail и FaultException<T>.

    Безопасность приложений Silverlight основывается на безопасности ASP.NET. Пользователь заходит на страницу *.aspx с контейнером для приложения Silverlight и аутентифицируется на ней. Полученное разрешение используется приложением Silverlight. Схема работает нормально, если используется один домен (рис. 3.25). Если злоумышленники хотят подменить приложение и выводят пользователя на другой домен и оттуда пользователь получает доступ к информации, тогда возможно нарушение безопасности (рис. 3.26). Ситуация, когда приложение из одного интернет домена работает с веб-сервисом из другого домена, называется кроcсдоменный доступ.

    (рис 3.25) Регистрация пользователя

    Регулирование кроcсдоменного доступа в технологии Silverlight происходит с помощью файла clientaccesspolicy.xml либо файла crossdomain.xml, используемого технологией Flash. Файл размещается в корне сайта, например, mydomain.com/clientaccesspolicy.xml. По умолчанию кросдоменный доступ запрещен.

    (рис 3.26) Кроcсдоменный доступ

    Вот URL такого файла http://video.msn.com/clientaccesspolicy.xml, и его содержимое:

    <?xml version="1.0" encoding="utf-8"?>
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from>
            <domain uri="*"/>
          </allow-from>
          <grant-to>
            <resource path="/" include-subpaths="true"/>
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>

    Это означает, что доступ разрешен из любого домена. И другой файл http://video.msn.com/crossdomain.xml:

    <?xml version="1.0"?>
    <cross-domain-policy>
      <allow-access-from domain="*.msn.com" /> 
      <allow-access-from domain="sand.msn-int.com" />
      <allow-access-from domain="articles.moneycentral.alpha.msn-int.com" />
      <allow-http-request-headers-from domain="*.msn.com" 
                       headers="SOAPAction"/>
    </cross-domain-policy>

    Тут видно, что для flash-приложений разрешен доступ только с определенных доменов.

    Браузерная аутентификация, т.е. аутентификация, основанная на файлах cookie, или windows аутентификация, подходит только в том случае, если веб-сервис находится в одном домене, что и приложение Silverlight, либо доступ ограничен только доверенными доменами. В остальных случаях нужно использовать Message-based аутентификацию.

    При Message-based аутентификации передача логина и пароля осуществляется в URL или в SOAP-заголовках.

    (рис 3.27) Message-based аутентификация

    Аутентификация осуществляется непосредственно из приложения Silverlight (рис. 3.27). В таком случае вредное приложение не может получить доступ к специфичной информации, имеющейся в распоряжении легального приложения. Передавать логин и пароль можно как параметр:

    [OperationContract]
    public decimal GetInfo(int accountID, string userName, string password);

    Можно использовать расширения WCF, что требует глубоких знаний WCF. Но можно использовать возможности Silverlight 3. Надо указать в файле ServiceReferences.ClientConfig приложения Silverlight:

    <bindings>
      <customBinding>
        <binding name="CustomBinding_Service1">
          <binaryMessageEncoding />
          <security authenticationMode="UserNameOverTransport"/>                    
          …
        </binding>
      </customBinding>
    </bindings>

    И в файле Web.config сайта:

    <bindings>
      <customBinding>
        <binding name="customBinding0">
          <security authenticationMode="UserNameOverTransport"/>
          <binaryMessageEncoding />
          <httpTransport />
        </binding>
      </customBinding>
    </bindings>

    Кроме того, в этом же файле надо указать:

    <behaviors>
      <serviceBehaviors>
        <behavior name="SilverlightApplication1.Web.Service1Behavior">
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"/>
          </serviceCredentials>
          …
        </behavior>
      </serviceBehaviors>
    </behaviors>

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

    ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
    client.ClientCredentials.UserName.UserName = "test";
    client.ClientCredentials.UserName.Password = "test";

    Следует помнить, что в заголовках SOAP имя и пароль будут передаваться в открытом виде, а значит, приложение должно использовать протокол HTTPS. В Silverlight 3 имеется утилита SlSvcUtil.exe, расположенная в каталоге C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools. Она позволяет более гибко настраивать безопасность приложения Silverlight.

    Ниже приведены некоторые опции для утилиты.

    -= COMMON OPTIONS =- C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools>slsvcutil.exe
    /directory:<directory> Directory to create files in (default: current directory) (Short Form: /d)
    /slsvcutilConfig:<configFile> Custom configuration file to use in place of the app config file. This can be used to register system.serviceModel extensions without altering the tool's config file
    /noLogo Suppress the copyright and banner message.
    /help Display command syntax and options for the tool. (Short Form: /?)
    -= CODE GENERATION =- Syntax: slsvcutil.exe <metadataDocumentPath>* | <url>* | <epr>
    <metadataDocumentPath> The path to a metadata document (wsdl or xsd). Standard command-line wildcards can be used in the file path.
    <url> The URL to a service endpoint that provides metadata or to a metadata document hosted online.
    <epr> The path to an XML file that contains a WS-Addressing EndpointReference for a service endpoint that supports WS-Metadata Exchange.
    Options:
    /out:<file> The filename for the generated code. Default: derived from the WSDL definition name, WSDL service name or targetNamespace of one of the schemas. (Short Form: /o)
    /config:<configFile> The filename for the generated config file. Default: ServiceReferences.ClientConfig
    /mergeConfig Merge the generated config into an existing file instead of overwriting the existing file.
    /noConfig Do not generate config
    /language:<language> The programming language to use for generating code. Provide either a language name registered in the machine.config file or provide the fully-qualified name of a class that inherits from System.CodeDom.Compiler.CodeDomProvider. Examples of language names to use are CS and VB. Default: C#. (Short Form: /l)
    /namespace:<string,string> A mapping from a WSDL or XML Schema targetNamespace to a CLR namespace. Using the '*' for the targetNamespace maps all targetNamespaces without an explicit mapping to that CLR namespace. Default: derived from the target namespace of the schema document for Data Contracts. The default namespace is used for all other generated types. (Short Form: /n)
    /messageContract Generate Message Contract types. (Short Form: /mc)
    /enableDataBinding Implement the System.ComponentModel.INotif yPropertyChanged interface on all Data Contract types to enable data binding. (Short Form: /edb)
    /internal - Generate classes that are marked as internal. Default: generate public classes. (Short Form: /i)
    /reference:<file path> Reference types in the specified assembly. When generating clients, use this option to specify assemblies that might contain types representing the metadata being imported. The assembly must be a Silverlight assembly. (Short Form: /r)
    /collectionType:<type> A fully-qualified or assembly-qualified name of the type to use as a collection data type when code is generated from schemas. (Short Form: /ct)
    /excludeType:<type> A fully-qualified or assembly-qualified type name to exclude from referenced contract types. (Short Form: /et)
    /noStdLib Do not reference standard libraries. By default mscorlib.dll and System.ServiceModel.dll are referenced.
    /serializer:Auto Automatically select the serializer. This tries to use the Data Contract serializer and uses the XmlSerializer if that fails. (Short Form: /ser)
    /serializer:DataContractSerializer Generate data types that use the Data Contract Serializer for serialization and deserialization
    /serializer:XmlSerializer Generate data types that use the XmlSerializer for serialization and Deserialization
    /wrapped Generated code will not unwrap "parameters" member of document-wrapped-literal messages.

    При создании приложения Silverlight можно использовать так называемое "дуплексное" взаимодействие. Это когда сервер вызывает методы клиента, например, для системы мониторинга клиента. Либо, например, клиент вызывает веб-сервис, с помощью которого подписывается на какое-либо событие на сервере. Когда это событие происходит веб-сервис обращается к клиенту и вызывает там какой-нибудь метод. Для реализации этого необходимо на клиенте с помощью средств WCF запустить прослушивание порта TCP. Этого может не допустить файрвол клиента. Реализовать "псевдо-дуплекс" (эмуляция) можно периодически вызывая веб-сервис и опрашивая его на предмет наличия обновлений.

    В Silverlight 3 дуплексное взаимодействие осуществляется с помощью фабрики factory, имеющейся в библиотеке System.ServiceModel.PollingDuplex, состоящей из клиентской *.dll для приложения Silverlight и серверной для веб-сервиса. Реализовать дуплекс в Silverlight 3 гораздо проще и не нужны знания WCF. Библиотеки расположены в каталогах C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Client\System.ServiceModel.PollingDuplex.dll и C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll.

    Чтобы организовать дуплекс, необходимо на веб-сервисе определить контракт с обратным вызовом:

    [ServiceContract(CallbackContract = typeof(…))]
    [OperationContract(IsOneWay = true)]

    Получить вызывающего "клиента"

    OperationContext.Current.GetCallbackChannel<…>();

    Для примера приведится приложение периодически генерирующее GUID, который после генерации передается клиенту и отображается в приложении Silverlight. Ниже приведен код веб-сервиса:

    namespace DuplexCommunication.Web
    {
      [ServiceContract(Namespace = "", CallbackContract = typeof(ICallback))]
      public class DataPushService
      {
        private ICallback client;
        private Timer timer;
    
        [OperationContract(IsOneWay = true)]
        public void Subscribe()
        {
          client = OperationContext.Current.GetCallbackChannel<ICallback>();
          timer = new Timer(
            new TimerCallback(PushUpdateToClient), null, 0, 3000);
          return;
        }
    
        private void PushUpdateToClient(object state)
        {
          try
          {
            client.Update(Guid.NewGuid().ToString());
          }
          catch (TimeoutException)
          {
            timer.Dispose();
          }
          catch (CommunicationException)
          {
            timer.Dispose();
          }
        }
      }
    
      [ServiceContract]
      public interface ICallback
      {
        [OperationContract(IsOneWay = true)]
        void Update(string message);
      }
    }

    Здесь в коде опеределен контракт и указан параметр CallbackContract = typeof(ICallback). Параметр ICallback представляет собой интерфейс, который передает одну переменную string message. Т.е. в этом примере GUID. В методе Subscribe() каждые 3 секунды генерируется новый GUID и в методе PushUpdateToClient он передается клиенту.

    На клиенте добавляется ссылка на веб-сервис, как было показано ранее. Но адрес веб-сервиса нужно указать явно "http://localhost:6652/DataPushService.svc".

    namespace DuplexCommunication
    {
      public partial class MainPage : UserControl
      {
        public MainPage()
        {
          InitializeComponent();
          DataPushServiceClient proxy = 
              new DataPushServiceClient(binding, address);
          proxy.UpdateReceived += 
              new EventHandler<UpdateReceivedEventArgs>(proxy_UpdateReceived);
          proxy.SubscribeAsync();
        }
    
        EndpointAddress address = 
            new EndpointAddress(@"http://localhost:6652/DataPushService.svc");
        CustomBinding binding = new CustomBinding(
        new PollingDuplexBindingElement(),
        new TextMessageEncodingBindingElement (MessageVersion.Soap12WSAddressing10, 
                                               Encoding.UTF8),
        new HttpTransportBindingElement());
    
        void proxy_UpdateReceived(object sender, UpdateReceivedEventArgs e)
        {
          serverResponse.Text = e.message;
        }
      }
    }

    Здесь создается прокси-класс, который "подписывается" на событие proxy.UpdateReceived, proxy.SubscribeAsync(). При возникновении этого события вызывается метод proxy_UpdateReceived, который выводит на экран то, что передает веб-сервис через прокси-класс.

    Подключение приложения Silverlight к веб-сервису REST

    Для доступа к REST сервису приложение Silverlight использует классы HttpWebRequest и WebClient. REST сервис возвращает данные в определенном формате. Если это формат XML, то на стороне сервера используются средства XmlReader / XmlWriter, Linq to XML, XmlSerializer.

    Чтобы вызвать веб-сервис REST надо:

  • Сослаться на веб-сервис Uri serviceUri = new Uri("http://fabrikam.com/service/getUser")
  • В случае, если приложение Silverlight находится в другом домене, чем веб-сервис, надо настроить кроссдоменность (см. ранее).
  • Создать экземпляр класса WebClient downloader = new WebClient()
  • Вызвать асинхронно и подписаться на событие получения ответа downloader.OpenReadCompleted += new OpenReadCompletedEventHandler(downloader_OpenReadCompleted); downloader.OpenReadAsync(serviceUri)
  • Начать обрабатывать результат System.IO.Stream responseStream = e.Result ;
  • Пример с классом WebClient:

    public void HowToMakeRequestsToHttpBasedServices()
    {
      Uri serviceUri = 
          new Uri("http://fabrikam.com/service/getUser");
      WebClient downloader = new WebClient();
      downloader.OpenReadCompleted += 
          new OpenReadCompletedEventHandler(downloader_OpenReadCompleted);
      downloader.OpenReadAsync(serviceUri);
    }
    
    void downloader_OpenReadCompleted(object sender, 
                                      OpenReadCompletedEventArgs e)
    {
      if (e.Error == null)
      {
        System.IO.Stream responseStream = e.Result;
      }
    }

    Таким образом, приложение Silverlight 3 может использовать как SOAP, так и REST веб-сервисы. И делается это довольно просто.

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