SharePoint 2010: Примеры работы с BCS

Введение

Предлагаю рассмотреть несколько примеров работы с SharePoint 2010 Business Connectivity Services.

Все ресурсы, связанные с Business Connectivity Services можно найти на Business Connectivity Services Resource Center. Кстати на нём же можно скачать замечательный постер-шпаргалку по технологии.

image

Для работы с рассматриваемыми примерами предлагаю вам скачать SharePoint 2010 SDK.

Adventure Works Web Service

В данном примере показана реализация стандартного asp.net asmx сервиса, ориентированного на работу с BCS. Этот сервис представляет доступ к данным из SQL Server. С помощью LINQ to SQL реализовано объектно-реляционное преобразование.

Вы наверняка согласитесь с утверждением, что при прочих равных условиях asmx сервисы создаются намного проще WCF сервисов

Для работы с ним понадобится sample database с соответствующим названием “AdventureWorks”.

Примеры баз данных (таких как AdventureWorks) для основных версий SQL Server, а так же решений для них, теперь централизованны и хранятся на CodePlex: http://msftdbprodsamples.codeplex.com/

Сервис WebService.asmx предоставляет ряд методов для стандартных операций взаимодействия с данными (Create, Read, Update, Delete).

Для наглядности предметной области основные сущности определены в виде POCO классов, к примеру:

public class SalesCustomer 
{ 
    public int CustomerId { get; set; } 

    public String Title { get; set; } 
    public String FirstName { get; set; } 
    public String MiddleName { get; set; } 
    public String LastName   { get; set; } 
    public String EmailAddress { get; set; } 
    public String Phone { get; set; } 
    public DateTime ModifiedDate { get; set; } 
}

Классы же, генерируемые LINQ to SQL используются по назначению, т.е. в качестве DataModel, которая в последствии отображается на модель предметной области. Хоть пример и учебный, было очень приятно, что он имеет довольно таки логичное и обоснованное применение технологий по назначению.

Предметная область в данном случае состоит из 3 основных сущностей:

  1. SalesCustomer
  2. SalesOrderDetail
  3. SalesOrderHeader

Причём дизайн данных сущностей ориентирован на работу с BCS, это можно проследить по построению ассоциаций между сущностями не по ссылке, а по идентификатору. К примеру, связь между SalesCostumer и SalesOrderHeader:

public class SalesOrderHeader 
{

    public int CustomerID { get; set; }

    …

}


Здесь стоит пояснить механизм работы с ассоциациями в BCS. Ассоциации в BCS – основной механизм связи нескольких “внешних типов содержимого”. Для его реализации используются так называемые Association Methods. По организации работы принцип вторит тому, что применяется в реляционных базах данных для реализации отношений, т.е. через доступ по внешнему ключу (Foreign Key).

В связи с данными особенностями в сервисе определяются специализированные методы, например этот:

[WebMethod] 
public SalesOrderHeader[] GetOrdersForCustomer(int customerId) 
{ 
    List<SalesOrderHeader> salesOrderHeaders = new List<SalesOrderHeader>(); 
    Adventureworks.SalesOrderHeader[] sohArr = 
        (from soh in dataContext.SalesOrderHeaders 
         where soh.CustomerID == customerId 
         select soh).ToArray(); 
    PopulateSalesOrderHeader(salesOrderHeaders, sohArr); 
    return salesOrderHeaders.ToArray(); 
}

Данные методы в дальнейшем будут привязаны к BCS Associations Methods, которые и будут работать для связанных сущностей.

Представленный веб-сервис следует опубликовать в IIS.

Далее, воспользовавшись SharePoint Designer 2010 создать и опубликовать BDC Model:

  • Открыть в SharePoint Designer 2010 интересующий вас узел
  • Перейти в секцию External Content Types
  • Создать новый “внешний тип содержимого”
  • В качестве источника данных выбрать WCF Service
  • Указать URL подключения и тип соединения
  • С помощью мастера произвести отображение всех необходимых веб-методов на методы BDС Model
  • Сохранить внешний тип содержимого

Данный процесс наглядно описан в SDK http://msdn.microsoft.com/en-us/library/ee556431(office.14).aspx

 

