SharePoint2010 新特性Document ID的工作原理

  今天尝试在自己环境中应用Document ID,希望能够了解一下他的应用场景,Document ID是SharePoint2010的众多新特性中的一个,主要应用场景是,当用户创建了各种文档,但是因为各种原因调整了她的位置,例如从一个lib move到了另外一个lib(同一个site collection下面),或者同一个lib里面的不同folder的move,为了结构调整。但是在SharePoint中文档的URL和文档是直接对应的,移动了位置它的URL就变化了,那么以前一些Link直接引用文档的URL的就作废了,而且,用户在深层次路径下面去寻找一个文档也比较困难,毕竟记住那么长一串URL不是所有人愿意的,这个时候如果有一个简单的,唯一的,不变得URL那么将是多么的方便,Document ID就这样诞生了。

  在SharePoint2010中默认是没有启用这个功能的,想启用只需要激活site collection feature 即可。如图:

之后上传新文档或者修改一个已存在的文档之后,view这个文档的时候,在view页面中就会看到多出一个field:Document ID:如图:

鼠标放上去之后,可以看到URL是一个格式为:http://<Site Collection URL>/_layouts/DocIdRedir.aspx?ID=HSE6QJTMYFJX-1-2 DocIDRedir.aspx页面看来就是关键,在同一个Site Collection下面的文档,只要有Document ID,那么传入这个页面之后就可以被重定向到这个文档真正的位置,而这个位置的寻找工作无疑就是这个页面OnLoad()的逻辑。

  到此似乎功能很简单,逻辑很清晰简单,也不过如此。那么第一个问题来了,这个ID是怎么被转换的,它存储在什么位置,怎么能够通过它查询到对应的文档呢?了解SharePoint数据库的开发人员应该知道,一个SiteCollection的Document的很多信息都存储在ContentDB中的AllDocs和AllUserData表中,而一般像这种通过feature激活才会有的扩展字段肯定存储在AllUserData表中的扩展字段中,所以打开SQLServer查询对应的文档记录,你会看到Nvarchar10,Nvarchar11,nvarchar12三个字段中存放着对应的Document ID,那么很简单了,SharePoint一定是通过查询这个字段反向找到对应的File,然后显示给前台进行重定向,似乎很有道理,一切都说得通了。

  第二个问题来了,当我又查看了另外一个document library,发现它对应存放的document id的扩展字段变成了nvarchar7,nvarchar8,nvarchar9,这样就说不通了,虽然都在同一个AllUserData表中,但是扩展字段不同,SharePoint肯定有一种方式统一查询,否则无法在一张表中的不同字段间查询是否存在同一个document ID。这么看查询逻辑并不像之前想的那么简单了,需要看看到底SharePoint如何来做的,在SharePoint Hive目录下面的找到Layouts folder里面的DocIdRedir.aspx,用记事本打开发现内容如下:

<%-- _lcid="1033" _version="14.0.4758" _dal="1" --%>
<%-- _LocalBinding --%>
<%@ Assembly Name="Microsoft.Office.DocumentManagement.Pages, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page language="c#" Codebehind="DocIdRedir.aspx.cs" AutoEventWireup="false" Inherits="Microsoft.Office.DocumentManagement.Pages.DocIdRedir" %>

看来这个页面的后台逻辑在"Microsoft.Office.DocumentManagement.Pages, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c中,那么用Reflector查看他的code应该会有所发现,果然在Microsoft.Office.DocumentManager.Pages.dll中找到了他的后台code:

DocIDRedir
[PermissionSet(SecurityAction.Demand, Name="FullTrust"), PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
public class DocIdRedir : LayoutsPageBase
{
// Methods
public DocIdRedir();
private static string GetSearchUrl(SPWeb web, string docId);
private static string GetWacUrl(SPListItem spListItem);
protected override void OnLoad(EventArgs e);
}

察看OnLoad方法的code,可以看到,有一段code是关键,

View Code
 try
{
strArray = DocumentId.FindUrlsById(contextSite, str);
}
catch (Exception exception)
{
SPUtility.TransferToErrorPage(string.Format(CultureInfo.InvariantCulture, (string) base.GetGlobalResourceObject("dlcdm", "DocIdSettings_Error_Lookup_Exception_Format_2"), new object[] { str, exception.Message }));
}

strArray = DocumentId.FindUrlsById(contextSitestr)中的str是URL中的参数ID,也就是Document ID,而contextSite就是当前上下文的SPSite对象,可以看出,SharePoint通过这个方法在当前SiteCollection中利用Document ID返回了对应Document的URL,然后重定向。那么核心的转换过程就应该在FindUrlsById这个里面了,继续进入察看

FindUrlsById
public static string[] FindUrlsById(SPSite site, string docId)
{
if (site == null)
{
throw new ArgumentNullException("site");
}
DocumentIdProvider iProvider = GetProvider(site);
bool doCustomSearchBeforeDefaultSearch = iProvider.DoCustomSearchBeforeDefaultSearch;
Func<string[]> func = delegate {
return DocIdLookup.DoSearch(site, docId);
};
Func<string[]> func2 = delegate {
return iProvider.GetDocumentUrlsById(site, docId);
};
string[] strArray = (doCustomSearchBeforeDefaultSearch ? func2 : func)();
if ((strArray == null) || (strArray.Length == 0))
{
strArray = (doCustomSearchBeforeDefaultSearch ? func : func2)();
}
return strArray;
}

一切的转换其实是通过DocumentIdProvider这个抽象类来完成的,而GetProvider这个工厂方法就是创建具体Provider的地方,继续进入你会发现其实每一个SiteCollection的root web中都记录了Provider的信息

rootWebProperties["docid_customProvider_assembly"]
rootWebProperties["docid_customProvider_class"] 

很明显通过这两个属性,SharePoint可以动态加载Provider,完成Document ID相关的处理,而如果用户希望自定义Document Id的逻辑,那么也可以重新实现一个Provider,同时注册到当前的Site Collection中就可以了。Provider的抽象定义如下:

DocumentIdProvider
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true), SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true)]
public abstract class DocumentIdProvider
{
// Methods
protected DocumentIdProvider()
{
}
//用于产生一个新的Document Id
public abstract string GenerateDocumentId(SPListItem listItem);
//通过document id返回对应的document的实际URL
public abstract string[] GetDocumentUrlsById(SPSite site, string documentId);
//当前Provider所产生的Document Id的格式化例子。
public abstract string GetSampleDocumentIdText(SPSite site);

// Properties
public abstract bool DoCustomSearchBeforeDefaultSearch { get; }
}

