SharePoint and oData

В SharePoint 2010 многое сделано для открытости доступа к данным, в том числе поддержка oData. Клиентская модель построена поверх этих возможностей.

Для того, чтобы опробовать возможности SharePoint oData в действии вам достаточно вызвать сервис ListData:

http://[serverName]/_vti_bin/ListData.svc

Если в результате получите ошибку 404, то скорее всего у вас не стоит ADO.NET Data Services v1.5 CTP2

Далее можно попробовать воспользоваться Visual Studio

image

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

  class Program
  {
    static void Main(string[] args)
    {
      var context = new ClientDataContext(new Uri("http://localhost/_vti_bin/ListData.svc"));
      context.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
      var результат = context.ДокументыПроекта.Где(x => x.Имя != null).ToList();
    }
  }

  public static class Расширения
  {
    public static IEnumerable<T> Где<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      return source.Where(predicate);
    }
  }

Ну или просто возможность работы из браузера:

image

А так же из Excel, благодаря PowerPivot.


Каких возможностей мне не хватает в SharePoint 2010 BCS

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

Итак, начнём:

  • Нельзя давать разрешения на отдельные записи во внешнем списке (external list). Наконец-то SharePoint в исполнении 2010ой версии научили корректно использовать Access Control Lists (ACL), однако, по вполне понятным причинам данные возможности не доступны при работе с внешними данными
  • Возможности рабочих процессов не доступны полностью для внешних списков. Казалось бы, всё замечательно и готово для интеграции, однако внешние данные остаются внешними данными, и многие события, необходимые для работы с рабочими процессами недоступны для SharePoint, однако это не исключает использование внешних данных в рабочих процессах, работающих на обычных списках. Об этой проблеме и способах её разрешения довольно-таки подробно описано в статье Using SharePoint workflows with Business Connectivity Services (BCS).
  • Отсутствие версионности и истории изменений во внешних списках. Вполне логично, что внешние списки остаются лишь “обёрткой” для внешних данных и хранить изменения, а следовательно и обеспечивать версионность, довольно-таки неординарная задача, которую команда BCS решила не касаться, по той же причине “элементы социального взаимодействия”, такие как рейтинги и тегирование, так же будут недоступными при работе с внешними данными (BCS).
  • Экспорт в Excel. Очень странно почему данный функционал не доступен, т.к. не накладывает никаких ограничений на используемые данные, а просто меняет их представление. Как альтернативу данному подходу могу предложить что-нибудь наподобие Print List (http://www.sharepoint-tips.com/2007/01/how-to-add-print-list-option-to-list.html), т.е. решения по экспорту внешних данных из SharePoint будут подразумевать кастомизацию.
  • RSS каналы. С одной стороны причины схожи с экспортом в Excel, однако здесь, как мне кажется, противоречие даже со здравым смыслом, т.е. RSS-канал как правило должен отслеживать изменения в данных и оповещать об этом подписчика, в случае с внешними данными, они могут и не меняться, а будет меняться лишь выборка, что будет генерировать многочисленное количество неконтролируемых обновлений.
  • Просмотр в виде таблицы. Очень удобная функция, не понятно, что именно сподвигло на её отсутствие команду разработчиков, скорее всего возможности пакетной обработки в новом интерфейсе SharePoint, которыми она была так удобна.
  • Внешние данные не доступны для доступа через REST сервисы. Как мне кажется причина так же в возможности непрогнозируемого изменения данных произвольным образом, что не даёт никакой гарантии для потребителя данных сервисов.

В заключение

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


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


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


SharePoint 2010: LINQ

Введение

Сегодня я расскажу о наиболее симпатичной для разработчиков возможности появившейся с приходом 2010-ой версии SharePoint, это механизм преобразования данных под названием LINQ to SharePoint.

Основным назначением данного инструментария является предоставить возможность разработчикам взаимодействовать со строго-типизированными данными, пользоваться при построении запросов к данным языковыми возможностями (LINQ). Давайте разберемся что же скрывается за этими словами.

Модель разработки (как было до LINQ to SharePoint)

SPListItem

Хочу напомнить вам, что основная работа с данными при разработки на SharePoint происходит через слаботипизированные сущности:

SPListItem listItem = GetLastItem(); 
Console.WriteLine(listItem["Название"]); 

Хочу обратить ваше внимание на работу с элементом SPListItem.

Многие разработчики обращаются к данным по Field Display Name, этот подход, хоть и присутствует во многих примерах на MSDN, но хранит в себе подводные камни: что случится, если ваше поле кто-нибудь захочет переименовать?

Альтернативным подходом является обращение по номеру столбца Internal Name, которое отменяет проблему изменения имени поля, однако оставляет за собой вопрос времени поиска поля по его внутреннему имени, а так же возможность ошибки.

Последним вариантом остается Field Id. Это наиболее приемлемый способ, т.к. он гарантирует, что вы получите в результате интересующее вас поле. Здесь я хочу напомнить вам, что все идентификаторы стандартных SharePoint полей вынесены в класс-справочник SPBuiltInFieldId.

Если же вы создаете свои поля в ваших решениях, то не составляет никакой проблемы создать подобные справочники и пользоваться ими при доступе к данным. Можно делать это собственноручно, а можно сделать шаблон Visual Studio T4, с помощью которого генерировать классы справочники по описаниям полей в ваших фичах (features). Выглядеть он может примерно так:

<#@ template language=«C#v3.5» #>
<#@ output extension=«cs» #>
<#@ Assembly Name=«Microsoft.SharePoint» #>
<#@ Import Namespace=«System.Collections.Generic» #>
<#@ Import Namespace=«Microsoft.SharePoint» #>
<#
string groupName = «My group»;
string siteCollectionUrl = «http://sharepoint&#187;;
#>
using System;

namespace SharePointSolution.Custom
{
  public static class FieldId
  {
<#
  using (SPSite site = new SPSite(siteCollectionUrl))
  {
    SPWeb web = site.RootWeb;
    SPFieldCollection fields = web.Fields;
    foreach (SPField f in fields)
    {
      if (f.Group.Equals(groupName))
      {
#>
    /// <summary>
    /// <para>Title: <#= f.Title #></para>
    /// <para>InternalName: <#= f.InternalName #></para>
    /// <para>Id: <#= f.Id.ToString() #></para>
    /// </summary>
<#
        if (f.TypeAsString.Equals(«LookupMulti») || f.TypeAsString.Equals(«Lookup»))
        {
#>
    public static string <#= f.InternalName #> = «<#= f.Title #>»;
<#
        }
        else
        {
#>
    public static Guid <#= f.InternalName #> = new Guid(«<#= f.Id.ToString() #>»);
<#
        }
      }
    }
  }
#>
  }
}

Кстати подобные шаблоны можно сгенерировать и для Типов Содержимого (ContentTypes), а так же для путей ваших UserControl’ов и проч. Примеры можно скачать здесь: http://imtech.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=31092

Тем не менее результатом listItem[“Title”] будет являться Object, что вынуждает нас производить многочисленные операции по преобразованию данных, а так же многочисленные проверки на неравенство null и прочим ограничениям.

CAML

Так же работа с данными частенько требует построения запросов к данным, данная операция так же не слишком элегантно реализована на SharePoint, реализуется она с помощью языка запросов CAML, являющегося подмножеством XML.

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

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

Модель разработки с LINQ

Прелюдия

Обычно в разработке своих проектов я стараюсь выделить некоторую модель предметной области, решением задач которой будет заниматься моё приложение. Обычно сущности данной модели выражены с помощью кода на языке программирования (к примеру C#). Где будет храниться эта модель меня чаще всего не сильно беспокоит (т.к. хранилище может варьироваться в зависимости от эффективности использования и требований: к примеру это может быть SQLite, Microsoft SQL Server, SharePoint Services, Web Services).

В настоящее время занимаясь разработкой SharePoint решений мне приходится описывать отображение данных, сохраняемых с списках на сущности моей модели. Делается это с помощью написанных собственноручно преобразователей (http://github.com/butaji/Sapphire). Это удобно, однако не решает всех трудностей (в том числе и построения запросов), в связи с этим всё таки я ожидаю прихода LINQ to SharePoint.

LINQ to SharePoint от сообщества

Появление данного инструментария в SharePoint Services 2010 было вполне ожидаемо, однако некоторые разработчики делали попытки сделать LINQ провайдеры ещё несколько лет назад:

  • http://linqtosharepoint.codeplex.com/ – самая первая попытка реализации LINQ to SharePoint, его создатель Bart De Smet – профессиональный разработчик, в связи с чем у меня есть предположение, что скорее всего команда SharePoint воспользовалась начинаниями его проекта. Данный проект не закончен (последняя версия 0.2.4 от 30 ноября 2007 года), однако он вызвал резонанс в своё время. Рассказ Барта о его проекте можно увидеть на Channel 9 GeekSpeak.
  • http://sporm.codeplex.com/ – проект, который был найден мною буквально на днях, он так же является LINQ провайдером, даже с более “чистой” объектной моделью (вывод по поверхностному ознакомлению). Проект имеет на данный момент версию 0.1, но находится в активной разработке
LINQ to SharePoint от Microsoft

Вот мы и добрались до официальной реализации LINQ провайдера. Начнём с небольшой критики:

  • невозможность реализации Model-First

Вы помните Entity Framework 1.0? Мне хватило одного проекта, чтобы понять, что с этой версией я больше работать не буду. Model-First может быть принципиально важен при разработке проектов, в которых целевой платформой может являться не только SharePoint.

  • неполная реализация

Причиной тому архитектура хранения данных в SharePoint, в частности язык запросов, в связи с чем мы не сможем делать, к примеру запросы с JOIN, т.к. они могут нанести тяжелый удар по производительности. Есть так же набор “неэффективных запросов“. Смысл названия заключается в: 1) все те детали запроса, которые могут быть преобразованы в CAML будут преобразованы 2) будет выполнен CAML запрос 3) оставшиеся непреобразоваными части запроса будут применены к объектам в памяти, которые вернул запрос (с помощью LINQ to Objects). Другими словами вы рискуете выбрать огромное количество записей, и преобразовывать их в памяти. Большинство математических функций неэффективны. Подробный и актуальный список данных запросов можно найти по адресу: http://msdn.microsoft.com/en-us/library/ee536585(office.14).aspx