Пример файла, описывающего BDC Model
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/windows/2007/BusinessDataCatalog BDCMetadata.xsd" Name="AdventureWorksWSModel" IsCached="false" xmlns="http://schemas.microsoft.com/windows/2007/BusinessDataCatalog">
   <LobSystems>
    <LobSystem Type="Wcf" Name="AdventureWorksWS">
      <Properties>
        <Property Name="WsdlFetchAuthenticationMode" Type="System.String">PassThrough</Property>
        <Property Name="WcfMexDiscoMode" Type="System.String">Disco</Property>
        <Property Name="WcfMexDocumentUrl" Type="System.String">http://webserver:90/webservice.asmx?wsdl</Property>
        <Property Name="WcfProxyNamespace" Type="System.String">BCSServiceProxy</Property>
        <Property Name="WildcardCharacter" Type="System.String">*</Property>
      </Properties>
      <LobSystemInstances>
        <LobSystemInstance Name="AdventureWorksWS">
          <Properties>
            <Property Name="WcfAuthenticationMode" Type="System.String">PassThrough</Property>
            <Property Name="WcfEndpointAddress" Type="System.String">http://webserver:90/webservice.asmx</Property>
            <Property Name="ShowInSearchUI" Type="System.String"></Property>
          </Properties>
        </LobSystemInstance>
      </LobSystemInstances>
      <Entities>
        <Entity Namespace="AdventureWorks” Version="1.0.0.0" EstimatedInstanceCount="10000" Name="WSCustomer" DefaultDisplayName="WSCustomer">
          <Properties>
            <Property Name="OutlookItemType" Type="System.String">Contact</Property>
          </Properties>
          <Identifiers>
            <Identifier TypeName="System.Int32" Name="CustomerId" />
          </Identifiers>
          <Methods>
            <Method IsStatic="false" Name="GetCustomerById">
              <Parameters>
                <Parameter Direction="In" Name="customerId">
                  <TypeDescriptor TypeName="System.Int32" IdentifierName="CustomerId" Name="customerId" />
                </Parameter>
                <Parameter Direction="Return" Name="GetCustomerById">
                  <TypeDescriptor TypeName="BCSServiceProxy.SalesCustomer, AdventureWorksWS" Name="GetCustomerById">
                    <TypeDescriptors>
                      <TypeDescriptor TypeName="System.Int32" ReadOnly="true" IdentifierName="CustomerId" Name="CustomerId" />
                      <TypeDescriptor TypeName="System.String" Name="Title" />
                      <TypeDescriptor TypeName="System.String" Name="FirstName">
                        <Properties>
                          <Property Name="OfficeProperty" Type="System.String">FirstName</Property>
                        </Properties>
                      </TypeDescriptor>
                      <TypeDescriptor TypeName="System.String" Name="MiddleName" />
                      <TypeDescriptor TypeName="System.String" Name="LastName">
                        <Properties>
                          <Property Name="OfficeProperty" Type="System.String">LastName</Property>
                        </Properties>
                      </TypeDescriptor>
                      <TypeDescriptor TypeName="System.String" Name="EmailAddress" />
                      <TypeDescriptor TypeName="System.String" Name="Phone" />
                      <TypeDescriptor TypeName="System.DateTime" Name="ModifiedDate" />
                    </TypeDescriptors>
                  </TypeDescriptor>
                </Parameter>
              </Parameters>
               <MethodInstances>
                <MethodInstance Type="SpecificFinder" ReturnParameterName="GetCustomerById" Default="true" Name="GetCustomerById" DefaultDisplayName="Read Item WSCustomer">
                 <Properties>
                   <Property Name="LastDesignedOfficeItemType" Type="System.String">Contact</Property>
                 </Properties>
                </MethodInstance>
               </MethodInstances>
            </Method>
          </Methods>
        </Entity>
      </Entities>
    </LobSystem>
  </LobSystems>
</Model>

AdventureWorksDll

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

Кстати, данный пример практически ничем не уступает в поддержке актуальности варианту с веб-сервисом, т.к. современные методы дистрибуции .NET решений (к примеру ClickOnce) позволяют поддерживать в актуальном состоянии решение у большого количества клиентов.

Так что же происходит в BCS при работе с веб-сервисами

Механизм вполне логичен и является стандартным во многих ситуациях. Запрашивается URL, который указан в качестве адреса сервиса. На основе полученной схемы (как правило в SOAP это WSDL). Далее происходит самое интересное, в рантайме генерятся прокси-объекты для доступа к данным. Данные объекты компилируются в памяти и кэшируются для дальнейших вызовов.

Custom Connector

Кроме подключения к .NET типу, так же существует возможность реализации “своего коннектора”. В чём же принципиальная разница? В том, что коннектор является свободной абстракцией над источником данных. Т.е. подразумевается его настройка пользователем, к примеру, в SharePoint Designer’e, в отличии от .NET типа, который будет отражаться на конкретный “внешний тип содержимого”. Подробнее можно узнать здесь: http://msdn.microsoft.com/en-us/library/ee554911(office.14).aspx

BCS Cache

В заключение хочу немного рассказать о способах оптимизации ваших решений на основе BCS.

В BCS существует мощных механизм кэширования. Реализуется он на основе так называемых Cache Description. Описываются они в XML файле subscription.xml и должны располагаться в папке вместе в XML описанием BDC Model. С помощью можно описывать правила кеширования определенных методов, определенных сущностей (по их идентификатору), а так же связанных сущностей.

Так выглядит Cache Description

  <?xml version="1.0" encoding="utf-8"?>
