65.9K
CodeProject 正在变化。 阅读更多。
Home

用 C# 读取 WebFolder 的内容

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (10投票s)

2006年3月1日

5分钟阅读

viewsIcon

124889

downloadIcon

812

分析以编程方式获取 WebFolder(包括 SharePoint)中的文件列表的各种方法,并提供一个简单的解决方案。示例代码适用于 C# 2.0。

问题

我曾经需要对位于 SharePoint Portal Server 的 Microsoft Word 文档进行一些自动化处理。但我遇到了一个棘手的问题——如何以编程方式查找 SharePoint 中的所有文档?

调查 – 失败与挑战

尝试 FileSearch 接口

首先,由于我之前已经使用过 Word 自动化接口,所以我尝试使用 Microsoft Office API 中实现的搜索功能,即 Office `Application` 对象公开的 `FileSearch` 接口。我曾认为,既然 Microsoft Word 能够直接从 SharePoint 打开文档,那么它当然也能搜索服务器上的 Word 文档…

天真的想法——当然 **不能**。事情永远不会简单到你可以放松并享受“著名”的 KISS 原则的乐趣。它只能在本地磁盘或最多是网络文件共享上正常工作。

尝试 SharePoint Web 服务

好吧,我知道 SharePoint Server 以 Web 服务形式公开其内容。我也尝试过那些东西,但我觉得我需要处理的实体比这个简单任务所要求的要复杂得多——有很多不同类型的列表和项目。而且,我不确定不同版本的 SharePoint Server(如 2001、2003 和下一个 2006)之间的兼容性,此外,我想要一个不仅能与 SharePoint Portal Server 配合使用,而且能与 Windows Explorer 中可以浏览的任何 WebFolder 配合使用的解决方案(例如,运行在纯 Windows Server 2003 下)。

尝试 WebDAV 和 Web Extender Client (WEC) 协议

您可能知道,只有当 IIS 服务器支持 FrontPage Web Extender Client (WEC) 或 Web Distributed Authoring and Versioning (WebDAV) 协议扩展时,才可以在 Windows Explorer 中浏览 WebFolder。

有多种方法可以发出 WebDAV/WEC 请求——通过构造 XML 请求,或通过在 HTTP 请求中编写一组特定的 HTTP 标头。这些都需要深入研究标准。

我发现 MS Office 产品使用 `FPWEC.DLL` COM 库,通常位于 `%PROGRAMFILES%\Common Files\Microsoft Shared\web server extensions\60\BIN`。它包含一组这些协议的包装器,但我没有找到关于它们的文档,并且它们的使用看起来并不直接,主要是以异步方式。

不幸的是,我没有找到任何免费的、真正工作的 C# 库,可以让我轻松使用 WebDAV。

一种方法是使用旧 ADO 中的 **OLE DB Provider for Internet Publishing**。但是,MSDN 上有这样的说法:“.NET Framework Data Provider for OLE DB 不支持 OLE DB 2.5 接口。需要支持 OLE DB 2.5 接口的 OLE DB Provider 将无法与 .NET Framework Data Provider for OLE DB 正常运行。这包括 Microsoft OLE DB Provider for Exchange 和 **Microsoft OLE DB Provider for Internet Publishing**”。我不喜欢从我的 C# 代码中使用 ADO 2.5 接口的 COM 互操作,所以我跳过了这个解决方案。不过,您可以尝试一下——在某些情况下可能非常可行。

替代解决方案 – WebFolder 快捷方式

突然,我记得有时当您尝试浏览 WebFolder 时,一个指向该 WebFolder 的快捷方式会被添加到 Windows Explorer 中可用的“我的网络位置”文件夹。

您可能知道,这些快捷方式实际上放置在 `c:\Documents and Settings\{USER NAME}\NetHood` 隐藏文件夹中。WebFolder 快捷方式是一个具有只读文件属性的物理目录(目录名称等于快捷方式名称),其中包含两个特定文件:

  • `Desktop.ini`(文件属性为隐藏、系统)——一个文本文件,其中包含处理如何向用户显示快捷方式内容的 shell 扩展的 ProgID。
  • `target.lnk` – 一个二进制文件,其中包含指向快捷方式所表示的 WebFolder 的 URL。

所以,我尝试像 Windows Explorer 所预期的那样读取 WebFolder 快捷方式的内容;我使用了“Shell Objects for Scripting”(VBScript 示例)

dim oShell
dim oFolder
dim sDir
Dim s

set oShell = CreateObject("Shell.Application")
set oFolder = oShell.NameSpace("c:\Documents and" & _ 
              " Settings\Administrator" & _ 
              "\NetHood\The_ShortCut_Name")

for i=0 to oFolder.Items.Count-1
    s = s + oFolder.Items.Item(i).Path+ _
            chr(10)+chr(13)
next

MsgBox s

……然后它奏效了。在内部,适当的 shell 扩展(已随 Windows 预装)提供了必要的 `Folder` 和 `FolderItem` 对象,为我们完成了与 Web 服务器通过 WebDAV/WEC 交互的所有繁重工作——我们只需利用它们的结果。请注意,将纯 HTTP URL 传递给 `Shell.Application` 对象将不起作用。