SPMetal

Генерированием модели данных для отображения занимается утилита SPMetal, её достаточно вызвать, указав расположение узла с данными, а так же указать какие именно списки и типы содержимого вы хотите отобразить.

Данный вызов, к примеру, сгенерирует файл MarketingSite, в котором будет описан так называемый DataContext, а так же все сущности из списков указанного узла.

Данная утилита обладает некоторой гибкостью в определении правил кодогенерации, о настройках можно прочитать здесь: http://msdn.microsoft.com/en-us/library/ee537010(office.14).aspx

Кстати, узлы SharePoint теперь можно просматривать через Server Explorer в Visual Studio 2010. А как мы знаем, Visual Studio 2010 довольно таки просто поддается расширению, в связи с чем SPMetal теперь можно вызывать и из графического интерфейса:

Данное расширение можно скачать здесь: http://visualstudiogallery.msdn.microsoft.com/en-us/c523e7ba-ba9d-45c4-98ea-b02b19f81640

Некоторые факты о LINQ to SharePoint

Правила преобразования типов SharePoint на CLR типы указаны в таблице по адресу: http://msdn.microsoft.com/en-us/library/ee536245(office.14).aspx

Благодаря типу EntitySet<TEntity> поддерживаются типы связей one-to-many, many-to-many, а так же ленивая загрузка связей по аналогии с Entity Framework 4.

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

