Система Orphus
Версия для печати

Реализация работы в Excel по сети на основе XML маппинга.

Автор: Сафонов Семён Геннадьевич
Опубликовано: 09.03.2011
Исправлено: 10.12.2016
Версия текста: 1.0
Введение
Архитектура
Работа с шаблонами.
Работа с XML схемами
Импортирование данных в шаблон
Сохранение изменений внесённых в шаблон и данные
Создание дата контракта
Импорт
Экспорт
Заключение
Литература

Введение

Excel предоставляет возможность добавлять собственные XML схемы (XSD) к рабочим книгам. При добавлении создаётся XML карта, которая даёт дополнительные возможности при работе с данными, а именно импорт и экспорт данных. Таким образом, вы можете загружать данные в документ из хранилища, а затем записывать данные обратно посредством той же XML схемы.

Процесс начинается с добавления XSD файла к рабочей книге Excel. Как только XSD файл прикреплён, Excel создаёт XML карту, которая отображается в панели задач «источник XML» (см рис. 1), которую пользователь использует для позиционирования данных в регионах или отдельных ячейках. Excel использует данную карту для управления взаимодействием между позиционированными регионами и элементами XML схемы. Книга Excel может содержать несколько XML карт, где каждая не будет зависеть от других. Также вы можете иметь несколько карт, которые будут ссылаться на одну схему. Когда пользователь осуществляет импорт или экспорт XML данных, Excel использует карту для связи содержимого позиционированного региона с элементами схемы.


Рисунок 1. Панель «Источник XML»

В данной статье описывается проект, основанный на XML картах, который даёт возможность пользователям иметь общие шаблоны документов, XML схемы и хранилища данных размещенных в интернете. Своего рода Excel 365 своими руками.

Теперь предположим, что мы имеем рабочую книгу Excel содержащую XML карту, с позиционированными регионами и импортированными данными. Если посмотреть на книгу со стороны программиста, то её можно назвать слабосвязанной. Под словом "слабосвязанный" понимается понятие программирования loose coupling, т.е шаблон документа слабо связан с данными документа что даёт возможность делегировать функцию составления документа от разработчика пользователю. Похожий подход можно наблюдать в WPF, где работа над дизайном делегирована от разработчика дизайнеру, чего не было в Win Forms.

Поэтому, проект может применяться и при реализации модуля «вывода отчетов в Excel» в бизнес приложениях. При этом разработчику не обязательно создавать шаблон документа, пользователь сможет сделать это сам.

Архитектура


Рисунок 2.

Данный проект имеет SOA (service oriented architecture) архитектуру. Клиентом является Excel VSTO AddIn, а сервером является WCF сервис. Сервис предоставляет следующую функциональность:

Работа с шаблонами.


Рисунок 3.

Хранилищем шаблонов является директория файловой системы сервера, которая может содержать поддиректории и файлы шаблонов. Для того чтобы просмотреть содержание директории и открыть нужный шаблон используются упрощенная версия сервисного протокола WS-Enumeration и протокол WS-Transfer. Упрощение состоит в том что в WS-Enumeration реализованы только методы Enumerate и Pull.


Рисунок 4. Протокол взаимодействия при работе с шаблонами.

  1. Клиент посылает запрос Enumerate.
  2. На основе параметров полученных от клиента, сервис строит коллекцию и идентификатор коллекции (контекст). Идентификатор отправляется клиенту.
  3. Клиент отправляет запрос Pull, который содержит в качестве параметра идентификатор коллекции и количество элементов которые необходимо вернуть. Так как идентификатор (контекст) является обязательным параметром, то запрос Pull имеет смысл только после удачного запроса Enumerate. Поэтому протокол WS-Enumeration называется протоколом состояний.
  4. В ответе на Pull запрос сервер отправляет данные клиенту.
  5. Клиент продолжает отправлять запрос Pull пока не получит в ответе элемент <EndOfSequence>.
  6. После того как пользователь выбрал элемент коллекции для загрузки отправляется запрос Get.
  7. В ответ сервер присылает экземпляр выбранного элемента коллекции. В нашем случае это может быть шаблон, либо XML схема, либо XML представление данных.

