Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Конвертер прокси-классов для веб-сервисов
 

Описание задачи

Предположим, мы программируем приложение для работы с клиентами некоторой фирмы. Клиентами фирмы могут быть физические или юридические лица. Соответствующая диаграмма классов предметной области приведена на Рис. 1.

Рис. 1 Диаграмма классов физических/юридических лиц

Есть абстрактный класс BaseBody и два наследника – PrivateBody (физическое лицо), CorporateBody (юридическое лицо). Все лица обладают целочисленными уникальными идентификаторами (атрибут Id на диаграмме), могут иметь несколько телефонов и несколько адресов. Физические лица дополнительно характеризуются фамилией, именем, отчеством, датой рождения. Юридические лица – названием, ИНН, БИК.

Архитектура приложения является многоуровневой и включает в себя: уровень доступа к данным (Data Access Layer), уровень бизнес-логики (Business Logic Layer), фасад веб-сервисов для доступа к методам бизнес-логики посредством SOAP по HTTP (Business Faзade Layer), уровень интерфейса пользователя, реализованный на ASP.Net (User Interface Layer).

Мы рассмотрим особенности построения и использования фасада веб-сервисов в среде .Net, а также некоторые аспекты определения сущностей предметной области (Business Entities), с которыми оперируют различные уровни приложения.

Сущности предметной области можно реализовать различными способами, подробно данный вопрос освещается в статье «Designing Data Tier Components and Passing Data Through Tiers». В нашем учебном приложении сущности реализованы в виде сериализуемых классов следующего вида:

/// <summary>
/// Адрес
/// </summary>
[System.Xml.Serialization.XmlType(Namespace="warner.bodies")]
[Serializable]
public class Address
{
   public Address() {}
 
   public Address(long id, long bodyId, string title)
   {
      this.Id = id;
      this.BodyId = bodyId;
      this.Title = title;
   }
 
   /// <summary>
   /// Идентификатор адреса
   /// </summary>
   public long Id;
 
   /// <summary>
   /// Идентификатор лица, к которому относится адрес
   /// </summary>
   public long BodyId;
 
   /// <summary>
   /// Строка адреса
   /// </summary>
   public string Title;
}
 
/// <summary>
/// Телефон
/// </summary>
[System.Xml.Serialization.XmlType(Namespace="warner.bodies")]
[Serializable]
public class Phone
{
   public Phone() {}
 
   public Phone(long id, long bodyId, string number)
   {
      this.Id = id;
      this.BodyId = bodyId;
      this.Number = number;
   }
 
   /// <summary>
   /// Идентификатор номера телефона
   /// </summary>
   public long Id;
 
   /// <summary>
   /// Идентификатор лица, которому принадлежит номер телефона
   /// </summary>
   public long BodyId;
 
   /// <summary>
   /// Номер телефона
   /// </summary>
   public string Number;
}
 
/// <summary>
/// Базовый класс лица.
/// </summary>
[System.Xml.Serialization.XmlIncludeAttribute(typeof(PrivateBody))]
[System.Xml.Serialization.XmlIncludeAttribute(typeof(CorporateBody))]
[System.Xml.Serialization.XmlType(Namespace="warner.bodies")]
[Serializable]
public abstract class BaseBody
{
   /// <summary>
   /// Идентификатор лица
   /// </summary>
   public long Id;
 
   /// <summary>
   /// Телефоны лица
   /// </summary>
   [XmlArray("Phones")]
   [XmlArrayItem("Phone", Type = typeof(Phone))]
   public Phone[] Phones; 

   /// <summary>
   /// Адреса лица
   /// </summary>
   [XmlArray("Addresses")]
   [XmlArrayItem("Address", Type = typeof(Address))]
   public Address[] Addresses; 
}

/// <summary>
/// Физическое лицо
/// </summary>
[System.Xml.Serialization.XmlType(Namespace="warner.bodies")]
[Serializable]
public class PrivateBody: BaseBody
{
   /// <summary>
   /// Имя
   /// </summary>
   public string FirstName;
 
   /// <summary>
   /// Отчество
   /// </summary>
   public string MiddleName;
 
   /// <summary>
   /// Фамилия
   /// </summary>
   public string LastName;
 
   /// <summary>
   /// Дата рождения
   /// </summary>
   public DateTime Birthday;
}
 
/// <summary>
/// Юридическое лицо
/// </summary>
[System.Xml.Serialization.XmlType(Namespace="warner.bodies")]
[Serializable]
public class CorporateBody: BaseBody
{
   /// <summary>
   /// Название
   /// </summary>
   public string Name;
 
   /// <summary>
   /// БИК
   /// </summary>
   public String BIK;
 
