Что нового для WCF в Visual Studio 2008 - Добавление ссылки на службу

ОГЛАВЛЕНИЕ

Добавление ссылки на службу

Расширения Visual Studio 2005 для .NET Framework 3.0 обеспечивают базовые возможности добавления ссылок на службы WCF. Многие дополнительные функции SvcUtil в них отсутствуют. В Visual Studio 2008 появилась новое диалоговое окно добавления ссылок на службы (см. рис. 7).

 

Рис. 7 Добавление диалога ссылки на службу

Чтобы открыть окно в проекте, нужно щелкнуть правой кнопкой мыши рабочую область проекта в обозревателе решения и в контектсном меню выбрать пункт Add Service Reference (Добавить ссылку на службу). При этом проект должен быть создан для .NET Framework 3.0 или более поздных версий.

В окне добавления ссылки нужно указать адрес метаданных службы (не адрес службы, как говорится в самом окне) и нажать кнопку Go, чтобы просмотреть список доступных конечных точек служб (а не самих служб, как следует из интерфейса). Затем вы должны указать пространство имен (к примеру MyService), в котором будет содержаться прокси, и нажать кнопку OK, чтобы создать прокси и обновить файл CONFIG. В большинстве случает Visual Studio 2008 не может самостоятельно определить оптимальные значения привязок, поэтому система объявляет для них значения по умолчанию, искажая тем самым содержимое файла CONFIG. Эта проблема будет устранена в будущих выпусках Visual Studio. Если для вас важно сохранить файл CONFIG в приличном виде, перед добавлением ссылки откройте его, добавьте ссылку, потом отмените одно действие (нажмите Ctrl+Z) и вручную добавьте нужные данные в части, относящейся к клиенту.

Кнопка Discover позволяет найти службы WCF в созданном вами решении, если службы размещены в проекте веб-узла или в одной из новых библиотек служб WCF. Если речь идет о веб-узле, Visual Studio 2008 либо получает метаданные от IIS, либо запускает сервер разработки на базе файловой системы ASP.NET. Если речь идет о библиотеке служб WCF, для получения метаданных WCF автоматически запускает размещающий узел (WcfSvcHost).

Кнопка Advanced открывает диалоговое окно, позволяющее настроить создание прокси, причем его функциональность мало отличается от SvcUtil (см. рис. 8). Используя интуитивно понятные параметры, вы настраиваете видимость прокси и контрактов (общие или внутренние); вы можете создать контракты сообщений для типов данных и обеспечить тем самым дополнительные возможности взаимодействия в ситуациях, когда приходится учитывать существующие, зачастую пользовательские, форматы сообщений; кнопка Add Web Reference (Добавить веб-ссылку) позволяет преобразовать ссылку в старый формат ссылок на веб-службы ASMX.

 

Рис. 8 Дополнительные параметры ссылки на службу

Флажок Generate asynchronous operations (Создать асинхронные операции) для каждой операции импортированного контракта добавляет пару элементов Begin<операция> и End<операция>, тем самым позволяя клиентам выполнять асинхронные вызовы рабочих потоков, а впоследствии синхронизировать результаты операции либо путем обратного вызова, либо путем блокировки завершения. К примеру, рассмотрим следующее определение контракта:

[ServiceContract]
interface ICalculator
{
  [OperationContract]
  int Add(int number1,int number2);

Импортированный контракт показан на рис. 9.

Рис. 9 Imported Async Contract

[ServiceContract]
interface ICalculator
{
  [OperationContract]
  int Add(int number1,int number2);

  [OperationContract(AsyncPattern = true)]
  IAsyncResult BeginAdd(int number1,int number2,
  AsyncCallback callback,object asyncState);
  int EndAdd(IAsyncResult result);

  //Rest of the methods

Соответствующих клиентский код для асинхронного вызова будет выглядеть следующим образом:

CalculatorClient proxy = new CalculatorClient();
int sum;
AsyncCallback completion = (result)=>
  {
  sum = proxy.EndAdd(result);
  Debug.Assert(sum == 5);
  proxy.Close(); 
  };
proxy.BeginAdd(2,3,completion,null); 

Эти методы можно использовать как есть, однако обратный вызов в методе Begin<операция> обращается к потоку из пула потоков. Это приводит к возникновению серьезной проблемы в том случае, если обратный вызов используется для доступа к ресурсам, связанным с определенным потоком или потоками. Классическим примером является приложение Windows Forms (или WPF), которое выполняет длительный асинхронный вызов службы (чтобы не блокировать пользовательский интерфейс), а затем пытается обновить интерфейс, добавив в него результаты вызова. Использовать метод Begin<операция> недопустимо, поскольку только поток интерфейса пользователя может обновлять интерфейс. Для решения подобных проблем был расширен базовый класс ClientBase<T>: в него был добавлен метод InvokeAsync, получающий контекст синхронизации от клиента и использующий его для обратного вызова при завершении операции, как показано на рис. 10.

Рис. 10 Async Callback Management in ClientBase<T>

public abstract class ClientBase<T> : ...
{
  protected delegate IAsyncResult BeginOperationDelegate(
  object[] inValues,AsyncCallback asyncCallback,object state);

  protected delegate object[] EndOperationDelegate(IAsyncResult result);
   