到目前为止,看来只要找到默认SharePoint的Provider,那么就可以看到默认解析逻辑了:),使用Power Shell或者SharePoint API,可以很容易的查找Root web的properties属性,找到上面两个value就是默认的Provider:

public sealed class OobProvider : DocumentIdProvider
Name: Microsoft.Office.DocumentManagement.Internal.OobProvider
Assembly: Microsoft.Office.DocumentManagement, Version=14.0.0.0

篇幅原因就不在粘贴code了,感兴趣的可以自己去查看了,我这里就快速的说一下他的原理了,原来,每一个SiteCollection的root web里面都有几个属性是和Document Id相关的:

docid_msft_hier_listcnt:用于记录当前已经开始使用Document Id的list的个数,从1开始,如果一个list里面有document产生了document ID,那么用当前的这个属性的value来作为这个list的序号(后面会说明这个序号的用处),同时这个属性自身加1。

docid_msft_hier_siteprefix :当前SiteCollection下面所有Document Id的前缀,从前面的图可以看到,我的都是"HSE6QJTMYFJX"。

docid_msft_hier_listidx:是一个xml,记录每一个已经存在使用Document Id的library的ID和序号(xml中的key)的对应关系,而序号就是那个大于0小于等于docid_msft_hier_listcnt属性value的范围。xml如下:

View Code
<?xml version="1.0" encoding="utf-16"?>
<DictionarySerializer>
<dictionary>
<item>
<key>1</key>
<webGuid>4304b03e-6677-4b1f-9677-34a8d0dcb703</webGuid>
<listGuid>6a8f7a76-6ad9-4085-99db-35cea4c58644</listGuid>
</item>
<item>
<key>2</key>
<webGuid>4304b03e-6677-4b1f-9677-34a8d0dcb703</webGuid>
<listGuid>a6d61471-0e0d-430b-9f25-54a6f8ca3b5d</listGuid>
</item>
<item>
<key>3</key>
<webGuid>4304b03e-6677-4b1f-9677-34a8d0dcb703</webGuid>
<listGuid>7752bca5-5a50-4031-9514-c7ab2a578af1</listGuid>
</item>
</dictionary>
</DictionarySerializer>

而到这里我们可以看到一个默认的document Id的格式是HSE6QJTMYFJX-1-2,其中HSE6QJTMYFJX是前缀,同一个SiteCollection都是一样的,第一个"1"是对应Library的序号,第二个"2"对应的是在当前library中的document的doclibrow ID。通过刚才root web中的这几个属性,我们就可以反向找到每一个document了,而alluserdata表中扩展字段存储的仅仅是为了显式和Link使用。到此为止,整个SharePoint的Document Id的转换逻辑分析完毕。

再多说几句,对于每一个使用了Document Id的Library来说它的SPList对象的Properties中会有对应的value:

docid_msft_hier_listid:对应当前Library的序号,和上面rootweb中的那个xml中的key对应。

docid_msft_hier_list_siteid:当前document ID所在的SiteCollection ID。

备注:要强调的是,如果你打开了Document ID这个feature,无论是新创建还是已经存在的Library里面都不会有上面的属性,只有上传了一个document或者修改一个已存在的document,产生了一个document Id之后,这些属性才会在SPList的properties中出现,同时会更新rootweb的docid_msft_hier_listcnt。

 

相关Reflector的Assembly为:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\BIN\Microsoft.Office.DocumentManagement.Pages.dll

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.Office.DocumentManagement.dll




posted @ 2012-01-27 18:12  咚咚  阅读(1361)  评论(1编辑  收藏  举报