2007年4月15日,星期日

示例代码-创建查找列作为功能

我答应了 较早的帖子 发布用于创建查找列的功能的代码。这里的基本思想是尽可能使用CAML定义网站栏,但使用代码注入该栏所引用的列表ID(在功能设计时未知,因为SharePoint为列表确定了GUID)。下面提取的是功能接收器的代码。

几个要点:

  • 假定文件D:\ COB.Demo.ListBasedSiteColumns.Fields.xml中存在以下内容(注意,“ List”属性为空):-
    <?xml version="1.0" encoding="utf-8"?>
    <elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <!-- _filecategory="ContentType" _filetype="File" _filename="fields.xml" _uniqueid="c0188da6-e320-44c0-b50c-cb6eaecec512" -->
    <Field
    Type="Lookup"
    Displayname="Locations"
    Required="FALSE"
    List=""
    ShowField="LinkTitleNoMenu"
    UnlimitedLengthInDocumentlibrary="FALSE"
    Group="COBDemo"
    ID="{ae8e6ab8-b151-4fa4-8694-3cd24ad3b1bc}"
    SourceId="{8c066b26-5a3e-4e1b-85ec-7e584cf178d7}"
    StaticName="Locations"
    Name="Locations">
    </elements>

  • 由于大多数列定义都在CAML中,因此您可以指定列的ID。稍后在我们部署需要通过ID引用此网站列的内容类型或列表时,这将很有用。
  • 现场 columns cannot be deleted when they are in use i.e. used in content types or lists. I prefer to try to delete the column on feature deactivation or reactivation, but omit error-handling so 的SharePoint throws an exception. This makes it clear to me why the feature cannot be deactivated. See comments in code for more information.
  • 请注意,激活此功能后,包含以下代码的程序集必须可用(在GAC或具有适当CAS策略的站点bin目录中)。您还必须在feature.xml文件中指定FeatureAssembly和FeatureReceiver属性,以注册功能接收者。
  • 关于部署程序集,请注意,尽管您当然可以使用功能框架将程序集部署到GAC或站点bin,但您不能在 这个 特征。尽管事件处理程序的名称为“ FeatureActivated”,但在执行代码时,仍未处理CAML。因此,您的程序集尚未被复制,并且在尝试加载该程序集时会收到FileNotFoundException。一个好的解决方案是使用功能依赖项。在这里,您可以创建第二个功能来部署程序集,并将主要功能设置为依赖此功能。另外,您可以将程序集部署功能标记为隐藏,从而使实现更简洁。如果查看我使用过的整个文件集有用,请给我留言,然后将它们放在某个地方。
抱歉,我会在有空的时候尝试做一些事情:-


public class FeatureReceiver : SPFeatureReceiver
{
private readonly string f_csSITE_COLS_DEFINITION_PATH = @"D:\COB.Demo.ListBasedSiteColumns.Fields.xml";

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
// feature is scoped 在 现场, so the parent is type SPSite rather than SPWeb..
使用(SPSite网站= properties.Feature.Parent作为SPSite)
{
SPWeb currentWeb = null;
Guid gRootWebId = Guid.Empty;
如果(网站!=空)
{
currentWeb = site.RootWeb;
gRootWebId = currentWeb.ID;
}
其他
{
currentWeb = properties.Feature.Parent作为SPWeb;
gRootWebId = currentWeb.Site.RootWeb.ID;
}





using (currentWeb)
{
// get reference to the list..
SPList referencedList = currentWeb.Site.RootWeb.Lists["LocationsList"];
string sFieldElement = null;
XmlTextReader xReader = new XmlTextReader(f_csSITE_COLS_DEFINITION_PATH);
while (xReader.Read())
{
if (xReader.LocalName == "Field")
{
sFieldElement = xReader.ReadOuterXml();
break;
}
}


string sFinalCaml = replaceListGuidString(sFieldElement, referencedList);
createLookupColumn(currentWeb, sFinalCaml, "Locations");
currentWeb.Update();
}
}
}


private string replaceListGuidString(string sFieldElement, SPList referencedList)
{
字符串sPopulatedGuid = string.Format(“ List = {0}”,referencedList.ID);
return sFieldElement.Replace("List=\"\"", sPopulatedGuid);
}

/// <summary>
/// Attempt to delete the column. Note that 这个 will fail if the column is inuse,
/// i.e. it is used in a content type or list. I prefer to not catch the exception
/// (though it may be useful to add extra logging), hence feature deactivation/re- /// activation will fail. This effectively means 这个 feature cannot be deactivated whilst the column is in use.
/// </summary>
/// <param name="column">Column to delete.</param>

private void 在 temptColumnDelete(SPFieldLookup column)
{
try
{
column.Delete();
}
catch (SPException e)
{
// consider logging full explanation..
throw;
}
}