  //Picks up sync context used for completion callback 
  protected void InvokeAsync(BeginOperationDelegate beginOpDelegate,
  object[] inValues,
  EndOperationDelegate endOpDelegate,
  SendOrPostCallback opCompletedCallback,
  object userState)
  {}
  //More members
}

В классе ClientBase<T> также имеется вспомогательный класс аргументов события и два выделенных делегата, которые используются для начала и завершения асинхронного вызова. Созданный класс прокси (производный от ClientBase<T>) задействует упомянутые базовые функции. Проски получает открытое событие <операция>Completed, в котором используется сильно типизированный класс аргументов события, непосредственно связанный с результатами выполнения асинхронного метода, и два метода <операция>Async, используемые для асинхронного распределения вызова:

partial class AddCompletedEventArgs : AsyncCompletedEventArgs
{
  public int Result
  {get;}
}

class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
  public event EventHandler<AddCompletedEventArgs> AddCompleted;

  public void AddAsync(int number1,int number2,object userState);
  public void AddAsync(int number1,int number2);

  //Rest of the proxy 

Клиент также может подписать обработчик событий на событие <operation>Completed, чтобы он вызывался по завершении операции. Главная разница в использовании между методами <операция>Async и Begin<операция> состоит в том, что методы <операция>Async получают контекст синхронизации от клиента и вызывают событие <операция>Completed для данного контекста синхронизации, как показано на рис. 11.

Рис. 11 UI-Friendly Asynchronous Call Invocation

partial class CalculatorForm : Form
{
  CalculatorClient m_Proxy;
 
  public MyClient()
  {
  InitializeComponent();

  m_Proxy = new CalculatorClient();
  m_Proxy.AddCompleted += OnAddCompleted;
  }
  void CallAsync(object sender,EventArgs args)
  {
  m_Proxy.AddAsync(2,3); //Sync context picked up here
  }
  //Called on the UI thread
  void OnAddCompleted(object sender,AddCompletedEventArgs args)
  {  
  Text = "Sum = " + args.Result;
  }
}

Поле со списком Collection type (Тип коллекции) позволяет указать способ представления клиенту отдельных видов коллекций и массивов, существующих в метаданных службы. К примеру, если служба возвращает коллекцию IEnumerable<T>, IList<T> или ICollection<T>, по умолчанию прокси представляет ее как массив. В качестве примера можно привести следующий вариант работы на стороне службы:

[OperationContract]
IEnumerable<int> GetNumbers();

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

[OperationContract]
int[] GetNumbers(); 

Однако Visual Studio 2008 можно настроить так, чтобы использовались другие коллекции: BindingList для привязок, List<T>, Collection, LinkedList<T> и т. д. Если преобразование представляется возможным, прокси будет использовать вместо массива требуемый тип коллекции:

[OperationContract]
List<int> GetNumbers(); 

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

[Serializable]
class MyDictionary<K,T> : IDictionary<K,T> 
{...}

[OperationContract]
MyDictionary<int,string> GetDictionary(); 

Затем класс прокси представляется этот словарь в виде Dictionary<T,K> — это значение, установленное по умолчанию в поле со списком Dictionary collection type (Тип коллекции словаря):

[OperationContract]
Dictionary<int,string> GetDictionary();

Однако вы можете запросить и другие типы словарей, например SortedDictionary<T,K>, HashTable или ListDictionary — прокси будет их использовать, если это возможно:

[OperationContract]
SortedDictionary<int,string> GetDictionary();

Наиболее важной возможностью, предоставляемой новой функцией создания ссылок на службы является возможность обмениваться типами контрактов данных между сборками. В Visual Studio 2005, если клиент добавлял ссылки на две независимые службы, поддерживающие один и тот же контракт данных, он получал два отдельных, хотя и совпадающих, типа, представляющих один и тот же контракт. В Visual Studio 2008, если в какой-либо из сборок, на которые ссылается клиент, уже имеется тип контракта данных, совпадающий с типом контракта, указанным в метаданных службы, на которую создана ссылка, то по умолчанию повторно этот тип не импортируется. Следует еще раз подчеркнуть, что существующая ссылка на контракт данных должна существовать в другой сборке, а не в самом проекте клиента. Это ограничение будет снято в будущих выпусках Visual Studio. Пока что лучшим и наиболее очевидным решением является следующее: вы сводите все совместно используемые контракты данных к одной выделенной быблиотеке классов, и все клиенты ссылаются на эту сборку.

Диалоговое окно дополнительных параметров ссылок на службы позволяет настроить совместное использование контрактов. Флажок Reuse types in the referenced assemblies (Повторно использовать типы в сборках, на которые создаются ссылки) установлен по умолчанию, но вы можете его снять. Несмотря на название этой функции, она влияет только на контракты данных, а не на контракты служб. Кнопки-переключатели сразу под этой функцией (см. рис. 8) позволяют настроить Visual Studio 2008 так, чтобы контракты данных использовались во всех сборках, на которые создаются ссылки, или только в определенных сборках (рядом с ними нужно установить флажки).

После добавления ссылки в проекте появляется папка Service References, в которой хранится элемент ссылки для каждой службы, для которой ссылки созданы (см. рис. 12).

 

Рис. 12 Папка ссылок на службу 

Вы в любой момент можете щедкнуть ссылку правой кнопкой мыши и выбрать пункт Update Service Reference (Обновить ссылку на службу), чтобы повторно создать прокси и обновить файл CONFIG для клиента. Это возможно, поскольку в элементе ссылки на службу кроме всего прочего содержится файл, хранящий исходный адрес метаданных.

Пункт Configure Service Reference (Настроить ссылку на службу) открывает окно, схожее с окном дополнительных параметров, доступным во время добавления ссылки. В окне настройки ссылки на службу можно изменить адрес метаданных службы и прочие дополнительные настройки прокси.

Джувел Лоуи — архитектор по программному обеспечению в компании IDesign, проводящий обучение по WCF и дающий консультации по архитектуре WCF. Он также является региональным директором Майкрософт в Силиконовой долине. Недавно вышла в свет книга Джувела «Программирование служб WCF» (Programming WCF Services, O'Reilly, 2007).