SOAP message Enumerate

Запрос

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/EnumerateTemplate</a:Action>
    <a:MessageID>urn:uuid:02c5cbfb-ae0d-4e96-806f-4f9e43d28d35</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body>
    <EnumerateTemplate xmlns="http://tempuri.org/">
      <request xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Filter />
      </request>
    </EnumerateTemplate>
  </s:Body>
</s:Envelope>

Ответ

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/EnumerateTemplateResponse</a:Action>
    <a:RelatesTo>urn:uuid:30bd2698-c632-4674-a2e1-1f7ad61650ab</a:RelatesTo>
  </s:Header>
  <s:Body>
    <EnumerateTemplateResponse xmlns="http://tempuri.org/">
      <EnumerateTemplateResult xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Context>a91d35d7-604c-4c8a-950c-32a227801b0a</d4p1:Context>
      </EnumerateTemplateResult>
    </EnumerateTemplateResponse>
  </s:Body>
</s:Envelope>

SOAP message Pull

Запрос

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/PullTemplate</a:Action>
    <a:MessageID>urn:uuid:9313cc5b-ccc4-463a-980f-bf5c30fd8e55</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body>
    <PullTemplate xmlns="http://tempuri.org/">
      <request xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Context>fb637a04-cb19-45b6-befc-51208f997ef5</d4p1:Context>
        <d4p1:MaxElements>100</d4p1:MaxElements>
      </request>
    </PullTemplate>
  </s:Body>
</s:Envelope>

Ответ

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/PullTemplateResponse</a:Action>
    <a:RelatesTo>urn:uuid:d3fbd191-9bf1-40ce-90e9-0fa90649ff2e</a:RelatesTo>
  </s:Header>
  <s:Body>
    <PullTemplateResponse xmlns="http://tempuri.org/">
      <PullTemplateResult xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:EndOfSequence>true</d4p1:EndOfSequence>
        <d4p1:Items xmlns:d5p1="http://schemas.datacontract.org/2004/07/Util">
          <d5p1:_d>
            <d4p1:FileSystemInfoDescriptor>
              <d4p1:Extension>.xlsx</d4p1:Extension>
              <d4p1:LastWriteTime>2010-12-05T14:52:15.03125+03:00</d4p1:LastWriteTime>
              <d4p1:Name>Мир_шаблон.xlsx</d4p1:Name>
              <d4p1:Root i:nil="true" />
              <d4p1:Size>12426</d4p1:Size>
              <d4p1:Type>File</d4p1:Type>
            </d4p1:FileSystemInfoDescriptor>
            <d4p1:FileSystemInfoDescriptor>
              <d4p1:Extension>.xlsx</d4p1:Extension>
              <d4p1:LastWriteTime>2010-12-05T15:50:18.84375+03:00</d4p1:LastWriteTime>
              <d4p1:Name>СводнаяВедомость_шаблон.xlsx</d4p1:Name>
              <d4p1:Root i:nil="true" />
              <d4p1:Size>10143</d4p1:Size>
              <d4p1:Type>File</d4p1:Type>
            </d4p1:FileSystemInfoDescriptor>
            <d4p1:FileSystemInfoDescriptor>
              <d4p1:Extension>.xlsx</d4p1:Extension>
              <d4p1:LastWriteTime>2010-12-03T02:18:00.875+03:00</d4p1:LastWriteTime>
              <d4p1:Name>шаблон_1.xlsx</d4p1:Name>
              <d4p1:Root i:nil="true" />
              <d4p1:Size>11245</d4p1:Size>
              <d4p1:Type>File</d4p1:Type>
            </d4p1:FileSystemInfoDescriptor>
          </d5p1:_d>
        </d4p1:Items>
      </PullTemplateResult>
    </PullTemplateResponse>
  </s:Body>
</s:Envelope>

SOAP message GET

Запрос

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/GetTemplate</a:Action>
    <a:MessageID>urn:uuid:fab32bc9-7e02-404a-a8cb-f0225a10a43e</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body>
    <GetTemplate xmlns="http://tempuri.org/">
      <request xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Identifier>СводнаяВедомость_шаблон.xlsx</d4p1:Identifier>
      </request>
    </GetTemplate>
  </s:Body>