<Subscription xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="CustomerQuery Item List" IsCached="true" EntityName="Customer" EntityNamespace="http://example.com" RefreshIntervalInMinutes="60" View="CustomerRead Item" LobSystemInstanceName="AdventureWorks" xmlns="http://schemas.microsoft.com/office/2006/03/BusinessDataCatalog">
  <Queries>
    <Query Name="27aa2ba2-fd5f-452c-87bb-24cf505f6071" IsCached="true" RefreshIntervalInMinutes="60" MethodInstanceName="CustomerQuery Item List" Enabled="false" />
  </Queries>
</Subscription> 

Подробнее о кэшировании можно узнать здесь: http://msdn.microsoft.com/en-us/library/ee556385(office.14).aspx


Может ли .NET Reflector 6 помочь разработчику SharePoint?

Сегодня наконец-то дошли руки до Reflector 6 Pro Beta. Если вы ещё не слышали, то я повторюсь, что эта версия рефлектора умеет дебажить внешние сборки в Visual Studio. Конечно же меня в первую очередь заинтересовала возможность дебага Microsoft.SharePoint.dll

При первом запуске рефлектора, он автоматически установит плагин в студию. Далее с помощью появившегося пункта меню можно подгрузить интересующую сборочку:

image

Microsoft.SharePoint.dll загружался довольно таки приличное количество времени (несколько минут), после чего передо мной была следующая картинка:

image

Далее я выбрал в том же пункте меню Explore Decompiled Assemblies, нашёл в Object Browser класс SPWeb, выбрал в меню Go To Decompiled Definition, подключился к рабочему процессу w3wp с SharePoint и увидел следующее:

image

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


Обработка ошибок со стороны пользователя

Сегодня немного поразмышлял о напрасно потраченных человекогодах, а именно о следующем.

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

image

Причём, для “агностицизма” поисковика, можно детектить тот, который стоит по-умолчанию в вашем браузере.


SharePoint 2010: Безопасность в Business Conectivity Services

Введение

Существует два подхода к аутентификации при работе с внешними источниками данных:

  • идентификатор пользователя
  • имперсонализация

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

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

Явным примером двух типов аутентификации может являться работа с SQL Server для развертывания SharePoint.

При наличии у разработчика локально-развернутой копии SharePoint, получение данных происходит через идентификатор текущего пользователя, т.к. разработчик обычно выполняет все действия под единым для SharePoint и SQL Server пользователем.

Однако при варианте развертывания SharePoint на ферме, появляется необходимость имперсонализации пользователя для доступа к данным из SQL Server. Как правило заводится специализированная доменная учетная запись с необходимыми для работы с SQL Server разрешениями. И соответственно, все действия с данными SharePoint производит именно через неё, независимо от того, какой пользователь в данное время аутентифицирован в нём.

Рассмотрим, как же организовать имперсонализацию при работе с внешними источниками данных в SharePoint 2010.

Single Sign-On или Secure Stored Service

Вы наверняка знаете, что такое SSO (single sign-on), и помните, что в предыдущих версиях SharePoint она называлась именно так. В SharePoint 2010 данная служба сменила своё название, теперь она называется Secure Store Service (русский эквивалент: Служба Безопасного Хранения).

Данная служба занимается проверкой подлинности на сервере приложений, если быть точным, то она обеспечивает взаимодействие пользователей/групп пользователей с различными системами без необходимости повторного входа в систему. Основное назначение данной службы в SharePoint – взаимодействие с внешними системами. Если в вашей организации имеются приложения, данные из которых вы хотели бы предоставить для пользователей SharePoint, а так же обеспечить безопасность доступа, то вы должны воспользоваться SSS (Secure Store Service).

При настройке SSS вы указываете отображение данных пользователя SharePoint на данные, передаваемые внешней системе. Данными параметрами могут Имя пользователя, Пароль, Идентификатор пользователя. Набор полей не ограничен и может быть изменен в зависимости от ваших нужд. Для обеспечения взаимодействия с настольными приложениями так же доступны сервисы определения разрешений на основе идентификатора Windows пользователя.

Настройка SSS

Secure Stored Service представляет собой сервис аутентификации на сервере приложений, а так же базу данных, содержащую информацию о разрешениях пользователей. Как и SSO, он поддерживает федеративную аутентификацию, а так же имперсонализацию и делегирование.

На основе хранимых соответствий во время подключения внешних источников данных каждому пользователю назначаются соответствующие

Кстати, в отличии от SSO и BDC, который был доступен только в MOSS, Secure Stored Service и Business Connectivity Services будут доступны и в SharePoint Server 2010 и в SharePoint Fundation 2010

SSS можно включить в SharedServices. По-умолчанию данный сервис отмечен галочкой в мастере настройке фермы, так что велика вероятность того, что у вас он уже установлен.

Создадим Secure Store Application, для этого нам понадобится:

  1. Пройти к Central Administration — Application Management — Manage Service Applications
  2. Перед созданием приложения необходимо так же сгенерировать для него ключик:
  3. Далее вам необходимо будет указать администратора и настройки приложения, а так же отображаемые поля (по-умолчанию они называются Windows User Name, Windows Password, название не несёт в себе никакой нагрузки, кроме описательной, в связи с чем можно переименовать их просто в UserName и Password, чтобы не путаться).
  4. В конце создания укажите разрешения для приложения:

Вот мы и создали приложение для безопасного хранения, зачем же оно нам может понадобиться?

Рассмотрим пример со сторонним приложением, данные которого хранятся в SQL Server, однако по историческим причинам в нём заведены для доступа отдельные учетные записи. Перед вами стоит задача организовать приложение на BCS для доступа к этим данным.

Для этого вам необходимо:

  1. Запустить SharePoint Designer 2010
  2. Перейти к секции External Content Types
  3. Создать новый тип содержимого
  4. В качестве источника данных указать SQL Server
  5. В окне доступа к данным уточнить наименование сервера, наименование базы данных, так же имя для отображения. Так же вам потребуется выбрать способ имперсонализации (в данном случае на основе Windows Identity) и указать идентификатор (его наименование, которое вы указывали при его создании) Secure Store Application

 

Если соединение удастся, то вы всё сделали верно, остальные действия ничем не будут отличаться от стандартных, а так же будут действовать все указанные вами в Secure Store Application правила.

Давайте также разберемся с терминологией SharePoint аутентификации

Users Identity (PassThrough)

Пользователь, прошедший идентификацию обращается к BCS со своим идентификатором. Это обозначает, что Windows идентификатор будет передан напрямую через BCS приложение к IIS, который в свою очередь отправит его на SQL Server (к примеру), где в зависимости от его разрешений будут предоставлены данные.

BDC Identity (RevertToSelf)

Как не трудно догадаться в данном случае доступ к внешнему источнику данных будет осуществляться под идентификатором пользователя пула приложения (под ним выполняются службы BCS). Соответственно вам необходимо предоставить доступ к данным для этой учетной записи.

Impersonated Windows Identity (WindowsCredentials)

Под данным термином и подразумевается SSS аутентификация.

Impersonated Custom Identity

В основном под данным пунктом подразумевается Claims based Federated Authentication. К примеру поддерживающий SAML аккаунты пользователей

SAML (англ. Security Assertion Markup Language — язык разметки подтверждения безопасности) — основанный на языке XML стандарт, разработанный OASIS для обмена данными об аутентификации и авторизации между защищенными доменами. Одной из важных проблем, которую пытается решить SAML это обеспечение сквозной аутентификации (Технология единого входа, Single Sign On) при работе через Web-браузер.

Данный тип аутентификации так же весьма подходит для WCF сервисов, позволяющих передавать резрешения в Secure Token Service.

Если вы будете писать,  к примеру настольное приложение для работы с BCS, то данный вид аутентификации будет вам весьма полезен. Аналогично работают и Office-клиенты (Outlook, SharePoint Workspace). Каждый клиент обращается к Security Token Service на ферме SharePoint, где получает ”доверенный идентификатор”, с которым впоследствии отправляется в BCS.

RdbCredentials

Этот тип аутентификации используется для обработки идентификатора пользователя, полученного из не-Windows среды с помощью SSS.

Разрешения в BCS

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

Напомню, что основными элементами описания в BCS являются:

  • Metadata store – фактически все описания, содержащиеся в BCS
  • Model – описание так называемой BDC Model, может содержать несколько внешних типов содержимого, связи с внешними данными, а так же типаутентификации
  • External system – источник данных (база данных, веб-сервис, .NET сборка)
  • External content type – внешний тип содержимого
  • Method – операция над типом содержимого
  • Method instance – настройка метода, к примеру параметры по-умолчанию

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

В SharePoint BCS существуют 4 типа разрешений:

  1. Edit – практически на всех уровнях иерархии позволяет редактировать элементы, а так же создавать новые
  2. Execute – как правило определяет какие методы могут быть вызваны
  3. Selectable in clients – означает, что пользователь может создавать на основе данного типа содержимого списки
  4. Set permissions – пользователь может указывать разрешения для других пользователей на данном элементе