最近,我偶然发现了一个允许创建 WebFolder 快捷方式的代码(Code Ccomments)——非常感谢作者。我将代码转换成了 C#

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections.Specialized;
using System.Text.RegularExpressions;

namespace Utils
{
    public static class PathRoutines
    {

        //'44 seems to be the length where we have 

        //to change a byte from 00 to a 01.

        private const int URL_CUTOFF = 44;

        //This is where we construct the target.lnk

        //file byte by byte. Most of the lines 

        //are shown in 16 byte chunks,

        //mostly because that is the way I saw it 

        //in the Debug utility I was using to inspect shortcut files.

        private static readonly byte[] LINK_CONTENT_PREFIX = new byte[]
        {//Line 1, 16 bytes

          0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 
          0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 
          0x00, 0x00, 0x00,
         //Line 2, 16 bytes

          0x00, 0x00, 0x00, 0x46, 0x81, 0x00, 0x00, 
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
          0x00, 0x00, 0x00, 
        //Line 3, 16 bytes

          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
          0x00, 0x00, 0x00,
        //Line 4., 16 bytes. 13th byte is significant.

          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
          0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 
          0x00, 0x00, 0x00,
        //Line 5. 13th byte is significant.

          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
          0x00, 0x00, 0x00, 0x00, 0x00,
        //When I was analyzing the next byte of shortcuts 

        //I created, I found that it is set to various values,

        //and I have no idea what they are referring to. 

        //In desperation I tried substituting some values.

        //00 caused a crash of Explorer. FF seeems to work fine for all.

          0xFF};

        private static readonly byte[] LINK_CONTENT_MID = new byte[]
        {
            0x14, 0x00,
            //Line 6, 16 bytes

            0x1F, 0x50, 0xE0, 0x4F, 0xD0, 0x20, 0xEA, 0x3A, 
            0x69, 0x10, 0xA2, 0xD8, 0x08, 0x00, 0x2B, 0x30,
            //Line 7, 16 bytes

            0x30, 0x9D, 0x14, 0x00, 0x2E, 0x00, 0x00, 0xDF, 
            0xEA, 0xBD, 0x65, 0xC2, 0xD0, 0x11, 0xBC, 0xED,
            //Line 8, 16 bytes

            0x00, 0xA0, 0xC9, 0x0A, 0xB5, 0x0F, 0xA4
        };

        private static readonly 
                byte[] LINK_CONTENT_MID2 = new byte[]
        {
            0x4C, 0x50, 0x00, 0x01, 0x42, 0x57, 0x00, 0x00,
            //Line 9, 16 bytes

            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
            //Line 10, 2 bytes

            0x00, 0x00
        };

        private static readonly 
                byte[] LINK_CONTENT_POSTFIX = new byte[]
        {
            //Last line, 13 bytes

            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };        

        /// <summary>

        /// Returns path to a directory with name 

        /// 'shortcutName' that is mapped

        /// to the specified web siteUrl.

        /// If the directory already exists - does nothing

        /// </summary>

        /// <param name="siteUrl">The target url</param>

        /// <param name="shortcutContainerPath">Folder where

        ///           the shortcut folder mapped to

        /// the specified web siteUrl will be created</param>

        /// <param name="shortcutName">Name of the 

        ///          shortcut folder</param>

        public static string CreateWebFolderLink(string siteUrl, 
               string shortcutContainerPath, string shortcutName)
        {
            if (siteUrl.Length > 255)
                throw new ArgumentOutOfRangeException("Length" + 
                      " of Url [" + siteUrl + "] is more" + 
                      " than 255 symbols");

            string localFolderPath = 
              Path.Combine(shortcutContainerPath, shortcutName);

            DirectoryInfo localDir = null;
            if (Directory.Exists(localFolderPath))
                return localFolderPath;

            localDir = Directory.CreateDirectory(localFolderPath);
            localDir.Attributes = FileAttributes.ReadOnly;

            string desktop_ini_filePath = 
              Path.Combine(localFolderPath, "Desktop.ini");
            StreamWriter writer = new 
              StreamWriter(desktop_ini_filePath, false);
            writer.WriteLine("[.ShellClassInfo]");
            writer.WriteLine("CLSID2={0AFACED1" + 
                   "-E828-11D1-9187-B532F1E9575D}");
            writer.WriteLine("Flags=2");
            writer.WriteLine("ConfirmFileOp=0");
            writer.Close();

            string lnk_filePath = 
              Path.Combine(localFolderPath, "target.lnk");
            FileStream fs = new FileStream(lnk_filePath, 
                                       FileMode.Create);
            BinaryWriter stream = new BinaryWriter(fs, 
                           Encoding.BigEndianUnicode);
            stream.Write(LINK_CONTENT_PREFIX, 0, 
                         LINK_CONTENT_PREFIX.Length);

            //'This byte is 00 if the URL is 44 characters 

            //or less, 01 if greater.

            if (siteUrl.Length > URL_CUTOFF)
            {
                stream.Write((byte)0x01);
            }
            else
            {
                stream.Write((byte)0x00);
            }

            stream.Write(LINK_CONTENT_MID, 0, 
                         LINK_CONTENT_MID.Length);

            //'This byte is 00 if the URL is 44 characters 

            //or less, 01 if greater.

            if (siteUrl.Length > URL_CUTOFF)
            {
                stream.Write((byte)0x01);
            }
            else
            {
                stream.Write((byte)0x00);
            }

            stream.Write(LINK_CONTENT_MID2, 0, 
                         LINK_CONTENT_MID2.Length);

            //The next byte represents the length of the site name.

            stream.Write((byte)shortcutName.Length);

            char[] webFolderNameChars = 
                         shortcutName.ToCharArray();
            stream.Write(webFolderNameChars, 0, 
                         webFolderNameChars.Length);

            //Middle line, separates the Folder Name 

            //from the URL. 3 bytes.

            stream.Write((byte)0x00);
            stream.Write((byte)0x00);
            stream.Write((byte)0x00);

            //The next byte represents the length of the site URL.

            stream.Write((byte)siteUrl.Length);

            char[] sitePathChars = siteUrl.ToCharArray();
            stream.Write(sitePathChars, 0, sitePathChars.Length);

            stream.Write(LINK_CONTENT_POSTFIX, 0, 
                         LINK_CONTENT_POSTFIX.Length);

            stream.Close();

            return localFolderPath;
        }
    }
}