private void createLookupColumn(SPWeb web, string sColumnDefinitionXml, string sColumnName)
{
// delete the column if it exists already and is not yet in use..
SPFieldLookup lookupColumn = null;
lookupColumn = web.Fields[sColumnName] as SPFieldLookup;
如果(lookupColumn!= null)
{
tryColumnDelete(lookupColumn);
}

// now create the column from the CAML definition..
string sCreatedColName = web.Fields.AddFieldAsXml(sColumnDefinitionXml);

// also set LookupWebId so column can be used in webs other than web which hosts list..
lookupColumn = web.Fields[sCreatedColName] as SPFieldLookup;
lookupColumn.LookupWebId = web.Site.RootWeb.ID;
lookupColumn.Update();
}


public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
// delete the column if it exists already and is not yet in use..



// feature is scoped 在 现场, so the parent is type SPSite rather than SPWeb..
使用(SPSite网站= properties.Feature.Parent作为SPSite)
{
SPWeb currentWeb = null;
Guid gRootWebId = Guid.Empty;
如果(网站!=空)
{
currentWeb = site.RootWeb;
gRootWebId = currentWeb.ID;
}
其他
{
currentWeb = properties.Feature.Parent作为SPWeb;
gRootWebId = currentWeb.Site.RootWeb.ID;
}

SPFieldLookup lookupColumn = null;
lookupColumn = currentWeb.Fields [“ LocationsList”] as SPFieldLookup;

如果(lookupColumn!= null)
{
tryColumnDelete(lookupColumn);
}
}
}



public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}


public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}
}


如果具有说明这两个功能的完整文件集很有用(特别是因为上面的内容不太可读!),请给我评论,然后将它们放在某个地方。

