sharepoint 2010 内容类型
SharePoint 2010 在上一版本的基础上进一步发展了内容类型对象。本系列作为一个专题,试图对其进行一个深入的剖析。方便大家在自己的自定义解决方案中对其进行定制。
内容类型的定义
SharePoint 2010 中,内容类型是我们要应用到特定内容类别的可重用的设置集合。
- 利用内容类型,可通过集中、可重用的方式管理某类别的信息的基础架构(如文档、项目或文件夹类型的元数据和行为)。
- 定 义在网站层次上,独立于网站中的任何文档库或列表。
- 可以将一个内容类型用在多个列表或文档库中。甚至,可能是在整个网站集范围内共用。
- 一 个列表或文档库可以附加多个内容类型。在用户点击“新建”时 ,可以选一种内容类型。其中每个内容类型都可以拥有独立的栏设置,工作流设置和其它相关行为。
一个企业内部的典型应用
“费用报告”内容类型:用户完成一个费用报告的提交工作可以分成以下步骤
创建内容类型,然后挂到列表或库上,如下图:
内容标准化
公司内除了“费用报告”外,还有“采购订单”和“发票”两种文件类型。这三种类型的文档有一些共同点。比如,都是财务类文档,包含有金额信息。但 是,它们各自又都有些独有的要求,使用不同的文档模板和工作流。创建4个内容类型:第一个内容类型为“财务文件”,用于封装组织中所有财务文件公共的数据 需求。其余3个为“采购订单”、“发票”、“费用报告”,继承“财务文件”公共的部分,并在进一步扩展出自身独特的部分。可以把它们用在同一个文档库中, 以实现公司财务文档的统一管理。
内容类型的范围
- 内容类型的范围用于定义内容类型的可用性
- 内容类型的范围等于定义该内容类型的站点及其所有子站点
- 由 此可得,在顶级站点添加的内容类型将在整个网站集内可用。 下图为内容类型范围的一个例子。通过折线所框的面积,可以形象化的理解各个内容类型的可 用范围。
内容类型封装数据需求
内容类型是封装某类信息的数据需求的一种手段。它可以包含以下信息:
- 元数据属性(或称为栏)
- 特定的新建,编辑,查看表单
- 基于用户的操作,在特定情况下启动的工作流
- 对 于文档,指定其文档模板
- 其他自定义信息
继承原则
- 永远不会从头开始创建内容类型的。必须基于一个现有的内容类型来创建新内容类型会自动继承父代的元数据,工作流,以及其他行为。
- 实 际使用中,和构建基类一样。首先定义好基本内容类型。例如,我们构建一个财务文档内容类型,代表公司内所有财务类文档。然后,以此为父,新建两个内容类 型:发票和采购订单。根据继承原则,我们可以将一些共同的元素放在父内容类型中。这样新的内容类型将自动具有这些元素。
- “网站设置”里 面的“网站内容类型”库中就可以看到这种模式。可以用于列表的最基本的内容类型是项目。联系人、链接 、 评论、 任务、事件 、通知、文章、问题、 消息等等都继承自项目。同理,可以用于文档库的最基本内容类型是文档。Wiki网页、表单、都柏林核心栏、空白页、母版页、图片、资产等等都继承自文档。 而图像、音频、视频由继承自资产。因此,在创建内容类型时,一定要首先想好将来是用于文档库还是用于列表。
- 继承原则的主要用途是隔离变 化的传播。理解时可以类比为NTFS文件系统的权限继承或者对于开发人员可以类比为类的继承。
下图为 SharePoint自带的一些内容类型及其在内容类型管理页面中显示的层次关系:
子内容类型
每个内容类型都包含父内容类型的一个引用。这样,就实现了从父代到子代的变更推送。
推送只会覆盖发生变更的部分,不会整个覆盖。
通过UI更新子内容类型
内容类型的某个设置页面包含的所有设置项都将在推送过程中被覆盖. 内容类型通常包含下列设置页
- 高级设置页面
- 文档模板的URL
- 实际的文档模板文件,可选择网站中现有的模板,或上传一个模板文件
- 只读属性
- 添 加网站栏到网站内容类型的页面
- 修改栏设置的页面
- 必需(必须包含信息
- 隐藏(将不显示在表单中)
- 从内容类型中删除该栏
更新子内容类型的注意事项
- 如果子内容类型的更改落在父内容类型的推送粒度范围内,则将被覆盖。比如,我们在子内容类型的一栏上修改了某些设置,然后对父级的栏模板 上又进行修改,甚至删除,这时的推送就会覆盖子内容类型原有的修改
- 每个推送操作只会针对当时的父内容类型的变更。也就是说,一旦错过了 这次推送。以后再想推送这些变化就很困难了。这种情况下只能撤销原先的更改,然后重做,再进行推送。比如,我们从父CT删除了以栏,但当时没有推送。任何 后续的推送过程都将不会包含从子CT中删除该列的操作。若要从子CT删除该栏,我们必须在父CT上将栏添加回去,然后再删除它,然后再执行推送操作。
- 如 果所推送的变更对子代不再适用,则会被忽略。比如,如果我们推送一个在子代中已被删除的栏的设置,该设置将被忽略。这种情况下SharePoint是不会 从新把栏添加回CT的。
- 如果子代设置为只读,则推送操作会失败。除非推送操作包含父代的只读/可写设置。
- 变更的推送 不是一个全或无的过程。指定子CT上报的错不会影响其他的子CT。在推送结束后,SharePoint会给出一个错误清单。
- 如果子CT 定义为密封的,则推送操作会失败。
- 创建或管理网站内容类型需要有网站设计者权限。如果你在某个子网站上没有所需的权限,则在该子站点中 包含的CT上推送会失败。
内容类型变更控制
- 阻止用户对某个内容类型的修改
- 无法阻止用户继承某网站内容类型创建新内容类型
- 只读内容类型:用于警 告用户不应该对该内容类型进行更改。如果需要更改,用户必须关闭只读。
- 密封内容类型:用于更严格的控制。
- 无法通过SharePoint UI修改。
- 必须是网站集管理员才可以通过OM修改成非密封。(通过 SPContentType类的Sealed属性)
网站内容类型与列表内容类型
内容类型的创建与维护通常都在网站层次上进行。当新内容类型创建并添加到网站内容类型集合后,它是一个网站内容类型。 此时该内容类型可以被添加到列表或文档库,但是还没有被添加。 当我们将一个网站内容类型添加到列表时,SharePoint会生成一个该网站内容 类型的本地副本,并将该副本添加到列表。这个本地的实例被称为列表内容类型。它只能用在该列表内。 默 认列表内容类型:SharePoint里的每个项都必然会属于一个内容类型。这可能是显性的也可能是隐性的。对未允许内容类型的列表栏 的更改实际上发生在其默认列表内容类型上。
内容类型分组
内置的内容类型分了很多组。如下图所示:
通过内容类型对象的Group属性获取所属分组。 _Hidden组 的内容类型不会显示在UI中。也就无法派生或添加到列表中
内容类型对象模型
- 包括内容类型的创建,查找,添加,更新,删除
- 服务器端代码使用Microsoft.SharePoint命名空间。此 命名空间的绝大多数类都有对应的Microsoft.SharePoint.Client命名空间类,供客户端调用
- 要得到某个特定内容 类型定义在哪个网站上,可以通过SPContentType对象的ParentWeb属性获得。也可以通过该对象的Scope属性获得一个定义网站的服务 器相对URL地址。
内容类型Id
内容类型的ID用于在网站集内唯一标识内容类型,并设计为可递归。ID封装了该内容类型的血统,或者说是从父内容类型到该内容类型的一条继承线。 每个内容类型的ID都包含了父内容类型的ID,一次类推,直到系统内容类型的ID。推送操作正式基于此。 可以基于以下两个公约之一构造一个有效的 内容类型ID。两者强调的重点不同,一个强调简洁,一个强调独特。可根据具体情况选择。
- 父内容类型ID + 两位16进制值(“00”除外)。通常默认的内容类型都是采用这种方式。
- 父内容类型ID + “00” + 16进制的GUID。通常我们继承的网站内容类型和列表内容类型都是采用这种方式。通常我们的自定义解决方案中推荐使用这种方式。以保证唯一性。但对于其 子代,推荐使用上一种方式,这样可以有效减少ID的长度,以保证可以继承的代数。因为ID的长度是有一个512字节的限制。由于一个字节可以存放两位16 机制值,所以内容类型ID不能大于1024个字符。
下图展示了SharePoint自带的文档内容类型的ID,以及基于两种公约进行的一些ID继承变化:
获取内容类型ID
- 通过网站设置,内容类型管理页面:鼠标悬停在内容类型名称上时,状态栏上可以看到ID。
- 通过 SPBuiltInContentTypeId类,可以很方便的获取SharePoint内置的内容类型的ID。 如:获取文档内容类型 SPContentType documentCType = web.AvailableContentTypes[SPBuiltinContentTypeId.Document];
- 通过 SPContentType对象(服务端)或ContentType对象(客户端)获取的Id属性并不是一个16进制的字符串,而是一个 SPContentTypeId对象(服务端)或ContentTypeId对象(客户端)。如果我们知道ID的16进制串,可以通过构造器得到对应的对 象。 如:获取文档内容类型 SPContentTypeId documentCTypeId = new SPContentTypeId("0x0101");
例子:获取网站内容类型——“项目”有多少子代。统计其被其他网站内容类型和列表内容类型继承的次数。
namespace Test {
//获取使用情况的集合.
// 对网站内容类型和列表内容类型进行计数.
Console.Write("The content type is inherited by {0} site content types", siteTypes);
在我的环境中的运行结果如下:
代码创建的内容类型的Id
当我们在代码中创建内容类型时,不必为它编个ID。相反,我们可以让系统基于父ID自动生成。
如下面的控制台应用程序所示,从项目内容类型派生出一个新的客户内容类型。并添加到网站内容类型集合中。然后又将该内容类型附加到某个列表内容类型 集合上。最后,比较全部3个内容类型的ID:顺序为父内容类型,新的网站内容类型,新的列表内容类型。
namespace Test {
// 创建一个新的内容类型——客户,派生自项目.
// 将新的内容联系添加到网站内容类型集合
// 添加到一个列表.
// 输出全部的3个ID.
Console.Write("\nPress ENTER to continue..."); Console.ReadLine(); } } }
在我的环境中的运行结果如下:
如何以指定Id的方式在代码中创建?
SPContentType构造器也可以通过SPContentTypeId方式创建。此时,其Parent属性的值实际上是通过分析所传递的内 容类型ID而得到的。
内容类型的排序
代码中的注释很详细,就不多废话了:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
namespace Test {
// 以组名排序.
// 输出.
for (int i = 0; i < list.Count; i++) {
if (i == 0 || ((SPContentType)list[i - 1]).Group != ct.Group) {
}
Console.WriteLine("{0,-35} {1,-12} {2}", ct.Name, ct.Parent.Name, ct.Id); } } }
Console.Write("\nPress ENTER to continue...");
// 基于 IComparer 接口实现比较操作. // 先通过组名比较,然后通过内容类 型Id比较.
// 首先比较组名 .
重点掌握实现的思路
查找内容类型
- 查找哪些内容类型在当前网站上下文可用——使用SPWeb(服务端)或Web(客户端)对象的AvailableContentTypes属 性。返回包含了SPContentType (服务端)或ContentType (客户端)的一个只读集合。可供枚 举,也可以传递内容类型名称索引到指定内容类型。 之所以只读是因为该集合不仅包含当前网站内定义的内容类型,还包含父或祖先网站内的内容类型。
- 查 找已经附加到某个列表或文档库上的内容类型——通过SPList (服务端)或List (客户端)对象的ContentTypes属 性。返回一个可枚举、可索引的可改写的集合。 要注意,当网站内容类型附加到列表或文档库时,并不会真的添加该 内容类型,而是会添加该内容类型的一个副本。该副本是一个列表内容类型。它的ID号基于相应的网站内容类型的ID而生成,但是是不相同的。这意味着我们可 能无法通过父内容类型ID从集合中直接得到相应内容类型。应该学会使用集合的BestMatch方法。
- 如 果希望知道某个网站内容类型在哪里被使用了,不管是被附加到一个列表还是被作为其他内容类型的父内容类型,我们可以调用静态方法GetUsages(SPContentType), 它是SPContentTypeUsage类的成员
新建内容类型
- 可作为Feature部署的一部分,来新建一个自定义内容类型。创建自定义内容类型的代码写在FeatureActivate里。 如 果内容类型中需要使用新的网站栏,要先处理网站栏的创建。
- 内容类型的显示名称不能包含特定的一些字符,可以调用静态方法ValidateName对 将要使用的现实名称进行验证。注意,该方法不会验证重名。
- 通过SPContentType类构 造函数创建一个内容类型对象。必须的一个参数是SPContentTypeCollection对 象。该参数代表了将要在其上创建内容类型的SPWeb所对应的ContentTypes。虽然如此,并不表示构造函数会自动添加新建的内 容类型。我们仍旧需要单独调用同一集合的Add方法才行。
- 如果在网站上需要添加一个新的内容类型或删除一个 已有的内容类型,我们需要访问站点对象的ContentTypes属性,返回一个允许修改的集合。然后就可以使用Add或Delete方法了。 如 要进一步设置内容类型的属性,可以将Add方法返回的SPContentType对象保存起来。通过SPFieldLink对象可以引用一个栏,然后调用 内容类型的FieldLinks属性返回的SPFieldLinkCollection对象的Add方法来为内容类型添加栏。完成修改后记得调用 Update()方法将设置写回数据库。
- 要在网站上创建或维护内容类型,我们必须具有网站设计者的访问权限。特别是需要在内容 类型所宿主的网站上具有管理列表和添加并自定义页面的访问权限。
示例:以第一篇开头的财务文档的例子,通过编程实现创建。注释很详细,就不多做解释了。
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/* 创建网站栏 */
string columnGroup = "Financial Columns";
// Amount
// Client Name
// Date Opened
// Cost Center Name
/* 创建网站内容类型 */
string contentTypeGroup = "Financial Content Types";
// 获取文档内容类型,作为财务文档的父内容类型.
// 创建财务文档内容类型.
// 注意: 内容类型在添加前不会被初始化.
// 添加 Date Opened 栏. 子代将继承该栏.
// 添加 Amount 栏. 子代将继承该栏.
// 提交变更.
// 创建发票内容类型.
// 修改从父内容类型继承而来的Title栏.
// 添加 Client 栏.
// 指定一个文档模板.
// 提交变更.
// 创建一个采购订单内容类型.
// 修改从父内容类型继承而来的Title栏.
// 添加 Department 栏.
// 指定一个文档模板.
// 提交变更.
}
注意:FeatureActived方法的properties参数是一个SPFeatureReceiverProperties对 象。该对象的Feature属性返回一个SPFeature对象,它的Parent属 性包含了一个装箱的SPWeb或SPSite对象。我们不应该销毁这些对象。但是,所有我们在代码中新建的SPWeb或SPSite对象都要记得销毁。
在网站中删除内容类型
如果要从内容类型定义所在网站集中删除该内容类型,首先要保证GetUsages方法要返回一个空表,也就是 说,该内容类型不能被附加到任何列表,或者被作为其他内容类型的父内容类型。 需要注意的是,AvailableContentTypes属性返回 的是只读的内容类型集合。无法从中删除对象。因为其中包含的不仅仅是本网站内的内容类型。 示例:删除一个网站 内容类型
namespace Test { class ConsoleCTDel { static void Main(string[] args) { using (SPSite siteCollection = new SPSite("http://sp2010u/sites/contoso")) { using (SPWeb webSite = siteCollection.OpenWeb()) { // 获取准备删除的内容类型. SPContentType obsolete = webSite.ContentTypes["客户"];
if (obsolete != null) // 得到. { IList<SPContentTypeUsage> usages = SPContentTypeUsage.GetUsages(obsolete); if (usages.Count > 0) // 被使用. { Console.WriteLine("The content type is in use in the following locations:"); foreach (SPContentTypeUsage usage in usages) Console.WriteLine(usage.Url); } else // 未被使用. { // 删除. Console.WriteLine("Deleting content type {0}...", obsolete.Name); webSite.ContentTypes.Delete(obsolete.Id); } } else // 未找到. { Console.WriteLine("The content type does not exist in this site collection."); } } } Console.Write("\nPress ENTER to continue..."); Console.ReadLine(); }
} }
在列表中添加和删除内容类型
- 为列表或文档库附加内容类型前,首先要使该列表支持内容类型。在UI 里,可以选择列表设置,然后选高级设置。在是否允许内容类型里选择是。也可以通过编程设置,获得SPList对象后,设置其ContentTypesEnabled属 性为true。
- 对于已存在的列表,我们可以添加到列表内 容类型集合里的内容类型必须是一个已存在的网站内容类型。我们可以通过UI中列表设置页的可用内容类型清单来为其添加。编程实现的话可以先从SPWeb对 象(服务端)或Web对象(客户端)得到AvailableContentTypes,从中选择一个内容类型。
- 添加的规则:最 重要的3个内置的内容类型是项目,文档和文件夹。 基于这3者派生的内容类型SharePoint会套用相应的某些规则。文档类的只能加到文档库,项目类的只能加到列表。文件夹类的是个例外。文件夹可以加 到列表也可以加到文档库。可以通过调用列表对象的IsContentTypeAllowed方法来验证我们所选择的内 容类型是否被接受。
- 调用SPList (服务端)或List (客户端)对象的ContentTypes的Add方法即可添加。 当通过对象模型将网站内容类型添加到列表后,SharePoint会自 动添加所有内容类型中包含但列表中不存在的栏。这一点与通过列表定义的方式添加明显的不同。
- 删除文档库或列 表的内容类型可以直接调用Delete方法,并传递一个标识要删除内容类型对象的SPContentTypeId (服务端)或ContentTypeId (客户端)。 我们无法删除使用中的内容类型。因此,在删之前首先需 要遍历列表项,并查看其ContentType属性来确保没有列表项使用到该内容类型。SharePoint此时并不考虑已经被移到回收 站中的列表项。如果这些项在内容类型删除后又被恢复回来,这将被指派到该列表的默认内容类型。
示例:下面的命令行应用程序将添加一个网站内容类型到共享文档列表。生产环境下,往往将类似的操作同样也应该放 在SPFeatureReceiver对象的FeatureActived方法内。
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
namespace Test { class Program { static void Main(string[] args) { using (SPSite siteCollection = new SPSite("http://sp2010u/sites/contoso")) { using (SPWeb site = siteCollection.OpenWeb()) { // 获取一个内容类型 SPContentType ct = site.AvailableContentTypes["Financial Document"];
if (ct != null) // 得到一个内容类型 { try // 获取一个列表 { SPList list = site.Lists["共享文档"]; // 如果没找到就抛出异常
// 确认列表允许内容类型 list.ContentTypesEnabled = true;
// 将内容类型添加到 列表 if (!list.IsContentTypeAllowed(ct)) Console.WriteLine("The {0} content type is not allowed on the {1} list",ct.Name, list.Title);// 不符合原则 else if (list.ContentTypes[ct.Name] != null) Console.WriteLine("The content type name {0} is already in use on the {1} list",ct.Name, list.Title);// 已存在 else list.ContentTypes.Add(ct); } catch (ArgumentException ex) // 未找到列表 { Console.WriteLine("The list does not exist."); } } else // 未找到内容类型 { Console.WriteLine("he content type is not available in this site."); } } } Console.Write("\nPress ENTER to continue..."); Console.ReadLine(); } } }
添加网站内容类型到列表所造成的影响
- 本地副本中所有的栏作为栏引用(FieldRef)添加到列表栏。
- 副本作为网站内容类型的子代存在。Id不同。内容在 初始时是相同的。
- 作为子代的列表内容类型可以随意定制,变更只在列表范围内。并不会更新到父内容类型。
- 父可以影响 子。当然,也可以通过设置变更控制保护一个内容类型,使其避免被影响。 最佳的做法是尽量在网站层次上应用更改,尽量限制列表层次上的自定义。
更新内容类型
SharePoint允许对内容类型进行修改,即使已经将内容类型部署到网站、列表或已经创建了相应的列表项。
更新内容类型的2条标准途径
途径1:对内容类型进行修改,并推送更新。这条途径主要用在对使用 中的内容类型进行有针对性的,离散的变更。比如,需要在网站内容类型上添加一列。 途径2:新 建一个内容类型,以满足变更的需求。然后把原来的内容类型加到_Hidden组。这条途径主要用在需要彻底替换原有内 容类型,但是还需要保留已有的列表数据的情况下。比如,我们有一个已经用了数年的产品规格内容类型。现在,不得不变更产品的规格,以便体现新的要求。但由 于历史的原因,我们仍然要保留原有的产品规格内容类型,因为之前已经有无数的列表项是基于它来创建的。这时,我们可以新建一个内容类型叫产品规格 2010。然后通过Feature打包分发,并在激活的代码中通过程序将新的内容类型添加到所有旧内容类型存在的列表中。然后将原来的产品规格内容类型的 Hidden属性设为true。这样,当用户创建产品规格时,他们只能选择产品规格2010,原先的产品规格不在列出。但是,原先的列表项却保持不变。
SharePoint提供了一种机制,允许通过改变SPContentType或ContentType对象的ReadOnly和Sealed属性来控制变更。因此,在修改内容类型前,应该首先检查这些属性设置。
将更新内容类型的代码放在SPFeatureReceiver对象的 FeatureActivated(SPFeatureReceiverProperties)方法内。在完成对相应对象中内容类型的修改后,要记得调用 该对象的Update方法将变更提交到数据库中。我们可以在Update方法中指定是否要更新子内容类型。
SharePoint并不会将内容类型的更新写回到内容类型定义文件,而是存在SharePoint数据库中。所有的列表内容类型也都是存 放在SharePoint数据库中的。因此,任何时候都不要更改已经安装并激活使用的内容类型的定义文件。 SharePoint不会跟踪这种变化。
使用对象模型更新内容类型
- 使用对象模型可以提供更细的推送粒度。
- 记得调用Update方法将变更写回数据库。
- 无法通过修改 Feature XML文件中的网站内容类型定义来添加栏
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
oContentType.Update(true); }
内容类型中的自定义信息
- 按照设计初衷,内容类型是可扩展的。
- 每个内容类型都有一个XML集,用于存储第三方解决方案中用到的一些信息(XML 节点所代表的XmlDocument元素)。通过添加XML节点,可以为内容类型加入自定义信息。通过在内容类型中包含自定义的XmlDocument元 素,可以将我们解决方案中所需的自定义信息进行一个封装,使其成为内容类型的一部分。
可以通过两种方式为内容类型添加自定义信息:- 对于已经部署好的内容类型,我们可以通过OM以编程的方式访问该集合中的Xml。只需要调用SPXmlDocumentCollection的Add方 法即可。
- 对于未进行部署的内容类型,我们可以在内容类型架构定义XML中添加一个XmlDocument元素。
- 内 容类型可以包含任何数目的XmlDocument元素。每个XmlDocument可以有自己的架构,只要是有效的XML即可。
- 网站内 容类型中包含的XmlDocument内容会自动复制到任何子代。推送的粒度也可以基于Xml节点。但是,SharePoint在覆盖该 xml节点前不会去判断该节点是否正在被使用或是否为某个处理过程所必需。我们也可以删除某个xml,并作为变更推送下去。
关联工作流
SPContentType.WorkflowAssociations属性返回一个SPWorkflowAssociationCollection对象代表了内容类型所关联的所有工作流。我们可以通过Id属性或 GetAssociationByName方法取到其中某个工作流。每个集合内的工作流的Name是唯一的。通过 GetAssociationByName的返回值判断是否重名。 代码
实例:创建一个工作流关联,将其添加到某个网站内容类型的工作流集合,然后推送到子代内容类型 (注意: 在新建项目的框架版本选择时要选择.Net Framework 3.5,还需要添加Microsoft.SharePoint.dll的引用)。下图是实例中要用到的工作流模板,首先要确保其已激活。
完成后查看工作流关联,可以看到下图:
以下是添加关联的代码:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
namespace TestWorkflowAssociation { class ConsoleAdd { static void Main(string[] args) { Console.WriteLine(); SPSite siteCollection = new SPSite("http://sp2010u/sites/contoso/Docs"); SPWeb site = siteCollection.OpenWeb();
SPContentType siteContentType = site.ContentTypes["费用报告"]; string taskListTitle = "任务"; string historyListTitle = "工作流历史记录"; string workflowName = "红-黄-绿";
// 获取一个模板. SPWorkflowTemplate workflowTemplate = null; foreach (SPWorkflowTemplate template in site.WorkflowTemplates) { workflowTemplate = template;
// 这里找一个自带的模板. if (workflowTemplate.Name == "三态") break; } if (workflowTemplate != null) { // 创建一个关联. SPWorkflowAssociation workflowAssociation = SPWorkflowAssociation.CreateWebContentTypeAssociation(workflowTemplate, workflowName, taskListTitle, historyListTitle);
// 添加一个关联到该内容类型,如果关联已存在则更新它. Console.Write("工作流 {0} 已关联 ", workflowAssociation.Name); if (siteContentType.WorkflowAssociations.GetAssociationByName(workflowAssociation.Name, site.Locale) == null) { siteContentType.WorkflowAssociations.Add(workflowAssociation); Console.WriteLine("添加."); } else { siteContentType.WorkflowAssociations.Update(workflowAssociation); Console.WriteLine("更新."); }
// 将工作流关联更新推送到子代内容类型. siteContentType.UpdateWorkflowAssociationsOnChildren(false, // 不需要生成完整变更清单 true, // 推送到继承的子代网站内容类型 true, // 推送到列表内容类型 false); // 如果遇到密封或只读内容类型不抛异常 } site.Dispose(); siteCollection.Dispose();
Console.WriteLine(); Console.Write("任意键继续..."); Console.ReadLine(); } } }
以下代码删除刚才关联的工作流:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
namespace TestWorkflowAssociation { class ConsoleDel { static void Main(string[] args) { using (SPSite site = new SPSite("http://sp2010u/sites/contoso/Docs")) { using (SPWeb web = site.OpenWeb()) { string ctName = "费用报告"; string wfName = "红-黄-绿";
SPContentType contentType = web.ContentTypes[ctName]; if (null != contentType) { SPWorkflowAssociation wfAssociation = contentType.WorkflowAssociations.GetAssociationByName(wfName, web.Locale);
if (null != wfAssociation) { // 移除该工作流关联. contentType.WorkflowAssociations.Remove(wfAssociation); Console.WriteLine("与工作流 {0} 的关联已移除.", wfAssociation.Name); } else { Console.WriteLine("名为 {0} 的工作流关联未找到.", wfName); } } else { Console.WriteLine("内容类型 {0} 不存在.", ctName); } // 将工作流关联更新推送到子代内容类型. contentType.UpdateWorkflowAssociationsOnChildren(false, true, true, false); } } Console.Write("\n任意键继续..."); Console.ReadLine(); } } }
内容类型XML架构定义
- 应用一:可以在Feature中通过内容类型的SchemaXml 新建一个内容类型定义。
- 应用二:可以通过List 定义中的ContentTypes元素在创建列表时自动附加一个已有的内容类型
网站内容类型定义中引用的栏或内容类型可以包含在另一个Feature里。这种情况下,我们要注意创建相应的激活依赖性。
内容类型架构定义位于C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\XML\wss.xsd
可以打开ctypewss.xml(位于%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\FEATURES\ctype)。其中包含了大多数SharePoint 内置的内容类型定义。我们可以将其做为一个很好的学习内容类型架构的例子。
ContentTypes 元素
包含元素:ContentType, ContentTypeRef
比如:某个列表定义中的3个内容类型
ContentType元素
用于定义一个内容类型。
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
其中属性:
- ID,必须,需要符合前面第二篇中所说的内容类型ID公约
- Name,必须,不操过128个字符,并且不能包含\ / : * ? “ # % < > { } | ~ &,两个连续的点(..)或其他特殊字符,如Tab
比如,SharePoint内置的“文档”内容类型的定义如下:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
DocumentTemplate 元素
用于为内容类型指定文档模板。也就是当用户点击新建时作为模板打开的文件。
需要注意得是,我们必须单独 另外部署好所需的模板文件。一种比较好的办法是利用Module元素,将其包含到Feature中。
示例:还是以开头财务文档的为例子,其所对应的Feature定义如下,注意其中FieldRef的使用场景:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
<!-- Document Templates -->
<Module Name="InvoiceDocumentTemplate" Path="FinancialDocuments" Url="_cts/Invoice" RootWebOnly="TRUE"> <File Url="Invoice.docx" Type="Ghostable" /> </Module>
<Module Name="PurchaseOrderDocumentTemplate" Path="FinancialDocuments" Url="_cts/PurchaseOrder" RootWebOnly="TRUE"> <File Url="PurchaseOrder.docx" Type="Ghostable" /> </Module>
<!-- Site Columns -->
<Field ID="{060E50AC-E9C1-4D3C-B1F9-DE0BCAC300F6}" Name="Amount" DisplayName="Amount" Type="Currency" Decimals="2" Min="0" Required="FALSE" Group="Financial Columns" />
<Field ID="{86811853-7E52-4515-A88D-A8FA9D450905}" Name="Client" DisplayName="Client Name" Type="Text" Required="FALSE" Group="Financial Columns"/>
<Field ID="{1511BF28-A787-4061-B2E1-71F64CC93FD5}" Name="DateOpened" DisplayName="Date Opened" Type="DateTime" Format="DateOnly" Required="FALSE" Group="Financial Columns"> <Default>[today]</Default> </Field>
<Field ID="{943E7530-5E2B-4C02-8259-CCD93A9ECB18}" Name="CostCenter" DisplayName="Department" Type="Choice" Required="FALSE" Group="Financial Columns"> <CHOICES> <CHOICE>Administration</CHOICE> <CHOICE>Information</CHOICE> <CHOICE>Facilities</CHOICE> <CHOICE>Operations</CHOICE> <CHOICE>Sales</CHOICE> <CHOICE>Marketing</CHOICE> </CHOICES> </Field>
<!-- Site Content Types -->
<!-- Parent ContentType: Document (0x0101) --> <ContentType ID="0x0101000728167cd9c94899925ba69c4af6743e" Name="Financial Document" Group="Financial Content Types" Description="Base financial content type" Version="0"> <FieldRefs> <FieldRef ID="{1511BF28-A787-4061-B2E1-71F64CC93FD5}" Name="DateOpened" DisplayName="Date" Required="TRUE"/> <FieldRef ID="{060E50AC-E9C1-4D3C-B1F9-DE0BCAC300F6}" Name="Amount" DisplayName="Amount" Required="FALSE"/> </FieldRefs> </ContentType>
<!-- Parent ContentType: Financial Document --> <ContentType ID="0x0101000728167cd9c94899925ba69c4af6743e01" Name="Invoice" Group="Financial Content Types" Description="Used for creating customer invoices" Inherits="TRUE" Version="0"> <FieldRefs> <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" DisplayName="Service" Required="TRUE" Sealed="TRUE"/> <FieldRef ID="{86811853-7E52-4515-A88D-A8FA9D450905}" Name="Client" DisplayName="Client Name" Required="TRUE"/> </FieldRefs> <DocumentTemplate TargetName="Invoice.docx" /> </ContentType>
<!-- Parent ContentType: Financial Document --> <ContentType ID="0x0101000728167cd9c94899925ba69c4af6743e02" Name="PurchaseOrder" Group="Financial Content Types" Description="Used for creating purchase orders" Inherits="TRUE" Version="0"> <FieldRefs> <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" DisplayName="Item" Required="TRUE" Sealed="TRUE"/> <FieldRef ID="{943E7530-5E2B-4C02-8259-CCD93A9ECB18}" Name="CostCenter" DisplayName="Department" Required="TRUE"/> </FieldRefs> <DocumentTemplate TargetName="PurchaseOrder.docx"/> </ContentType>
</Elements
Folder元素
用于指定内容类型的资源文件夹的文件夹相对路径。
FieldRefs 元素
用于包含FieldRef 栏的引用。可以引用已存在的栏或者在 同一个Feature中创建的新栏。可以引用父内容类型的栏,并覆盖栏的部分属性。可覆盖的属性包 括:
- 显示名
- 升级与降级相关的XML属性
- 栏是否为只读,必需,隐藏
其中的ID,Name,DisplayName,Required是必须的。即无论与栏定义是否相同都必须指定。 其中ID应该是一个GUID,包括大括号,并且是大小写敏感的。
RemoveFieldRef 要从父内容类型中移除的栏。只需要ID,其他的属性都将被 SharePoint忽略。
内置的栏的ID值可以从fieldswss.xml(位于%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\FEATURES\fields)中查看。同时还可以用来学习栏的定义。
XmlDocuments和XmlDocument 元素
用于在内容类型中包含自定义信息。可用于对象模型编程时访问。
SharePoint自身包含了两个XmlDocument,用于内容类型的自定义表单模板和客户端重定向。
- FormTemplates架构
- FormUrls架构
例如,通过自定义信息指定打开内容类型所需的表单模板
FormTemplates架构
用于指定UI中列表条目的查看,新建与编辑表单模板。包括以下元素:
- FormTemplates根节点,其xmlns必是http://schemas.microsoft.com/sharepoint /v3/contenttype/forms
- Display 指定自定义查看表单模板
- Edit 指定自定义编辑表单模板
- New 指定自定义新建表单模板
这里指定的表单模板是指列表对应表单页面中央的.asmx部分。表单模板的名称必须是一个已存在的渲染模板(渲 染模板位于%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\CONTROLTEMPLATES下的一个.ascx文件中)
如果没有在内容类型定义中指定表单模板,则会使用SharePoint的默认值。
以下为默认的文档内容类型的表单模板
以下为默认的列表项的表单模板
FormUrls架构
用于为内容类型指定客户端重定向到另外的查看,编辑,新建表单页面。包括以下内容:
- FormUrls根元素,其xmlns必是http://schemas.microsoft.com/sharepoint/v3 /contenttype/forms/url
- Display 可选,指定自定义查看表单的地址
- Edit 可选,指定自定义编辑表单的地址
- New 可选,指定自定义新建表单的地址
表单页是一个.aspx页面,用于整个替换SharePoint默认的页 面。包括SharePoint的显示框架,如顶部,左侧导航等等。因此在自定义的表单页上,我们要根据需要重建出相应的一些内容。
地址是相对于内容类型根目录而言的。
如果没有指定,SharePoint将使用默认值。
在网站定义中指定内容类型
在网站定义中指定内容类型,可以在用户每次创建该类型的网站时,让SharePoint默认添加上该内容类型。 首先,需要在另一个 Feature中创建该内容类型。然后,通过站点定义文件Onet.xml内的Configuration元素中引用 引用该Feature。可以是在SiteFeatures元素内,也可以是在WebFeatures元素内。 注意:对SharePoint内 置的站点定义修改Onet.xml是不被支持的。因此,最好先创建一个自定义站点定义。
在列表定义中添加内容类型
添加前必须确认列表被配置为支持内容类型。
第一个要检查的是列表所使用的列表模板的ListTemplate元素有一个DisallowContentTypes属 性。如果值为TRUE,则列表不支持内容类型。如果值为FALSE或未设置,则列表支持内容类型。
第二个要检查的是列表实例是否启用了内容类型。在定义该列表实例的Schema.xml中有一个List元素。该元素包含一个必要的属性——EnableContentTypes, 该属性值必须为TRUE。这等同于在UI中的列表设置,高级设置中选择了允许内容类型(可参考第三篇中的内容)。
可以作为列表定义的一部分添加列表内容类型。在列表定义的ContentTypes元素内,我们可以引用已有的网站内容类型(通过包含一系列ContentTypeRef元 素,每个元素只需要一个ID属性,设置为所添加的内容类型的ID),也可以创建新的内容类型。这时,我们新建的内容类型仅在该列表定义的 实例中有效。新内容类型并不属于网站内容类型集合或其他列表。
我们所引用的网站内容类型必须位于列表的范围内——即相同的网站层次或更高。
注意:当SharePoint 创建一个列表实例时,它将仅仅包含基类列表架构或该列表架构中的栏。如果我们在列表架构中引用了一个网站内容类型,该内容类型引用了一些并不包含在基类列 表架构或该列表架构中的网站栏,则这些栏将不会被包含进来。想要包含,必须先在列表架构中对其进行定义。
示例:创建一个自定义字段,内容类型,列表定义和列表实例
现在将实际动手使用VS2010创建一个名为“病人姓名”的自定义栏,并创建一个包含该栏的内容类型。然后,创建一个列表定义包含该内容类型。最 后,创建基于该列表定义创建一个列表实例,并从中找到上面创建的内容类型和栏。
新建空白SharePoint项目
由于列表定义无法作为沙箱解决方案部署,所以这里必须选择服务器场部署。
添加空元素
编辑Elements.xml文件
如果是首次在该站点上调试,VS会帮我们修改web.config,启用调试设置。
执行结果
新建内容类型
选择父内容类型
在自动生成的Elements.xml文件中,添加上面的栏的引用
执行结果
点击进去,看到已经添加了栏。并且继承了父内容类型。
选择基于内容类型创建的列表定义。
使用刚刚添加的内容类型
自动创建的列表模板定义
自动创建的列表架构定义,其中已经自动包含了上面的内容类型
执行结果
注意:
在测试环境下发现回收解决方案后列表实例不会被删除,但是访问时会报错。成为垃圾。 解决办法是在回收前先删除该列表。