顺便说一句,您可以使用 `CreateWebFolderLink` 方法创建必要的 WebFolder 快捷方式以使其在“我的网络位置”文件夹中可见;为此,只需将 `"c:\Documents and Settings\{USER NAME}\NetHood"` 字符串作为 `shortcutContainerPath` 参数传递。

所以现在,当有人想获取 WebFolder 中的文件列表时,算法如下:

  • 我们创建一个指向 WebFolder 的临时快捷方式。
  • 我们使用 `Shell32` 来读取其内容。

创建指向 WebFolder 的临时快捷方式

/// <summary>

/// Returns path to a shortcut folder that is mapped

/// to the specified web siteUrl. Name of the folder is derived

/// automatically.

/// If the shortcut folder already exists - does nothing

/// </summary>

/// <param name="siteUrl"></param>

/// <param name="shortcutContainerPath"></param>

/// <returns></returns>

public static string CreateWebFolderLink(Uri siteUrl)
{
    //derive shortcut name

    string sitePath = siteUrl.ToString();
    string shortcutName = 
           sitePath.GetHashCode().ToString();

    return CreateWebFolderLink(sitePath, 
           Path.GetTempPath(), shortcutName);
}

使用 Shell32 读取内容

为了使以下代码能够编译,您需要向您的项目添加一个 COM 引用,指向 `Shell32` 库(其标题为“Microsoft Shell Controls and Automation”)。

/// <summary>

/// Uses Shell32

/// </summary>

/// <param name="siteUrl"></param>

/// <param name="filePathPattern">For example,

///        to make a wildcard search ".doc" use the following

/// reg expression: Regex regex = new Regex(@".*\.doc",

///         RegexOptions.Compiled | RegexOptions.IgnoreCase);

/// to exclude word template documents assigned

/// to a SharePoint workspace, use the following 

/// reg expression: new Regex(@"(?<!.*/Forms/[^/]+)\.doc$", 
///          RegexOptions.Compiled | RegexOptions.IgnoreCase)</param>

/// <param name="searchSubFolders"></param>

/// <returns></returns>

public static StringCollection 
       SearchFilesInWebFolder(Uri siteUrl, 
       Regex filePathPattern, bool searchSubFolders)
{
    string mapFolderPath = CreateWebFolderLink(siteUrl);
    
    StringCollection ret = new StringCollection();

    Shell32.ShellClass shell = new Shell32.ShellClass();

    Shell32.Folder mapFolder = shell.NameSpace(mapFolderPath);
    SearchInFolder(mapFolder, filePathPattern, searchSubFolders, ret);

    return ret;
}

private static void SearchInFolder(Shell32.Folder folder, 
        Regex filePathPattern, bool searchSubFolders, 
        StringCollection resultList)
{
    foreach (Shell32.FolderItem item in folder.Items())
    {
        if (item.IsLink)
            continue;

        if (item.IsFolder && searchSubFolders)
        {
            SearchInFolder((Shell32.Folder) item.GetFolder, 
                filePathPattern, searchSubFolders, resultList);
            continue;
        }

        if (filePathPattern.IsMatch(item.Path))
        {
            resultList.Add(item.Path);
        }  
    }
}

结论

建议的解决方案似乎非常简单且适用于 Windows 或控制台应用程序。但是,对于服务器应用程序,请谨慎使用——您很可能会遇到一些安全问题(我有点怀疑“ASP.NET”用户是否能够使用 Shell32 COM 互操作)和多线程问题。在这些情况下,我认为最好的方法仍然是直接使用 Web 服务或 WebDAV/WEC 协议。

© . All rights reserved.