   /// <summary>
   /// ИНН
   /// </summary>
   public String INN;
}
Приведенные выше классы сущностей описаны в отдельной сборке Warner.Bodies.BusinessEntities. Классы помечены как сериализуемые, кроме того, для них заданы атрибуты, управляющие XML-сериализацией. Атрибут XmlTypeAttribute используется для указания пространства имен XML для сериализованного представления объектов соответствующих классов, явное указание пространства имен крайне желательно, об этом подробнее будет сказано позднее. Атрибут XmlIncludeAttribute используется для указания наследников BaseBody.

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

 

 
/// <summary>
/// Уровень доступа к данным
/// </summary>
public class BodiesDAL: IDisposable
{
   public void Dispose() {}
 
   public BaseBody GetBodyById(long id)
   {
      // В реальном приложении здесь было бы обращение к БД
      // У нас учебное приложение, поэтому мы просто
      // вернем некоторый тестовый объект.
 
      if (id < 100)
      {
         PrivateBody body = new PrivateBody();
         body.Id = id;
         body.FirstName = "Иван";
         body.MiddleName = "Иванович";
         body.LastName = "Иванов";
         body.Birthday = DateTime.Now.AddYears(-20);
         body.Addresses = new Address[1];
         body.Addresses[0] = new Address(1, id, "Москва,Кремль");
         body.Phones = new Phone[1];
         body.Phones[0] = new Phone(1, id, "8-095-1111111");
         return body;
      }
      else
      {
         CorporateBody body = new CorporateBody();
         body.Id = id;
         body.Name = "Рога и копыта";
         body.BIK = "28358263923";
         body.INN = "23826932837";
         body.Phones = new Phone[0];
         body.Addresses = new Address[0];
         return body;
      }
   }
}
 
/// <summary>
/// Уровень бизнес-логики
/// </summary>
public class BodiesBL: IDisposable
{
   public void Dispose() {}
 
   public BaseBody GetBodyById(long id)
   {
      using (BodiesDAL bodiesDAL = new BodiesDAL())
      {
         return bodiesDAL.GetBodyById(id);
      }
   }
}
 