Итак, мы имеем установленный SharePoint Foundation 2010, а так же Visual Studio 2010 с установленными “Imtech Get SPMetal Definition Extension”.

Создаем проект Console Application. После чего добавляем ссылку на Microsoft.SharePoint.Linq.dll. Далее, пользуясь расширениями для SPMetal генерируем контекст и сущности из списка на нашем узле в файл (в моём примере Employee.cs):

internal partial class OrganisationSiteDataContext 
    : Microsoft.SharePoint.Linq.DataContext
{}

Данный контекст пригодится нам для построения запросов. Работа с ним аналогична DataContext’у во всех технологиях типа LINQ to “Data”.

Стоит уделять особое внимание жизненному циклу данного объекта, т.к. он потребляет ресурсы, а так же должен быть уникален, к примеру для каждого пользователя, в зависимости от типа вашего приложения решения могут быть разными, можно так же воспользоваться услугами Dependency Injection контейнера.

Все сгенерированные классы будут иметь дополнительные аттрибуты, указывающие на отображение:

[ContentType(Name = »Employee»)]
public partial class Employee
{
    [Column(Name=»ID», IsId=true, ReadOnly=true, FieldType=»Counter»)]
    public int Id {getset;}
}

Кстати, эти атрибуты почему-то так же не очень гибко спроектированы, к примеру ListAttribute привязан к имени списка http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.linq.listattribute_properties(office.14).aspx, т.е. если пользователь захочет переименовать список, ваше решение хрупко пошатнется и упадёт.

Преобразованием сущностей можно управлять дополнительно, для этого реализован интерфейс ICostumMapping:

public partial class Project : ICustomMapping
{
   [CustomMapping(Columns = new String[] { «Due_x0020_Date»«IsCancelled» })]
   public void MapFrom(object listItem)
   {
        this.DueDate = ((SPListItem)listItem)[“Due_x0020_Date”];
        this.IsCancelled = ((SPListItem)listItem)[“IsCancelled”];
   }

   public void MapTo(object listItem)
   {
       ((SPListItem)listItem)[“Due_x0020_Date”] = this.DueDate;
       ((SPListItem)listItem)[“IsCancelled”] = this.IsCancelled;
   }

    public void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
    {
    }
}

 

Вернемся к работе с контекстом:

using(DataContext data = new DataContext(SPContext.Current.Web.Url))
{

    EntityList<Employee> employees = data.GetList<Employee>(«Employee»);

    var youngEmployees = from employee in employees
                      where employee.Age < 25
                      select employee;
   
   //do something

}

В принципе всё аналогично LINQ to “Data”, это очень приятно, так как нет необходимости менять стиль при каждом новом решении.

Все CUD (Create, Update, Delete) операции так же поддерживаются, выглядит это примерно следующим образом:

using(DataContext data = new DataContext(SPContext.Current.Web.Url))
{

    EntityList<Employee> employees = data.GetList<Employee>(«Employee»);

    Employee newEmployee = new Employee{Name = «Вася»};    

    employees.InsertOnSubmit(newEmployee);

    data.SubmitChanges();
}

На этом я закончу данную статью, в скором времени расскажу об особенностях LINQ to SharePoint как ORM.

Материалы:

Видео по LINQ to SharePoint http://www.microsoft.com/belux/MSDN/nl/chopsticks/default.aspx?id=1517