</s:Envelope>

Ответ

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IService/GetTemplateResponse</a:Action>
    <a:RelatesTo>urn:uuid:fab32bc9-7e02-404a-a8cb-f0225a10a43e</a:RelatesTo>
  </s:Header>
  <s:Body>
    <GetTemplateResponse xmlns="http://tempuri.org/">
      <GetTemplateResult xmlns:d4p1="http://schemas.datacontract.org/2004/07/DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <d4p1:Resource>UEsDBB...stream...cAueAQABAAKAQAAC8pAAAAAA==</d4p1:Resource>
      </GetTemplateResult>
    </GetTemplateResponse>
  </s:Body>
</s:Envelope>

Открытие документа

Код

        void LLOpen(int iRow)
{
    if (_lst[iRow].Type == "Dir")
        return;

    FileInfo fi = new FileInfo(_workDir + _xpath + "/" + _data[iRow].Name);
    if (fi.Exists)
        fi.Delete();

    using (ChannelFactory<si.IService> serverChannel = new ChannelFactory<si.IService>("cdExcel"))
    {
        serverChannel.Open();
        si.IService theService = serverChannel.CreateChannel();

        DTO.GetResponse res = theService.GetTemplate(new DTO.GetRequest(_data[iRow].Name));

        if (res.Resource != null && res.Resource.Length > 0)
            using (FileStream fs = fi.OpenWrite())
                fs.Write(res.Resource, 0, res.Resource.Length);
    }

    try
    {
        _excel.Workbooks.Open(fi.FullName,
                    Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
    }
    finally
    {
        _view.Close();
    }
}

Работа с XML схемами

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

Код

        void LLAdd(int iRow)
{
    if (_lst[iRow].Type == "Dir")
        return;

    XmlDocument doc = new XmlDocument();
    FileInfo fi = new FileInfo(_workDir + _xpath + "/" + _data[iRow].Name);
    if (fi.Exists)
        fi.Delete();

    using (ChannelFactory<si.IService> serverChannel = new ChannelFactory<si.IService>("cdExcel"))
    {
        serverChannel.Open();
        si.IService theService = serverChannel.CreateChannel();

        DTO.GetResponse res = theService.GetXmlMap(new DTO.GetRequest(_data[iRow].Name));

        if (res.Resource != null && res.Resource.Length > 0)
            using (FileStream fs = fi.OpenWrite())
                fs.Write(res.Resource, 0, res.Resource.Length);
    }
    
    try
    {
        doc.Load(fi.FullName);
        XmlNode node = doc.SelectSingleNode("//d:schema/d:element", _ns);
        string rootElementName = node.Attributes["name"] == null ? "" : node.Attributes["name"].Value;

        if (String.IsNullOrEmpty(rootElementName))
            return;

        foreach (ex.XmlMap map in _excel.ActiveWorkbook.XmlMaps)
            if (map.RootElementName == rootElementName)
                return;

        _excel.ActiveWorkbook.XmlMaps.Add(fi.FullName, rootElementName);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
    }
    finally
    {
        _view.Close();
    }
}

Импортирование данных в шаблон


Рисунок 5.

Типизированные и не типизированные дата контракты.

Для облегчения понимания введём три понятия:

  1. Дата контракт – это структура данных которая описывается XML схемой.
  2. Типизированный дата контракт – это XML схема + CLR тип, который, как правило наследуется от IXmlSerializable и при серилизации которого получается XML документ соответствующий XML схеме.
  3. Не типизированный дата контракт – только XML схема. Экземпляром является XML документ соответствующий данной XML схеме. При этом способ формирования документа не указывается.

Такое разделение полезно для более чёткого разделения клиентской и серверной частей. Так как клиентская часть не должна зависеть от CLR типов, которые используется сервером для формирования XML документов, то клиент должен использовать только не типизированные дата контракты. В противном случае при добавлении нового дата контракта на сервере необходимо будет обновлять сборки и на клиентах, для того чтобы добавить недостающий CLR тип.

ПРИМЕЧАНИЕ

Таким образом клиентская часть оперирует с не типизированными дата контрактами, а серверная с типизированными.

Описание реализации:

Предположим мы имеем БД и мы хотим осуществлять импортирование данных из БД расположенную на сервере в рабочую книгу Excel расположенную на клиенте. Для этого в серверную часть нам необходимо добавить дата контракт:

Dictionary<string, Func<IXmlSerializable[]>> _dictGetDataFuncByName;

public Service()
{
    _dictGetDataFuncByName = new Dictionary<string, Func<IXmlSerializable[]>>();
//добавляем ссылку на функцию для дата контракта с идентификатором     _DATA_CONTRACT_NAME в словарь 
    _dictGetDataFuncByName.Add(_DATA_CONTRACT_NAME, Get_DATA_CONTRACT_NAME);
  ...
}

ПРИМЕЧАНИЕ

Ключ NamespaceName + RootElementName является уникальным идентификатором дата контракта для всей системы (клиент - сервер).

Рассмотрим случай когда клиент хочет импортировать данные в рабочую книгу Excel. Клиент просматривает XML карты прикреплённые к рабочей книге Excel, и для каждой карты отправляет запрос EnumerateData на сервер, в котором в качестве параметра передаёт идентификатор дата контракта. Сервер просматривает реализован ли метод DataContract[] GetDataContractName() для данного дата контракта. Если нет, то ничего не происходит. Если да, то метод GetDataContractName() выполняется и строится коллекция которая передаётся клиенту по протоколу WS-Enumeration.

          public DTO.EnumerateDataResponse EnumerateData(DTO.EnumerateDataRequest request)
{
    string ctx = String.Empty;

    if (String.IsNullOrEmpty(request.Filter))
        thrownew Exception();

  //находим функцию для дата контракта с идентификатором request.Filter
    Func<IXmlSerializable[]> func = _dictGetDataFuncByName[request.Filter];
  //выполняем
    IXmlSerializable[] res = func();

    DTO.EnumerateDataResponse msg = new DTO.EnumerateDataResponse();

  ...     

    return msg;
}
public DTO.PullDataResponse PullData(DTO.PullDataRequest request)
{
    DTO.PullDataResponse msg = new DTO.PullDataResponse();

    lock (_storage)
    {
        if (!_storage.Contains(request.Context))
            thrownew DTO.InvalidEnumerationContextException();

        IEnumerator<IXmlSerializable> e = (IEnumerator<IXmlSerializable>)_storage[request.Context];

        using (MemoryStream ms = new MemoryStream())
        using (XmlTextWriter writer = new XmlTextWriter(ms, Encoding.UTF8))
        {
            writer.WriteStartElement("DataContract");
            writer.WriteAttributeString("Context", request.Context);

            for (int i = 0; i < request.MaxElements; i++)
            {
                if (e.MoveNext())
                {
                    (e.Current as IXmlSerializable).WriteXml(writer);
                }
                else
                {
                    writer.WriteStartElement("EndOfSequence");
                    writer.WriteEndElement();
                    break;
                }
            }

            writer.WriteEndElement();

            writer.Flush();
            ms.Seek(0, System.IO.SeekOrigin.Begin);
            XmlDocument doc = new XmlDocument();
            doc.Load(ms);
            msg.Data = ms.ToArray();
        }
    }
    return msg;
}

В отличии от работы с шаблонами мы не используем WS-Transfer, а передаём экземпляры дата контрактов в ответе на запрос Pull.

Сохранение изменений внесённых в шаблон и данные


Рисунок 6.

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

Если тип удовлетворяет условиям экспорта и реализован метод сохранения данных, то мы можем сохранять изменения произведённые в Excel в БД.

При сохранении сохраняются как изменения шаблона так и изменения данных. Данная операция происходит в два этапа:

Создание дата контракта

Импорт


Рисунок 7.

Предположим мы хотим иметь возможность отображать в Excel структуру данных состоящую из нескольких вложенных списков. Для примера возьмём: Мир->Страны->Области ->Города.

«Мир» является корневым элементом, который содержит список стран. Каждая страна содержит список областей, а каждая область список городов. Карта данных для подобного типа данных будет выглядеть так:

<?xmlversion="1.0"encoding="utf-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
  <element name="Mir">
    <complexType>
      <sequence minOccurs="0" maxOccurs="1">
        <element name="Strana" minOccurs="0" maxOccurs="unbounded">
          <complexType>
            <sequence minOccurs="0" maxOccurs="1">
              <element name="Nazvanie" type="string" />
              <element name="Ploshad" type="string" />
              <element name="Jazik" type="string" />
              <element name="Naselenie" type="string" />
              <element name="Oblast" minOccurs="0" maxOccurs="unbounded">
                <complexType>
                  <sequence minOccurs="0" maxOccurs="1">
                    <element name="Nazvanie" type="string" />
                    <element name="Gorod" minOccurs="0" maxOccurs="unbounded">
                      <complexType>
                        <sequence minOccurs="0" maxOccurs="1">
                          <element name="Nazvanie" type="string"></element>
                        </sequence>
                      </complexType>
                    </element>
                  </sequence>
                </complexType>
              </element>
            </sequence>
          </complexType>
        </element>
      </sequence>
    </complexType>
  </element>
</schema>

Следующим шагом является создание CLR типов, при серилизации которых мы получим Xml документ соответствующий нашей карте. Для создания множественно вложенных структур удобно наследовать тип от IXmlSerializable. В результате для нашего случая получим четыре типа:

        //Аттрибуты типа u.Display используются для информирования клиента о том каким 
        //образом отображать список дата контрактов. В данном случае это будет список 
        //содержащий 3 колонки ID, TheRowVersion, Nazvanie. Каждая строка соответствует 
        //экзкмпляру дата контракта
    [u.Display("Name", "Name", "")]
    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Mir", IsNullable = true)]
    [Serializable]
    publicclass Mir : IXmlSerializable
    {
        #region data
        publicstring Name = "Mir";
        public IList<Strana> Stranas { get; set; }
        #endregion
    
    …  

    }

    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Strana", IsNullable = true)]
    [Serializable]
    publicclass Strana : IXmlSerializable
    {
        #region data
        publicint? ID { get; set; }
        public gt.RowVersion TheRowVersion { get; set; }
        publicstring Nazvanie { get; set; }
        publicint Ploshad { get; set; }
        publicstring Jazik { get; set; }
        publicint Naselenie { get; set; }
        public IList<Oblast> Oblasts { get; set; }
        #endregion

   …

    }

    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Oblast", IsNullable = true)]
    [Serializable]
    publicclass Oblast : IXmlSerializable
    {
        #region data
        publicint? ID { get; set; }
        public gt.RowVersion TheRowVersion { get; set; }
        publicint StranaID { get; set; }
        publicstring Nazvanie { get; set; }
        public IList<Gorod> Gorods { get; set; }
        #endregion
         
        …
    }

    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Gorod", IsNullable = true)]
    [Serializable]
    publicclass Gorod : IXmlSerializable
    {
        #region data
        publicint ID { get; set; }
        publicint OblastID { get; set; }
        publicstring Nazvanie { get; set; }
        #endregion

        …
    }

Для того чтобы получить Xml документ необходимо вызвать метод WriteXml головного объекта:

        using (MemoryStream ms = new MemoryStream())
using (XmlTextWriter writer = new XmlTextWriter(ms, Encoding.UTF8))
{
      (Mir as IXmlSerializable).WriteXml(writer);
       writer.Flush();
       ms.Seek(0, System.IO.SeekOrigin.Begin);
       XmlDocument doc = new XmlDocument();
       doc.Load(ms);
}

На выходе получается вот такой XML документ. Xml данные пересылаются на клиент, после чего импортируются в excel документ.

Помимо прочего необходимо реализовать метод получения данных из базы данных. Это может быть любая база и любой ORM, главное чтобы на выходе получили заполненный экземпляр типа «Mir». В проекте который прилагается к данной статье реализован вариант с MySQL (NHiberbate).

        // Ссылка на этот метод хранится в словаре методов для поддерживаемых дата контрактов. 
        // Когда на сервер приходит запрос EnumerateData с параметром EnumerateDataRequest.Filter = “Mir” вызывается этот метод.
        public Mir[] GetMir()
{
    Mir[] arr = new Mir[1];
    using (var ctx = dal.SessionFactory.OpenSession(CONN))
    {
        IList<Strana> lst = CreateStrana(GetGorodDAOs(ctx),
                                                GetOblastDAOs(ctx),
                                                GetStranaDAOs(ctx));
        
        arr[0] = new Mir { Stranas = lst };    
    }
    return arr;
}

Экспорт


Рисунок 8.

XML схема используемая нами для импорта не может быть использована для экспорта, так как она имеет больше двух вложенных списков. Давайте тогда в качестве примера рассмотрим упрощенный тип Область->Город. XML схема будет выглядеть так:

<?xmlversion="1.0"encoding="utf-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
  <element name="Oblast">
    <complexType>
      <sequence minOccurs="0" maxOccurs="1">
        <element name="StranaID" type="string" />
        <element name="Nazvanie" type="string" />
        <element name="Gorod" minOccurs="0" maxOccurs="unbounded">
          <complexType>
            <sequence minOccurs="0" maxOccurs="1">
              <element name="Nazvanie" type="string" />
            </sequence>
          </complexType>
        </element>
      </sequence>
    </complexType>
  </element>
</schema> 

Клиент экспортирует данные из рабочей книги Excel в XML документ, который пересылается на сервер. На сервере происходит десерилизация. Для того чтобы десерилизация была возможно мы должны добавить соответствующие CLR типы. Мы можем взять их из реализации импорта, так типы остаются прежними:

        //Аттрибуты типа u.Display используются для информирования клиента о том каким 
        //образом отображать список дата контрактов. В данном случае это будет список 
        //содержащий 3 колонки ID, TheRowVersion, Nazvanie. Каждая строка соответствует 
        //экзкмпляру дата контракта
    [u.Display("ID", "ID", "")]
    [u.Display("TheRowVersion", "TheRowVersion", "")]
    [u.Display("Nazvanie", "Nazvanie", "")]
    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Oblast", IsNullable = true)]
    [Serializable]
    publicclass Oblast : IXmlSerializable
    {
        #region data
        publicint? ID { get; set; }
        public gt.RowVersion TheRowVersion { get; set; }
        publicint StranaID { get; set; }
        publicstring Nazvanie { get; set; }
        public IList<Gorod> Gorods { get; set; }
        #endregion
         
        …
    }

    [XmlSchemaProvider(null, IsAny = true)]
    [XmlRoot(ElementName = "Gorod", IsNullable = true)]
    [Serializable]
    publicclass Gorod : IXmlSerializable
    {
        #region data
        publicint ID { get; set; }
        publicint OblastID { get; set; }
        publicstring Nazvanie { get; set; }
        #endregion

        …
    }

Далее необходимо сохранить изменения в базу. Для этого необходимо реализовать метод сохранения данных. В проекте который прилагается к данной статье реализован вариант с MySQL (NHiberbate).
        // Ссылка на этот метод хранится в словаре методов для поддерживаемых дата контрактов. 
        // Когда на сервер приходит запрос SaveData с параметром Name = “Oblast” Вызывается этот метод.
        int SaveOblast(int id, gt.RowVersion theRowVersion, byte[] data)
{
    Oblast s = null;

    using (MemoryStream ms = new MemoryStream(data))
    using (XmlReader reader = XmlReader.Create(ms))
        s = new Oblast(reader);

    if (s == null)
        return -1;

    s.ID = id;
    s.TheRowVersion = theRowVersion;

    return SaveOblast(s);
}

Заключение

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

Разделение документа на шаблон и данные является дополнительной степенью свободы при работе с документами.

Литература

  1. [Bryan Thomas Weikel] Three-tier .NET Application Utilizing Three ORM Technologies
  2. [Roman Kiss] WS-Transfer for WCF (Indigo)
  3. [Sam Safonov] WS-Enumeration for WCF/WF
  4. [Mathias Brandewinder] Excel 2007 VSTO add-in tutorial


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