/// <summary>
/// Фасад веб-сервисов для операций с физическими/юридическими лицами
/// </summary>
[WebService(Description="Фасад для операций с физическими/юридическими
 лицами", Namespace="warner.bodies")]
public class BodiesFacade : System.Web.Services.WebService
{
   [WebMethod (Description="Получить название или имя лица")]
   public BaseBody GetBodyById(long id)
   {
      using (BodiesBL bodiesBL = new BodiesBL())
      {
         return bodiesBL.GetBodyById(id);
      }
   }
}
Уровень доступа к данным, уровень бизнес-логики и уровень фасада веб-сервисов используют одну и ту же сборку Warner.Bodies.BusinessEntities, оперируя описанными в ней сущностями предметной области.

Уровень интерфейса пользователя (ASP.Net) получает доступ к функциям приложения только через фасад веб-сервисов. В среде Visual Studio .Net веб-сервисы подключаются к приложению с помощью Add Web Reference, при этом VS.Net по WSDL указанного веб-сервиса генерирует модуль, содержащий код прокси-класса, через который будет осуществляться вызов методов веб-сервиса, и код дополнительных классов для представления структур, возвращаемых веб-методами. Например, при подключении к приложению веб-сервиса BodiesFacade.asmx, описанного выше, среда генерирует следующий код:

 

namespace Warner.Bodies.WebApplication.Facades {
   using System.Diagnostics;
   using System.Xml.Serialization;
   using System;
   using System.Web.Services.Protocols;
   using System.ComponentModel;
   using System.Web.Services;
   
   [System.Diagnostics.DebuggerStepThroughAttribute()]
   [System.ComponentModel.DesignerCategoryAttribute("code")]
   [System.Web.Services.WebServiceBindingAttribute(Name="BodiesFacadeSoap", 
      Namespace="warner.bodies")]
   public class BodiesFacade: SoapHttpClientProtocol {

      public BodiesFacade() {
         this.Url = http://localhost/BodiesWebSvcFacade/BodiesFacade.asmx";
      }

      [SoapDocumentMethodAttribute("warner.bodies/GetBodyById",
         RequestNamespace="warner.bodies", ResponseNamespace="warner.bodies",
      Use=System.Web.Services.Description.SoapBindingUse.Literal,
      ParameterStyle= SoapParameterStyle.Wrapped)]
         public BaseBody GetBodyById(long id) {
            object[] results = this.Invoke("GetBodyById", new object[] {id});
            return ((BaseBody)(results[0]));
         }

      public System.IAsyncResult BeginGetBodyById(long id,
         System.AsyncCallback callback,
         object asyncState) {
         return this.BeginInvoke("GetBodyById", new object[] {id}, callback, 
            asyncState);
      }

      public BaseBody EndGetBodyById(System.IAsyncResult asyncResult) {
         object[] results = this.EndInvoke(asyncResult);
         return ((BaseBody)(results[0]));
      }
   }

   [System.Xml.Serialization.XmlTypeAttribute(Namespace="warner.bodies")]
   [System.Xml.Serialization.XmlIncludeAttribute(typeof(CorporateBody))]
   [System.Xml.Serialization.XmlIncludeAttribute(typeof(PrivateBody))]
   public abstract class BaseBody {
   
      public long Id;
   
      public Phone[] Phones;
   
      public Address[] Addresses;
   }
   
   [System.Xml.Serialization.XmlTypeAttribute(Namespace="warner.bodies")]
   public class Phone {
   
      public long Id;
   
      public long BodyId;
   
      public string Number;
   }
   
   [System.Xml.Serialization.XmlTypeAttribute(Namespace="warner.bodies")]
   public class Address {
   
      public long Id;
   
      public long BodyId;
   
      public string Title;
   }
   
   [System.Xml.Serialization.XmlTypeAttribute(Namespace="warner.bodies")]
   public class CorporateBody : BaseBody {
   
      public string Name;
   
      public string BIK;
   
      public string INN;
   }
   
   [System.Xml.Serialization.XmlTypeAttribute(Namespace="warner.bodies")]
   public class PrivateBody : BaseBody {
   
      public string FirstName;
   
      public string MiddleName;
   
      public string LastName;
   
      public System.DateTime Birthday;
   }
}
Отметим, что структура классов BaseBody, PrivateBody, CorporateBody, Address, Phone, сгенерированных VS.Net, в точности соответствует структуре классов, описанных в сборке сущностей Warner.Bodies.BusinessEntities. Тем не менее, с точки зрения компилятора классы, определенные в разных сборках и с разными пространствами имен, не являются эквивалентными, то есть, например, классы BusinessEntities.BaseBody и WebApplication.Facades.BaseBody – это совершенно разные классы. Если мы подключим к приложению с помощью Add Web Reference еще один веб-сервис, оперирующий с теми же сущностями предметной области, то VS.Net уверенно сгенерирует дополнительную копию определений BaseBody, PrivateBody, CorporateBody, Address, Phone.

Предположим, что нам необходимо создать некоторый элемент интерфейса пользователя для отображения информации о клиенте. Например, это может быть User Control - BodyInfoControl.ascx. Сведения о лице передаются контролу через публичное свойство соответствующего типа. Было бы удобно использовать в контроле привязку к классам сущностей, определенным в общей сборке Warner.Bodies.BusinessEntities. С учетом того, что веб-сервис возвращает классы, определенные в модуле, сгенерированном VS.Net, требуется некоторый механизм конвертирования данных из прокси-классов (назовем так сгенерированные по WSDL структуры) в классы сущностей предметной области и наоборот.

Методика решения

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

 

using Facades = Warner.Bodies.WebApplication.Facades;
using Entities = Warner.Bodies.BusinessEntities;

Facades.BodiesFacade facade = new Facades.BodiesFacade();
Facades.BaseBody fBody = facade.GetBodyById(1);
 
Entities.BaseBody eBody;
 
if (fBody is Facades.PrivateBody)
{
   Entities.PrivateBody ePBody = new Entities.PrivateBody();
   Facades.PrivateBody fPBody = fBody as Facades.PrivateBody;
 
   ePBody.FirstName = fPBody.FirstName;
   ePBody.MiddleName = fPBody.MiddleName;
   ePBody.LastName = fPBody.LastName;
   ePBody.Birthday = fPBody.Birthday;
   eBody = ePBody;
}
else if (fBody is Facades.CorporateBody)
{
   Entities.CorporateBody eCBody = new Entities.CorporateBody();
   Facades.CorporateBody fCBody = new Facades.CorporateBody();
 
   eCBody.Name = fCBody.Name;
   eCBody.BIK = fCBody.BIK;
   eCBody.INN = fCBody.INN;
   eBody = eCBody;
}
 
eBody.Id = fBody.Id;
eBody.Phones = new Entities.Phone[fBody.Phones.Length];
for (int i=0; i < fBody.Phones.Length; i++)
{
   eBody.Phones[i] = new Entities.Phone(Body.Phones[i].Id,
      fBody.Phones[i].BodyId,
      fBody.Phones[i].Number);
}
 
eBody.Addresses = new Entities.Address[fBody.Addresses.Length];
for (int i=0; i < fBody.Addresses.Length; i++)
{
   eBody.Addresses[i] = new Entities.Address(fBody.Addresses[i].Id,
      fBody.Addresses[i].BodyId,
      fBody.Addresses[i].Title);
}
 
Видно, что даже для таких простых классов конвертирование потребовало значительного объема кодирования. Если бы приложение использовало несколько веб-сервисов, возвращающих идентичные структуры данных, то для каждого веб-сервиса пришлось бы писать отдельную процедуру конвертирования. Заметим, что подобная методика требует явной проверки типов в случае конвертирования объекта базового класса, имеющего наследников. Удовлетворительным такое решение назвать нельзя.

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

Исходный код класса-конвертора будет иметь следующий вид:

 

 
/// <summary>
/// Конвертор прокси-классов
/// </summary>
public class ProxyTypeConverter
{
   /// <summary>
   /// Конвертирование объекта одного класса в объект другого класса
   /// </summary>
   /// <param name="from">Конвертируемый объект</param>
   /// <param name="to">Класс, в который конвертируется
   /// объект</param>
   /// <param name="useDerivatives">Использовать ли 
   /// наследников</param>
   /// <returns></returns>
   public static object Convert(object from, Type to, 
   bool useDerivatives)
   {
      Type sourceType = from.GetType();
      if (sourceType.Equals(to))
         return from;
 
      // сериализация исходного объекта
      StringWriter writer = new StringWriter();
      PolymorphousXmlSerializer.Serialize(writer, from);
 
      // десериализация содержимого исходного объекта 
      // в конечный тип
      StringReader reader = new StringReader(writer.ToString());
      return PolymorphousXmlSerializer.Deserialize(reader, to,
         useDerivatives); 
   }
 
   /// <summary>
   /// Конвертирование объекта одного класса в объект другого класса
   /// </summary>
   /// <param name="from">Конвертируемый объект</param>
   /// <param name="to">Класс, в который конвертируется
   /// объект</param>
   /// <returns></returns>
   public static object Convert(object from, Type to)
   {
      return Convert(from, to, true);
   }
}
В реализации конвертора используется класс PolymorphousXmlSerializer, предназначенный для полиморфной сериализации/десериализации объектов в XML. Подробно данный класс описан в статье «Полиморфная сериализация объектов в XML», здесь скажем лишь то, что использование этого класса позволяет добиться полиморфного конвертирования. С использованием ProxyTypeConverter конвертирование прокси-класса в класс сущности после вызова веб-сервиса будет выглядеть следующим образом:

 

 
using Facades = Warner.Bodies.WebApplication.Facades;
using Entities = Warner.Bodies.BusinessEntities;

Facades.BodiesFacade facade = new Facades.BodiesFacade();
Entities.BaseBody eBody = (Entities.BaseBody)
   ProxyTypeConverter.Convert(facade.GetBodyById(1), 
   typeof(Entities.BaseBody));
Полученный конвертор удобен и прост в использовании, однако важно помнить о требовании, которое он предъявляет к конвертируемым классам – они должны быть совместимы по XML-сериализации, то есть иметь совершенно одинаковое XML-представление. При использовании в веб-сервисе и в веб-приложении одной и той же сборки (в нашем случае это сборка Warner.Bodies.BusinessEntities) данное требование выполняется автоматически. Единственное замечание – нужно обязательно задавать при описании классов сущностей атрибут XmlTypeAttribute для указания пространства имен, чтобы сгенерированные VS.Net по WSDL веб-сервиса прокси-классы получили то же самое пространство имен, что и классы сущностей.

Заключение

Итак, мы реализовали класс ProxyTypeConverter, позволяющий решать задачу конвертирования прокси-классов в классы сущностей и наоборот. Какие же преимущества мы получаем в результате использования ProxyTypeConverter:

  1. На уровне интерфейса пользователя (User Interface Layer) мы можем использовать те же сборки с описанием классов-сущностей предметной области, что и на других уровнях приложения. Все изменения, вносимые в классы сущностей, автоматически становятся доступными на всех уровнях приложения.
  2. Повторное использование кода в рамках одного проекта и/или между различными проектами значительно упрощается – уровень сложности не повышается с подключением к проекту новых веб-сервисов, даже если они используют одни и те же классы предметной области.
  3. Нет необходимости писать какие-то специализированные конверторы для каждого веб-сервиса, применяется один универсальный ProxyTypeConverter.
Как показала практика использования ProxyTypeConverter в реальных проектах, объем кода приложений существенно уменьшается, а сам код становится проще для понимания, надеюсь, что этот


Может пригодится:


Автор: rsivanov
Прочитано: 8069
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру