SPWebConfigModification 工具和探索
一个 ASP.NET Web 部件,用于测试 SharePoint 2007 SPWebConfigModification 更改的效果

引言
当我第一次接触 `SPWebConfigModification` 并开始研究它时,我惊讶于博客和文章中关于如何使用这个 SharePoint 类以编程方式更改 _web.config_ 文件的混乱程度。经过一段时间的使用,我不再对这种混乱感到惊讶……它通常在 Feature 接收器中使用,但从 Feature 接收器进行_测试_却很困难,所以我编写了这个工具(一个 Web 部件)来使其更容易。在本文中,我试图重点介绍许多常见的障碍,并解释一些不那么明显的用法细节。
背景
SPWebConfigModification 是什么?

SharePoint Web 应用程序基本上与 IIS Web 应用程序相同。SharePoint 通过区域(zones)扩展了这一点。SharePoint 区域(默认、内网、互联网、外网、自定义)就像打开同一扇门的几扇不同的门——它们提供了对同一内容的不同访问方式。每个 SharePoint 区域扩展都实现为一个单独的 IIS Web 站点,拥有自己的 _web.config_ 文件。因此,一个 SharePoint Web 应用程序可能包含多个 IIS Web 站点——因此有多个 _web.config_ 文件。
为什么这很重要?_应用_更改集会影响 Web 应用程序的所有 _web.config_ 文件,但不是统一的——存在一些怪癖。SharePoint Web 应用程序在 SharePoint 配置数据库中维护这些更改的集合。`SPWebService` 类用于通过安排一个计时器作业将 Web 应用程序的更改集应用于所有前端 Web 服务器。

通常,这在 Feature 接收器中使用,在 Feature 激活时添加 _web.config_ 更改,在 Feature 停用时删除它们,这样一切都能保持整洁。这里出现的一个怪癖是,_移除_更改_仅在默认区域中应用_,而将其他所有区域保持不变。另一个怪癖是,如果_不同区域需要不同的更改_——例如自定义角色提供程序——你无法做到——所有区域都获得相同的 XML。
这基本是如何工作的?创建一个 `SPWebConfigModification` 类的实例,并设置 6 个属性。使用 `Add`(modification)方法将所需数量的这些修改添加到 Web 应用程序的修改集合中,然后调用 `SPWebApplication.Update()` 来保存集合。调用 `SPWebService.ContentService.ApplyWebConfigModifications()` 来将更改传播到配置数据库和 WFE。移除操作类似——使用 `Owner` 属性在 Web 应用程序的集合中找到修改,调用集合的 `Remove`(modification)方法,然后像之前一样更新和应用。这 6 个属性有一些棘手的方面,所以要小心!`Name` 和 `Value` 属性的定义根据修改的 `Type` 而变化。另外,根据 MSDN,“你必须是前端 Web 服务器上的管理员才能使你的代码正常工作。”
SPWebConfigModification 属性
类型
- `EnsureChildNode` - 该修改将添加/删除子节点
- `EnsureAttribute` - 该修改将确保特定属性的值
- `EnsureSection` - 该修改将确保一个节存在并且是唯一的
- `Path` - 一个 `XPath` 表达式,用于定位此修改中正在操作的父节点
- `Name` - 取决于 _Type_
- 对于 `_EnsureAttribute_` 和 `_EnsureSection_`,属性名或节名作为有效的 XML 名称(基本上是字母数字加上一些标点符号)
- 对于 `_EnsureChildNode_`,一个 `XPath` 表达式,用于唯一标识要作为“`Path`”父节点的子节点插入的子节点。
- `Owner` - 不要留空,这对于标识要移除的修改至关重要。明智地选择。选择一个唯一的名称——可能是 Feature 的名称或 ID。
- `Sequence` - 这**仅**在同一个节点或属性存在多个_相同类型_的修改时适用;否则将被忽略。如果使用,修改将按升序应用。有效范围:0 到 65536。本文档未包含此项。
- `Value` - 取决于 `Type`
- 对于 `_EnsureChildNode_`,要添加/移除的子节点
- 对于 `_EnsureAttribute_`,属性的值
- 对于 `_EnsureSection_`,节的名称
使用工具
该工具是一个在 MOSS 2007 开发框上创建的 Web 部件,使用 `WSPBuilder` 进行部署(http://www.codeplex.com/wspbuilder)。关于 _wsp_ 文件及其部署的简要说明,请参阅 Building and deploying SharePoint Solution packages。它的目的是在实际使用修改之前进行故障排除,并帮助理解过程是如何工作的。作为最佳实践,在开发机器上创建一个可丢弃的 Web 应用程序,根据需要进行扩展以进行测试,并将该工具指向那里。创建的修改可能会破坏 Web 站点……甚至损坏数据库……所有这些我们都在努力避免!
EnsureChildNode 示例 – 单个节点(“Hello, World”示例)
选择一个 Web 应用程序(使用顶部的**更改/设置测试 WebApp** 链接),最好是专门为测试修改而设置的。
添加子节点
<add key="AcademicYear" value="2009-2010" />
到父节点
configuration/appSettings
按如下方式填写属性:(注意 – 如果您复制粘贴,请小心引号 – 如果将智能引号粘贴到文本框中,它们会产生错误。)
属性的文本版本
Path | configuration/appSettings |
名称 | add[@key="AcademicYear"] |
Owner | AcademicFeature |
序列 | 0 |
类型 | EnsureChildNode |
值 | <add key="AcademicYear" value="2009-2010" /> |
点击 **创建并验证修改**
消息显示在 Value 文本框正下方,有一个虚线边框。将尝试进行基本验证。将检查 _Path_ 是否具有正确的 XPath 语法,以及节点是否存在于默认区域的 _web.config_ 中。如果 `EnsureChildNode` 类型,将检查 _Name_ 是否具有正确的 XPath 语法,否则是否是有效的 XML。如果 _Owner_ 为空,将进行标记。如果 `EnsureChildNode` 类型,将测试 _Value_ 以查看 XML 是否格式正确,否则是否是有效的 XML。
点击 **将当前修改添加到本地集合**
这将创建修改并将其添加,如果一切顺利,修改将出现在右侧窗格中。修改按它们在集合中找到的顺序排列,第一个在列表顶部。
点击 **保存修改集合**
这将为当前 Web 应用程序调用 `SPWebApplication.Update()`;右侧窗格中可见的集合是可通过代码访问的整个集合。
点击 **将修改应用于前端 Web 服务器**
调用 `SPWebService.ContentService.ApplyWebConfigModifications()`,它会启动一个计时器作业。如果一切顺利,将显示“已应用修改”消息,并且选定 Web 应用程序的所有 _web.config_ 文件将具有新节点,通常位于父节点结束标签之前。在这种情况下,搜索 `</appSettings>` 应该有助于快速找到新的 `< add key="AcademicYear" value="2009-2010" />` 子节点。配置数据库也会更新。
点击默认区域 _web.config_ 的 **显示/隐藏** 链接。
(我对设计不擅长,所以请包容 _web.config_ 的显示。)浏览器的“在此页面查找”工具将有助于找到新节点——_web.config_ 必须先显示才能让浏览器搜索它们,所以展开您感兴趣的节点。这里是工具的右侧窗格,显示默认区域 _web.config_ 中的新子节点。
配置数据库
为了好玩,让我们看看 SharePoint 配置数据库。类似这样的查询会显示一个列表,该列表似乎是修改集。(警告 – 我在这里只是_猜测_。)
USE SharePoint_Config_Nancy
SELECT [O1].[Name]
,[O1].[Properties]
,[O2].[Name] ParentName
,[Classes].[FullName] ParentClassName
,[O1].[ParentId]
,[O1].[Status]
,[O1].[Version]
FROM [SharePoint_Config_Nancy].[dbo].[Objects] O1
LEFT OUTER JOIN
[SharePoint_Config_Nancy].[dbo].[Objects] O2
ON [O1].[ParentId] = [O2].[Id]
LEFT OUTER JOIN
[SharePoint_Config_Nancy].[dbo].[Classes] Classes
ON [O2].[ClassId] = [Classes].[Id]
WHERE ([O1].[Name] LIKE '%WebConfigChanges%')
ORDER BY [O1].[Name]
**右键单击**感兴趣的 Web 应用程序的**属性**单元格,从上下文菜单中选择**复制**,然后将结果粘贴到记事本中。在记事本中,将 XML 保存为一个友好的名称,如 _SharePoint3616.xml_,然后在 Internet Explorer 中查看。
好了,我认为就是这样——以及一大堆在代码中不显示的节点。一些我读过的文章评论说,修改似乎是按字母顺序应用的,按 Path+Name 排序。不确定如何验证这一点,但由于这里的 XML 似乎是一个 `SortedList` 的序列化,并且排序字段由 Path+Name 组成,我倾向于认为事实就是如此。这可能非常有用——稍后会详细介绍。
关闭 fld 元素可得到此视图——注意 3 个名称

回到工具
点击 **从本地集合中移除当前所有者的修改**。
屏幕刷新,现在找不到该 Web 应用程序的修改了。数据库呢?还在那里!_web.config_ 呢?也还在那里!糟糕——我们从未点击**保存修改集合**来更新 Web 应用程序,也没有点击**将修改应用于前端 Web 服务器**。
让我们再试一次,真正移除该条目。将其加回。移除。更新。应用。现在它已从数据库中消失。默认区域 _web.config_ 不再包含 `AcademicYear` 元素,但 Extranet 和 Internet 区域仍然包含。它以其怪异的方式起作用了。
那么**清除本地修改集合**呢?谨慎使用。这会像任何集合一样,移除所有条目。如果将空的集合更新并应用于 WFE,那么该 Web 应用程序的所有修改都将被移除。如果存在来自多个 Feature 的修改,这可能会破坏某些应用程序。但由于这是针对一个可丢弃的测试应用程序(对吗?)——所以可以尝试一下。
EnsureChildNode 示例 - 节点集
添加 `Membership` 提供程序的 XML 怎么样?它看起来像这样
<membership defaultProvider="SPListUser">
<providers>
<add name="SPListUser" type="
FBA.MembershipProvider.SPListUser,
FBA.MembershipProvider,
Version=1.0.0.0, Culture=neutral,
PublicKeyToken= 8383efad02158ca5"
applicationName="/" />
</providers>
</membership>
这可以一次添加一个节点
Path | configuration/system.web |
名称 | membership[@defaultProvider="SPListUser"] |
Owner | MembershipFeature |
序列 | 0 |
类型 | EnsureChildNode |
值 | <membership defaultProvider="SPListUser" /> |
Path | configuration/system.web/membership[@defaultProvider="SPListUser"] |
名称 | providers |
Owner | MembershipFeature |
序列 | 0 |
类型 | EnsureChildNode |
值 | <providers /> |
Path | configuration/system.web/membership[@defaultProvider="SPListUser"]/providers |
名称 | add[@name="SPListUser"] |
Owner | MembershipFeature |
序列 | 0 |
类型 | EnsureChildNode |
值 | <add name="SPListUser" type="FBA.MembershipProvider.SPListUser, FBA.MembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken= 8383efad02158ca5" applicationName="/" /> |
或者一次性添加如下
Path | configuration/system.web |
名称 | membership[@defaultProvider="SPListUser"] |
Owner | MembershipFeature |
序列 | 0 |
类型 | EnsureChildNode |
值 | <membership defaultProvider="SPListUser"> <providers> <add name="SPListUser" type="FBA.MembershipProvider.SPListUser, FBA.MembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken= 8383efad02158ca5" applicationName="/" /> </providers> </membership> > |
如何使移除失败(或“这如何让你抓狂”)
对于许多开发人员来说,XPath 是按需学习的;也就是说,只学习足够完成当前任务的知识,因此检测细微不一致的能力可能不强。如果我们更改上面成员资格提供程序修改的一次性版本的 `Name` 属性,仅一个字符——删除“**@**”,使其变成 `membership[defaultProvider="SPListUser"]`,它仍然会验证,并且仍然会添加节点集。它不仅会添加节点集,当应用修改时,它还会_不断添加_,导致 _web.config_ 中出现多个副本,从而出现“PARSER ERROR MESSAGE: Sections must only appear once per config file”错误。
**更糟糕的是,移除现在失败了!** 因此,虽然使用 `Owner` 属性来识别要移除的修改至关重要,但 `Path`+`Name` 正确识别 `Value` 属性中的节点也同样至关重要。有许多出色的 XPath 教程和参考资料;这里我最喜欢的一个是:https://w3schools.org.cn/xpath/ (在使用 `SPWebConfigModification` 时,我通常会打开一两个 XPath 教程或参考资料。)
EnsureSection 示例 - 究竟什么是 Section?(以及更多关于排序)
许多博客文章提到节不能通过移除方法移除——但首先,什么是节?感谢 DTerrell,看起来节是一个没有属性且不是最内层子节点或子节点的子节点的元素节点。我怀疑 `EnsureSection` 在需要确保父节点仅存在一次时很有用。(如果您尝试向不存在的父节点添加子节点,则会失败。)我尝试将 `connectionStrings` 节添加为 `EnsureSection` 类型,将其移除——它仍然存在。`EnsureSection` 的添加是永久性的。但是,只要 `EnsureSection` 修改仅用于标准节,这应该不是问题——子节点始终可以移除,空的节不会造成危害。
如果存在 2 个或更多用于添加同一节的修改怎么办?只创建一个节,因此唯一性方面似乎是正确的。
还记得查看数据库时的 `type=SortedList` 吗?如果修改按照 Path+Name 排序,那么修改将按正确的顺序构建父节点。查看数据库中的 3 个“fld”节点,**如果** `EnsureSection` 修改先应用,然后是 `EnsureChildNode`,然后是 `EnsureAttribute`,所有这些都按 Path+Name 排序——**那么**父节点将在添加子节点之前构建,子节点将在编辑属性之前添加。**这是一个猜测**。我做了一些实验来测试这一点。下面的实验使用 `EnsureSection` 一次添加一个父节点,使用 `EnsureChildNode` 添加一个子节点,_但以错误的顺序添加到修改集合中,所有节点的 Sequence=0_。
在下面的示例中,如果它们按预期排序并应用,那么此 XML 将作为 configuration/system.web 节点的子节点添加
<webParts>
<personalization>
<authorization>
<allow users="*" verbs="modifyState" />
</authorization>
</personalization>
</webParts>
这是集合(authorization 元素在 personalization 元素之前添加,子节点在所有这些之前——完全是乱序的)
这有效!
另一个示例,仅使用 `EnsureChildNode`,乱序添加节点,并尝试添加相同的节点两次。请注意,第二次尝试的“duration”属性值不同,为 120。当多个 Feature 各自向集合添加修改时,这种情况很有可能发生。
这有效,并且只添加了一次节点集,duration 属性设置为 120。我认为很清楚排序正在发生。
那么为什么要使用 `EnsureSection` 而不是 `EnsureChildNode` 呢?两个突出的特点是 `EnsureSection` 确保节点只创建一次,并且无法移除。在使用 `EnsureChildNode` 时,同一个节点可能被创建多次,从而导致网站损坏。
EnsureAttribute 示例
一个典型的 _web.config_ 有许多 `appSettings` 条目,如下所示
让我们将 `FeedCacheTime` 的值更改为 `450`。首先在 `Path` 中识别目标节点,并记住 `Name` 和 `Value` 取决于 `Type`——`Name` 现在是目标属性名,`Value` 是要赋的值——如下所示
Path | configuration/appSettings/add[@key="FeedCacheTime"] |
名称 | value |
Owner | FeedCacheAttributeFeature |
序列 | 0 |
类型 | EnsureAttribute |
值 | 450 |
不要忘记 XML 是区分大小写的。我第一次尝试时,在 `Name` 中输入了“`Value`”(注意大写的“V”),它添加了一个名为“`Value`”的第二个属性,值为 `450`。结果如下
<add key="FeedCacheTime" value="300" Value="450" />
糟糕。
使用 `Name = “value”` 再次尝试,它成功了——结果
<add key="FeedCacheTime" value="450" />
呼。
杂项有价值的引用
来自 http://msdn.microsoft.com/en-us/magazine/cc507633.aspx
SafeControl 和 CAS 条目
对于 Web 部件 SafeControl 条目和自定义访问控制策略,请依赖 SharePoint 解决方案打包框架。在 SPWebConfigModifications 使用期间手动编辑 web.config
手动移除或更改由 SharePoint 管理的 _web.config_ 中的条目在长期内对您没有好处。SharePoint 会跟踪这些条目,并确保它们在 WSS 服务每次重新启动时都存在于 _web.config_ 中。
来自 http://www.crsw.com/mark/Lists/Posts/Post.aspx?ID=32
开发与生产
记住,在 SharePoint 的单服务器安装上进行开发和测试时,许多活动都能立即完成。然而,当同一段代码在具有多台服务器的 SharePoint 场上执行时,完成给定任务所需的时间会更长。这是由于必须发生的计时器作业和场同步事件。因此,您应该只在 Feature 激活或事件处理程序中调用一次 `ApplyWebConfigModifications()` 方法。如果在短时间内多次调用 `ApplyWebConfigModifications()` 方法,您很可能会收到以下错误:A web configuration modification operation is already running。(一个 Web 配置修改操作已在运行。)
[在整个场中应用更改——此代码有效]
myWebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();**注意**:此代码在整个场中无效(我无法解释原因)。
SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
尾声
总而言之,`SPWebConfigModification` 可能很难处理。`Name` 和 `Value` 属性的定义根据 `Type` 属性而变化,XML 和 XPath 表达式必须精确,这需要相当多的关于两者的通用知识,并且通用用法要求仍然有些模糊。我希望该工具能使测试修改和理解 `SPWebConfigModification` 变得更容易、更快捷。编码愉快!