[更新-这些现已上传到Codeplex,请参见 http://sharepointnutsandbolts.blogspot.com/2007/04/feature-to-create-lookup-fields-on.html]

24条评论:

匿名 said...

克里斯你好,
非常感谢分享您的代码,昨天是我的生日,对于我目前的工作,这对我有很大帮助,非常感谢。

劳伦特

匿名 said...

克里斯你好,
最好访问完整的文件集。

非常感谢

劳伦特

匿名 said...

嘿...这真的很好,但是可下载格式的代码应该更好。
克里斯,你能把文件上传到某个地方吗?

谢谢

大卫·T。

克里斯·奥'Brien说过...

你好

我现在已经上传了文件,请参阅我的帖子 http://sharepointnutsandbolts.blogspot.com/2007/04/feature-to-create-lookup-fields-on.html.

匿名 said...

似乎,在字段重新创建期间更改了字段的GUID,这掩盖了站点列/内容类型中的字段引用……最终将导致巨大的问题。

克里斯·奥'Brien说过...

你好

我不确定我是否会完全按照您的观点,但从本质上讲,这是该技术要解决的问题-列表在创建时会获得新的GUID。通过使用此代码,这意味着您的网站列/内容类型“功能”与列表的耦合程度较低。

HTH,

克里斯。

匿名 said...

这是好东西,但我只是想知道-您的代码按名称引用了列表。如果由于列表名称可能使用其他语言而以其他语言配置站点,这将如何工作?

引用列表名称时使用资源字符串会更好吗?

克里斯·奥'Brien说过...

你好

确实-我想我并不是要建议硬编码列表名称是您使用此代码的最佳方法-我在那里简化了它,以专注于实际创建查找链接。

我认为在现实生活中,我是从Feature属性获得列表名称的,所以我从Feature XML中传递了列表名称。您也可以选择从SharePoint列表中获取一些详细信息-沿着这些行,我想做的是拥有一个隐藏的“ config”网站,该网站存储这样的配置数据。

HTH,

克里斯。

科沃说过...

克里斯,你好

这篇文章的大帮助。我在输入此帖子时试图将其添加到我自己的功能中:P,但是可以使用以下方式代替查找在源中进行硬编码的XML文件:
properties.Feature.Definition.RootDirectory

克里斯·奥'Brien说过...

嗨Kwoque,

是的,这是一个好建议。为了简单起见,在我的示例中有一些硬编码的东西-我完全同意在现实世界中,您通常会避免硬编码路径/对象名称/ ID等。

如前所述,建议的另一个选择是使用Feature属性传递这些参数。

干杯,

克里斯。

杰森·阿佩吉斯(Jason Apergis)说过...

克里斯,

我收到此错误-本地设备名称已在使用中。 (来自HRESULT的异常:0x80070055)-当我运行Fields.AddFieldAsXml时。这绝对是在杀了我,没有东西在上面。我可以解决的唯一方法是将整个网站集销毁并重新开始,如果本产品投入生产,则不可行。有任何想法吗???

谢谢,
杰森

克里斯·奥'Brien说过...

嗨杰森,

嗯,对我来说,这表明文件系统有些奇怪-我以前从未见过/听说过这个文件系统。我会检查诸如:

-您是否正在尝试通过映射的驱动器访问文件(例如功能文件)?
-您的磁盘空间不足吗?
-SQL事务日志是否已满?

您的错误很可能不是来自功能框架或SharePoint本身,而是来自下面的错误。

HTH,

克里斯。

匿名 said...

嘿克里斯,

Have you tried to deploy a site with lookup columns created in 这个 manner using the Content Deployment wizard. You will get a strange error that says: "The element 'FieldTemplate' in namespace 'urn:deployment-manifest-schema' has invalid child element 'Field' in namespace 'http://schemas.microsoft.com/sharepoint/'. List of possible elements expected: 'Field' in namespace 'urn:deployment-manifest-schema'. 在 ..."
我还没有找到解决方法。关于内容部署API为何会感到困惑的任何想法?

克里斯·奥'Brien说过...

吉姆,你好

Yikes,那是一个有趣的话题-我实际上认为我 一起使用这些东西。我没有遇到这个问题,但是不久前,我再也无法访问该网站了。

从逻辑上看问题,部署API抱怨'Field'元素位于错误的名称空间中。除了不应该发生这种情况外,我的评论是:

-该错误表明字段定义有问题。我想知道这是否与CAML定义有关,而不是与代码中发生的GUID修复有关。
-我没有解决方案,但是可能的解决方法是将.cmp文件重命名为cab,然后在其中处理有问题的文件(您必须以某种方式进行搜索)。然后,您将重新生成cab文件(包括新修改的文​​件),最后将其重命名为.cmp。这很可能成功导入。

显然,理想的解决方案是从源头上解决问题。让我知道您是否找到解决方案。

祝你好运

克里斯。

匿名 said...

克里斯,你好

做得好!
一个问题:
我注意到,当我查询包含查找列的列表时,查找列不会出现在列列表中。

例如,如果您查询_vti_bin / Lists.asmx的GetList方法

希望您能指出一些解决方案。

克里斯·奥'Brien说过...

嗨,莫顿,

嗯,怕我不知道为什么会这样。这仅限于列表Web服务吗?如果您遍历SPList.Fields集合,会出现查询列吗?

谢谢,

克里斯。

克里斯·奥'Brien说过...

嗨,莫顿,

嗯,怕我不知道为什么会这样。这仅限于列表Web服务吗?如果您遍历SPList.Fields集合,会出现查询列吗?

谢谢,

克里斯。

匿名 said...

克里斯,你好

我发现SchemaXML的xmlns属性导致了此行为。
我不知道,是否有包括该属性的特定原因?
如果没有,也许您可​​以将其从Codeplex上的项目中删除。

克里斯·奥'Brien说过...

嗨,莫顿,

是的,我认为其他一些人也对此有所了解。感谢您向我指出这一点,我将尽快尝试并更新Codeplex项目。

同时,其他用户应注意此主要修订!

谢谢,

克里斯。

匿名 said...

克里斯

感谢这篇文章。您的技术效果很好,但是您可能需要对Codeplex项目进行一个简单的修改。

当我使用Word 2007在库中创建内容类型包括使用功能激活代码创建的查找字段的文档时,发现了一个问题。当Word提供用于填充列表项的列的表单时,将显示“找不到库”异常,并且查找列的值的下拉列表为空。

经过数小时的挫败,我发现在List属性中使用GUID的花括号格式(例如List =“ {e34bb359-4f60-49cc-8b28-77d82ec758a3}”)解决了该问题。

功能激活码中的代码行

字符串sPopulatedGuid = string.Format(“ List = {0}”,referencedList.ID);

创建没有括号的属性值,该属性值似乎定义了令人满意的查找列,直到Word尝试通过Sharepoint Web Service使用它为止。

用以下内容替换行应该可以解决问题

字符串sPopulatedGuid =“列表= {” + referencedList.ID.ToString()+“}”;

汤姆

克里斯·奥'Brien说过...

你好汤姆,

非常感谢您的更正-我将在可能的情况下更新Codeplex项目。

非常感激,

克里斯。

匿名 said...

A follow up to Jim Brown's comment above concerning the namespace error: "The element 'FieldTemplate' in namespace 'urn:deployment-manifest-schema' has invalid child element 'Field' in namespace 'http://schemas.microsoft.com/sharepoint/'. List of possible elements expected: 'Field' in namespace 'urn:deployment-manifest-schema'. 在 ..."

有关可能的解决方案,请参见克里斯的Codeplex项目下的以下问题:

http://www.codeplex.com/SP2007LookupFields/WorkItem/View.aspx?WorkItemId=10172

sgoodyear的这个建议对我来说就像一个魅力。

里克·梅森 said...

对于任何有问题的人"本地设备名称已被使用",我刚走过这一关。

当我尝试使用格式不正确的XML使用AddFieldAsXml时,问题就开始了。那'显然以某种方式在数据库中设置了错误的设置。

当我更改了要添加的字段的内部名称时,问题消失了。

我还找到了网站栏的副本'不能在用户界面中删除,但我使用功能接收器中的代码将其删除,然后删除了代码。

匿名 said...

我知道这是一个旧线程,但以为我'd ask.
我成功地为列表,站点列(从列表中查找数据)和内容类型(使用站点列)创建了功能。所有范围"Site"。在一个级别的网站上,我创建了一个库,关联了内容类型,上传了一个文件,但是在编辑页面上我的查找(网站列)为空。当我查看库设置中的列时,我看到"Get Information from"是空白的。但是在站点列库中,我对其进行了检查,它正确地指向了我的列表。
任何人对此都有任何想法。
谢谢,
侯凯文