Так же существуют специализированные группы разрешений для SharePoint:

  • Администртор фермы – привилегированный пользователь BCS, имеет полный набор прав по-умолчанию. Данные полномочия предоставлены для возможности восстановления прав. Однако администратор фермы не имеет прав Execute, пока ему их не предоставит администратор экземпляра BDC
  • Клиенты объектной модели – сюда попадают скриптовые сценарии (PowerShell, IronPython, STSADM Extensions), а так же приложения, написанные на .NET (C#, VB). Они так же имеют полный набор разрешений
  • Пользователи, под которыми запущены пулы приложений на серверах приложений – имеют те же права, что и администраторы ферм, это связанно, с тем, что есть необходимость в разрешениях для установки определений
  • Пользователи SharePoint Designer – имеют, как правило, полный набор прав, за исключением предоставления прав (set permissions), хотя могут убирать права

Редактировать разрешения можно через SharePoint Designer, узел администрирования, редактируя описание BDC Model’и, а так же через объектную модель. Первые два средства соответственно не дают возможностей тонкой настройки.

Материалы

http://www.lightningtools.com/blog/ – блог о SharePoint BDC, BCS от создателей BDC MetaMan и MCS MetaMan

http://technet.microsoft.com/en-us/library/ee681491(office.14).aspx – планирование BCS на Technet


В эту пятницу (22.01) RUSUG

Первая встреча RUSUG в Москве в новом году состоится 22 января.

Местонахождение: Офис «Microsoft Russia» в Москве

План встречи:

19:00 — доклад «Пользуемся готовыми решениями сообщества для SharePoint», Виталий Баум;
20:30 — перерыв;
20:45 — доклад «PowerShell и SharePoint», Василий Гусев.
Регистрация: http://sharepoint.su/UG/Lists/Jan2010Reg/NewForm.aspx

На эту встречу к нам приедет Виталий Баум из Санкт-Петербурга. Если вы смотрите видеозаписи встреч группы, то уже видели один доклад Виталия, посвящённый введению в SharePoint. Тема доклада Виталия «Пользуемся готовыми решениями сообщества для SharePoint».

Он расскажет нам о следующем:

  • Почему надо использовать решения с CodePlex
  • Почему не надо использовать решения с CodePlex
  • Как надо использовать решения с CodePlex
  • Обзор доступных решений от сообщества для SharePoint

Вторым докладчиком будет Василий Гусев, известный эксперт, MVP по PowerShell. Он расскажет нам о связке PowerShell и SharePoint. После краткого знакомства с основами PowerShell вы узнаете что он может дать разработчику, и как можно его использовать для работы с SharePoint.


Совместный подкаст spbalt.net и csharpus

Встречались:

http://spbalt.net : butaji и dnesteruk

http://csharpus.com/ : dimapasko и tihobrazov

Холиварим, .NET и аналоги:

http://highscalability.com/blog/2009/8/5/stack-overflow-architecture.html

http://stackoverflow.com/questions/791447/windows-azure-vs-amazon-ec2-vs-google-app-engine

 

Обсуждение новых фич .NET 4.0, в частности:

 

Подробнее о подкастах:

Подкасты Петерубргской Группы Alt.Net
Подкасты Петерубргской Группы Alt.Net

Подкасты о разработке в среде .Net. Ключевые слова: C#, F#, Boo, Visual Studio, .Net, PostSharp, Asp.Net
личная подкаст-лента
Петербургская Группа Alt.NetПетербургская Группа Alt.Net (подробнее, RSS-поток)

 
csharpus podcast

Подкаст о разработке на платформе .NET
личная подкаст-лента
Dima PaskoDima Pasko (подробнее, RSS-поток)


Unit-тесты для SharePoint

Снова о unit-tests?


Да, я уже рассказывал о том, как “правильно” писать юнит-тесты для SharePoint, однако сегодня наткнулся на одну интересную разработку, речь о которой пойдёт далее.

В состав Pex входит инструментарий для создания stub-объектов под названием Mole, он умеет работать с закрытыми для наследования объектами, на коих строится объектная модель SharePoint.

Если вы не знаете что такое Pex

В компании Microsoft есть подразделение, занимающееся научными разработками, называется оно Microsoft Research, подробнее узнать о том, чем же занимается это подразделение можно узнать здесь: http://research.microsoft.com/en-us/research/default.aspx

Одно из разработок данного подразделения – средство white-box тестирования для .NET под названием Pex. Разработка доступна для скачивания: http://research.microsoft.com/en-us/downloads/d2279651-851f-4d7a-bf05-16fd7eb26559/.

Конечно же концептуально Pex противоречит некоторым аспектам юнит-тестирования, т.к. проверяет как написан код, а не что он должен делать.

Насколько известно для SharePoint удобно писать лишь интеграционные тесты, которые помогут вам убедится, в том, что решение благополучно “прилунилось” и проверить, что вся необходимая логика исполняется на “живых” объектах SharePoint.

Однако во многих случаях хочется избавиться от всех инфраструктурных накладок интеграционного тестирования (производительность: я не хочу уходить пить кофе каждый раз при запуске тестов, обновление данных и проч.) и проводить полноценное модуль тестирование в изолированной среде, что обычно и позволяют делать многочисленные фреймворки для создания “тестовых двойников”, примеры для .NET:

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

Пример работы с Mole

Предположим нам необходимо протестировать некоторый участок кода:

public void UpdateTitle(SPItemEventProperties properties) {
    using (SPWeb web = new SPSite(properties.WebUrl).OpenWeb()) {
        SPList list = web.Lists[properties.ListId];
        SPListItem item = list.GetItemById(properties.ListItemId);
        item["Title"] = item["ContentType"];
        item.SystemUpdate(false);
    }
}

Для этого нам необходимо будет произвести ряд действий для генерации тестового проекта, которые я опущу (они подробно описаны в литературе на которую я ссылаюсь). Больше всего интересует код, который будет заниматься э(mole)яцией типов SharePoint:

string url = "http://foo";
// с помощью рефикса MS мы подскажем исполняющей среде, что данный тип э(mole)ирует // SPItemEventProperties и что по вызову свойства WebUrl
// (заметьте что Get свойства опять же опрделен специфичностью синтаксиса) необходимо // будет вернуть заданную нами строку
var properties = new MSPItemEventProperties {
    WebUrlGet = () => url
};

Аналогично и для остальных объектов:

MSPSite.ConstructorString = (site, _url) => {
    new MSPSite(site);
};

new MSPSite(site) {
    OpenWeb = () => new MSPWeb()
};

Так будет выглядеть пример Assert’ов

ListsGet = () => new MSPListCollection {
    ItemGetGuid = id => {
        Assert.IsTrue(listId == id);
        return new MSPList();
    }
}

Думаю данных примеров будет достаточно, для того, чтобы понять, что достойной альтернативы TypeMock Isolator пока нет, однако, с точки зрения сравнения подходов к решению проблемы с промышленной и академической стороны Mole очень интересен.

Материалы

Статья об использовании Mole: http://research.microsoft.com/en-us/projects/stubs/stubstutorial.pdf

Статья о тестировании SharePoint с помощью Mole и Pex: http://research.microsoft.com/en-us/projects/pex/pexsharepoint.pdf


Spb ALT.NET Group: The Zero Podcast

Подкасты Петерубргской Группы Alt.Net

Подкасты о разработке в среде .Net. Ключевые слова: C#, F#, Boo, Visual Studio, .Net, PostSharp, Asp.Net

личная подкаст-лента Петербургская Группа Alt.NetПетербургская Группа Alt.Net (подробнее, RSS-поток)

Обсуждаем
Итоги 2009 года:

  • 17 встреч http://spbalt.net
  • Инета в Питере
  • Рынок труда в Питере

Новости 2010:

  • 10 лет .NET
  • Visual Studio 2010
  • Что и как будем использовать в 2010ом году

Говорили в обычном составе: butaji и dnesteruk


SubSonic: магия и ORM

О чём это?

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

  • ASP.NET MVC
  • SQLite

Набросав первое приближение модели предметной области, я озаботился её сохраняемостью и решил заняться выбором ORM:

  • LINQ to SQL насколько известно умеет работать только с Microsoft SQL Server
  • Entity Framework ещё не готов в пригодной версии
  • DBLinq вроде бы прекрасно работает со всеми известными RDBMS, однако отсутствие его популярности и низкая версия немного меня оттолкнули
  • О SubSonic я слышал краем уха, однако так и не имел тесного знакомства, на нём и решил остановится, фреймворк имеет в багаже уже 3юю версию, а так же известного создателя (Rob Conery)

Чем же хорош SubSonic?

SubSonic

Очень заинтересовали 5 минутные ролики о SubSonic:

http://subsonicproject.com/docs/The_5_Minute_Demo

http://subsonicproject.com/docs/Simple_Repo_5_Minute_Demo

Кстати SubSonic помимо того, что является ORM, так же предоставляет слой доступа к данным.

Итак в 3ей версии SubSonic мы имеем 2 сценария работы:

  • ActiveRecord – фактически повторяет классический для Ruby On Rails подход, в котором модель является моделью данных, т.е. знает о способах своей сохраняемости. Данный подход в случае SubSonic полезен в построении дата-ориентированных решений, вам достаточно просто сохранить в T4 шаблоне имя строки подключения к имеющейся у вас базе данных, остальное SubSonic сделает сам (генерирует соответствующие данным .NET-типы в вашем приложении)
  • SimpleRepository – более привычный для меня подход, подразумевает под собой абстракцию хранения данных под некоторым классом-коллекцией объектов.

Реализация обоих сценариев прозрачна с точки зрения подхода и реализует соответствующие паттерны PoEAA:

Как же я воспользовался SubSonic в своём приложении?

Я о определил интерфейс репозитория:

  public interface IRepository<T>
  {
    void Add(T entity);
    T FetchById(long id);
    IEnumerable<T> FetchAll(Expression<Func<T, bool>> predicate);
    IEnumerable<T> FetchAll();
    void Update(T entity);
    void Remove(T entity);
  }

Имплементация его имела следующий вид:

  public class EntityRepository : Domain.IRepository<Entity>
  {
    readonly IRepository _db;

    public ComputersRepository()
    {
      _db = SimpleRepositoryFactory.Create();
    }

    public void Add(Entity entity)
    {
      _db.Add(entity);
    }

    public Computer FetchById(long id)
    {
      return _db.Single<Entity>(x => x.Id == id);
    }

    public IEnumerable<Computer> FetchAll(Expression<Func<Entity, bool>> predicate)
    {
      return _db.Find(predicate);
    }

    public IEnumerable<Computer> FetchAll()
    {
      return _db.All<Entity>();
    }

    public void Update(Entity entity)
    {
      _db.Update(entity);
    }

    public void Remove(Entity entity)
    {
      _db.Delete<Entity>(entity.Id);
    }
  }

Как вы успели заметить производством SimpleRepository занимается фабрика:

  public class SimpleRepositoryFactory
  {
    public static SimpleRepository Create()
    {
      IDataProvider dataProvider = ProviderFactory.GetProvider(ConnectionString, "System.Data.SQLite");
      return new SimpleRepository(dataProvider, SimpleRepositoryOptions.RunMigrations);
    }

    protected static string ConnectionString
    {
      get
      {
        return "Data Source=data.db;Version=3;";
      }
    }
  }

Кстати, это всё, что нам понадобится, для работы со слоем сохранения. Давайте теперь разберёмся, что же именно будет делать SubSonic. Фактически он возмёт на себя все задачи по генерации базы данных и всех таблиц. Так же он предоставит все механизмы для доступа по-средствам Linq. Однако стоит иметь ввиду, что SubSonic SimpleRepository не умеет работать со сложными типами и ссылками на другие типы (однако это проблема решается благодаря Join’ам в запросах)

Интересной возможностью является возможность работы с «пакетами» данных:

IEnumerable<Post> posts=GetABunchOfNewPosts();
repo.AddMany(posts);
 
//update a post
repo.Update(post);
 
//update a bunch of posts in a transaction
IEnumerable<Post> posts=GetABunchOfNewPosts();
repo.UpdateMany(posts);
 
//delete a post
repo.Delete<Post>(key);
 
//delete a lot of posts
repo. DeleteMany <Post>(x=>x.Title.StartsWith("M"));
 
//delete posts using a transaction
IEnumerable<Post> posts=GetABunchOfNewPosts();
repo.DeleteMany(posts);

Проблема с пейджингом так же решена из коробочки:

//a PagedList of posts - using 10 per page
var posts=repo.GetPaged<Post>(0,10);
//sort by title
var posts=repo.GetPaged<Post>("Title",0,10);

Диагноз

Рекомендую SubSonic как лучшее решения доступа к данным для прототипирования


Расширяем возможности SharePoint ListFieldIterator

Введение

Если вы занимаетесь разработкой под SharePoint, то наверняка вам знаком этот элемент управления (ListFieldIterator). В настоящее время передо мной стоит задача, решение которой я решил провести через расширение стандартных возможностей данного контрола.

Если же вы не совсем понимаете о чём идёт речь, то я готов рассказать о ListFieldIterator

Этот контрол предназначен для отображения атрибутов ваших записей для чтения и редактирования на формах:

image

Насколько известно, отображение записей (я имею ввиду под “запись” SPListItem) регулируется с помощью списка (SPList), либо типа содержимого (SPContentType), для этого необходимо указать в свойствах DisplayFormTemplateName, EditFormTemplateName, NewFormTemplateName наименование необходимого шаблона. Сам же шаблон должен находится в файле: 12\TEMPLATE\CONTROLTEMPLATES\DefaultTemplates.ascx

Описание стандартного шаблона выглядит следующим образом:

<SharePoint:RenderingTemplate ID="ListForm" runat="server"> 
  <Template> 
    <span id='part1'> 
      <SharePoint:InformationBar ID="InformationBar2" runat="server" /> 
      <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&nbsp;" 
        runat="server"> 
        <template_rightbuttons> 
                        <SharePoint:NextPageButton runat="server"/> 
                        <SharePoint:SaveButton runat="server"/> 
                        <SharePoint:GoBackButton runat="server"/> 
                    </template_rightbuttons> 
      </wssuc:ToolBar> 
      <SharePoint:FormToolBar ID="FormToolBar2" runat="server" /> 
      <table class="ms-formtable" style="margin-top: 8px;" border="0" cellpadding="0" cellspacing="0" 
        width="100%"> 
        <SharePoint:ChangeContentType ID="ChangeContentType2" runat="server" /> 
        <SharePoint:FolderFormFields ID="FolderFormFields2" runat="server" /> 
        <SharePoint:ListFieldIterator ID="ListFieldIterator2" runat="server" /> 
        <SharePoint:ApprovalStatus ID="ApprovalStatus2" runat="server" /> 
        <SharePoint:FormComponent ID="FormComponent2" TemplateName="AttachmentRows" runat="server" /> 
      </table> 
      <table cellpadding="0" cellspacing="0" width="100%"> 
        <tr> 
          <td class="ms-formline"> 
            <img src="/_layouts/images/blank.gif" width="1" height="1" alt=""> 
          </td> 
        </tr> 
      </table> 
      <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 7px"> 
        <tr> 
          <td width="100%"> 
            <SharePoint:ItemHiddenVersion ID="ItemHiddenVersion2" runat="server" /> 
            <SharePoint:ParentInformationField ID="ParentInformationField2" runat="server" /> 
            <SharePoint:InitContentType ID="InitContentType2" runat="server" /> 
            <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&nbsp;" 
              runat="server"> 
              <template_buttons> 
                        <SharePoint:CreatedModifiedInfo runat="server"/> 
                    </template_buttons> 
              <template_rightbuttons> 
                        <SharePoint:SaveButton runat="server"/> 
                        <SharePoint:GoBackButton runat="server"/> 
                    </template_rightbuttons> 
            </wssuc:ToolBar> 
          </td> 
        </tr> 
      </table> 
    </span> 
    <SharePoint:AttachmentUpload ID="AttachmentUpload2" runat="server" /> 
  </Template> 
</SharePoint:RenderingTemplate>

Этот шаблон, под названием “ListForm”, используется всеми стандартными списками по-умолчанию. Как не трудно заметить в данном шаблоне как раз и описан элемент управления ListFieldIterator.

Первоначально продемонстрирую результат, который мы получим по завершению данной статьи:

image

Итак, приступим к изменению представления карточки, чтобы получить возможности:

  • Разделения полей на области, что порой востребовано
  • Регулирование отображения количества полей в строке
  • Регулирование правил отображения полей в зависимости от состояния и текущего пользователя

Во-первых создадим свой класс, в моём случае это будет FieldSetFieldIterator:

 
public class FieldSetFieldIterator : ListFieldIterator
{}

Воспользовавшись рефлектором и предварительно ознакомившись в ListFieldIterator’ом я пришёл к выводу, что основная логика формирования шаблнов представления полей для их последующего вывода основана на коллекции доступных полей

 
SPContext.Current.FormContext.FieldControlCollection

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

 
protected override bool IsFieldExcluded(Microsoft.SharePoint.SPField field)
{
    return base.IsFieldExcluded(field);
}

Не трудно догадаться, что переопределение данного метода мне так же импонирует.

Самая сложная часть реализации нашего контрола – переопределение логики представления, а именно метода

 
protected override void Render(HtmlTextWriter output)
{
    foreach (var fieldSet in fieldSets)
    {
       GenerateTableStart(fieldSet, output);
       GenerateTableBody(fieldSet, configurationFieldsSets.Rows, output);
       GenerateTableEnd(fieldSet, output);
     } 
}

Для каждого из FieldSet’ов (это условное название для обозначения регионов отображения полей, по мотивам HTML FieldSet) рисуется табличка, котрое в последствии заполняется описанными в конфигурационном файле полями в соответствующих состояниях.

Состояние поля, так же как и состояние страницы определяется перечислением:

 
public enum SPControlMode
{
    Invalid,
    Display,
    Edit,
    New
}

Соответственно, в наших силах получив элемент BaseFieldControl для текущего поля, изменить его состояние на необходимое нам.

Конфигурирование элемента

Конфигурация данного контрола реализуется через интерфейс, соответственно даёт гибкость в реализации

 
public interface IFieldSetConfigurator : IFieldConfigurator
{
  FieldsConfiguration FieldsConfiguration { get; }
}

В настоящее время конфигурационные данные будут хранится в описании типа содержимого в разделе XmlDocuments

 

<XmlDocuments>
  <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
    <FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
      <Display>FieldSetListForm</Display>
      <Edit>FieldSetListForm</Edit>
      <New>FieldSetListForm</New>
    </FormTemplates>
    <Configuration>
      <FieldsConfiguration>
        <FieldsSets Rows='3'>
          <FieldSet Name='Основные'>
            <Field InternalName='Title' />
            <Field InternalName='StartDate' DisableForUsers='domain/user1'/>
            <Field InternalName='Author' />
            <Field InternalName='_ModerationComments' />
            <Field InternalName='Gender' />
            <Field InternalName='PercentComplete' />
            <Field InternalName='URL' />
          </FieldSet>
          <FieldSet Name='Дополнительные'>
            <Field InternalName='Editor' />
            <Field InternalName='WorkFax' />
          </FieldSet>
        </FieldsSets>
      </FieldsConfiguration>
    </Configuration>
  </XmlDocument>
</XmlDocuments>

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

Стоит так же не забыть, что при переопределении ListFieldIterator’а, при работе с вложениями будет нарушено единообразие формы, т.е. этот элемент так же необходимо переопределить:

 
 public class FieldSetFormComponent : FormComponent
  {
    public const string AttachmentTemplateName = "AttachmentRows";
    public const string AttachmentsFieldName = "Attachments";

    /// <exception cref="ArgumentException">TemplateName</exception>
    protected override void CreateChildControls()
    {
      if (TemplateName != AttachmentTemplateName)
        throw new ArgumentException("TemplateName");
      //…
    }
  }

Здесь необходимо обратить внимание на то, что столбец для вложений отображается с помощью шаблона, основанного на FormComponent c определенным названием “AttachmentRows”, его и необходимо переопределить.

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