Поля таксономии, также известные как поля управляемых метаданных (managed metadata), появились в SharePoint 2010. Метаданные позволяют создавать иерархии терминов, которые можно использовать как справочные значения (c typeahead в UI) и, начиная с SharePoint 2013, для навигации.
Метаданные поддерживают множество языков, синонимы и дополнительные свойства. Можно организовать навигацию по метаданным в библиотеке или фильтрацию по поддереву в поиске SharePoint.
И все было бы прекрасно, но развернуть в составе WSP пакета поле метаданных в списке – очень нетривиальная задача. Можно конечно сделать с помощью кода, некоторые даже очень любят этот подход, но в большом масштабе будет много копипасты и статический анализ делать гораздо сложнее.
Если деплоить с помощью CAML, то возникает две проблемы, но об этом по порядку
Проблема первая – схема поля
Если вы погуглите, то найдете минимум четыре статьи как деплоить таксономические поля:
- http://www.sharepointconfig.com/2011/03/the-complete-guide-to-provisioning-sharepoint-2010-managed-metadata-fields/
- http://www.instantquick.com/index.php/correctly-provisioning-managed-metadata-columns?c=elumenotion-blog-archive/random-whatnot
- http://magenic.com/Blog/CorrectlyProvisioningManagedMetadataTaxonomyF
- http://www.wictorwilen.se/Post/How-to-provision-SharePoint-2010-Managed-Metadata-columns.aspx
Все рецепты приведут к неверному результату. Ближе всех к правде оказался последний пост, но и в нем есть проблемы.
Ни в одном посте не указано как поле попадает в список. А именно от этого зависит как поле будет работать. В прошлом посте я писал, что методы полей срабатывают, только если используется ContenTypeRef в схеме List Definition. Эти методы выполняют много работы – добавляют поля в список, привязывают event receiver_ы для синхронизации значений полей таксономии и catchall поля.
Итак правильная схема таксономического поля:
<Field ID="{defbf0ed-377a-4e62-a980-0493ac0ef42e}" Name="TaxonomyColumn" DisplayName="Taxonomy Column" Type="TaxonomyFieldType" Required="FALSE" Group="Custom Site Columns" DisplaceOnUpgrade="TRUE" Overwrite="TRUE" > </Field>
И все Нет, это не шутка.
Если нужны множественные значения, то указать Mult=”TRUE” и Type="TaxonomyFieldTypeMulti".
Далее необходимо поле добавить в тип контента, а тип контента в список. Как писал в прошлом посте. Сделаете по-другому – не заработает.
На что стоит обратить внимание:
- TaxonomyFieldType унаследован от Lookup, но атрибут ShowField нельзя указывать в схеме, это делается при добавлении поля в список. Если очень хочется указать, то ShowField=”Term$Resources:core,Language” и только так.
- Не надо указывать Id связанного текстового поля. В случае когда Id не указан – генерируется автоматически.
- Связанное поле деплоить не надо. Скорее всего вы это сделаете неправильно и у вас перестанет работать поиск. Кстати алгоритм создания связанного поля в 2010 и 2013 отличается.
- В элементе Customizations можно указать атрибуты поля по-умолчанию. Это проще всего сделать поправив свойства поля на сайте, а потом аккуратно скопировав в схему.
К сожалению на этом месте возникает вторая проблема.
Проблема вторая – привязка к набору терминов
Мало задеплоить поле, надо еще привязать его к набору терминов. Для этого у поля нужно задать два свойства SspId и TermSetId, соответственно Id службы управляемых метаданных и Id набора терминов. Эти два идентификатора не являются инвариантными, то есть могут быть разными в разных фермах. Имена групп и наборов терминов тоже могут варьироваться в зависимости от текущего языка.
По сути есть два сценария развертывания:
TermSet уровня коллекции сайтов
Для каждой коллекции сайтов в хранилище Managed Metadata создается отдельная группа, видимая только в пределах этой коллекции сайтов. При при активации фичи с полем можно создавать (или импортировать) TermSet в локальную группу, а при деактивации – удалять.
public override void FeatureActivated(SPFeatureReceiverProperties properties) { var site = properties.Feature.Parent as SPSite; var web = site.RootWeb; var field = web.Fields[FieldId] as TaxonomyField; var session = new TaxonomySession(site); var store = session.DefaultSiteCollectionTermStore; //sp2010 //var group = store.Groups // .OfType<Group>() // .First(g => g.IsSiteCollectionGroup); //sp2013 var group = store.GetSiteCollectionGroup(site); bool allTermsAdded; string errorMessage; var termSet = store.GetImportManager() .ImportTermSet(group, textReader, out allTermsAdded, out errorMessage); if (!allTermsAdded) { termSet.Delete(); store.CommitAll(); throw new InvalidOperationException(errorMessage); } field.SspId = store.Id; field.TermSetId = termSet.Id; field.Update(true); } public override void FeatureDeactivating(SPFeatureReceiverProperties properties) { var site = properties.Feature.Parent as SPSite; var web = site.RootWeb; var field = web.Fields[FieldId] as TaxonomyField; var session = new TaxonomySession(site); var store = session.DefaultSiteCollectionTermStore; store.GetTermSet(field.TermSetId).Delete(); store.CommitAll(); }
Это самый простой метод. Вместо импорта можете использовать любой способ создания набора терминов.
Недостатков у этого метода два:
- Нельзя создавать наборы терминов с фиксированными Id, так как фича может быть активирована на нескольких сайтах, а Id в рамках одной службы метаданных должны быть уникальны.
- Набор терминов виден только в коллекции сайтов и не может быть использован в других коллекциях, например для XSP.
Внимание! Код выше не является referenece-кодом, его нельзя бездумно копипастить в свои решения. Для того чтобы он работал надо как минимум добавить логирование и проверку, что поле не настроено. FeatureActivated вызывается как минимум один раз для каждого экземпляра фичи, может быть и больше.
TermSet уровня службы
Если же вы хотите чтобы набор терминов не был привязан к одной коллекции сайтов, то нужно код развертывания набора терминов отделить от кода настройки поля.
public override void FeatureActivated(SPFeatureReceiverProperties properties) { var site = properties.Feature.Parent as SPSite; var web = site.RootWeb; var field = web.Fields[FieldId] as TaxonomyField; var termStoreId = new Guid((string)site.WebApplication.Properties[field.InternalName + ".SspId"]); var termSetId = new Guid((string)site.WebApplication.Properties[field.InternalName + ".TermSetId"]); field.SspId = termStoreId; field.TermSetId = termSetId; field.Update(true); }
Осталась только указать нужные SspId и TermSetId при установке WSP до активации фичи. Проще всего это сделать в PowerShell скрипте установки. Причем скриптом можно создать свое приложение-службу таксономии и полностью перенести все термы из среды разработки. Это легко сделать с помощью командлетов Export-SPMetadataWebServicePartitionData и Import-SPMetadataWebServicePartitionData.
Этим же способом можно привязывать поле к уже существующему в целевой ферме набору терминов.
Заключение
Как и в прошлом посте, в этом я выкладываю правила для SPCAF (http://www.spcaf.com/) , которые помогут контролировать правильность развертывания таксономии.
PS. Кто заинтересовался как устроена таксономия внутри предлагаю почитать http://www.andrewconnell.com/sharepoint-2010-managed-metadata-in-depth-look-into-the-taxonomy-parts