通过 ResourcePath API 支持文件和文件夹中的 % 和 #

对文件和文件夹中 % 和 # 的支持已部署到 SharePoint Online 中。 遗憾的是,由于现有的 API 结构和调用模式,有时不确定是否可以使用这些文件名。 你可以在我们的开发人员博客上找到此问题的更多背景知识

总而言之,新的 API 已添加到 SharePoint Online 客户端对象模型 (CSOM) 外围,以提供对 # 和 % 字符的支持。

新的 ResourcePath 类

回顾一下,值得注意的是,基于字符串的现有 SharePoint API(例如 SPFileCollection.GetByUrl)通过自动假设路径中的 % 和 # 字符表示 URL 已编码,从而处理已编码和已解码的 URL。 新增对文件和文件夹中 % 和 # 的支持后,这种自动处理现在是存在问题的,因为它可能导致下游代码忽略或不当处理带有 % 和 # 的文件名。

已向 API 添加一个新类:Microsoft.SharePoint.Client.ResourcePath。 ResourcePath 类是支持这些新字符的基础。 它提供了一种明确的全新方式来解决 SharePoint 和 OneDrive for Business 中的一个问题,这是因为它假定 URL 已解码并且仅使用已解码 URL。 它代替基于字符串的路径来表示网站集、站点、文件、文件夹或其他项目以及 OneDrive for Business 的完整(绝对)或部分(相对)URL。 要正确支持 URL 中的 % 和 #,必须使用基于 ResourcePath 的 API 以及已解码 URL。

ResourcePath 只需通过调用静态函数 ResourcePath.FromDecodedUrl(string) 即可进行构造。 可以通过调用属性 DecodedUrl 从 ResourcePath 对象访问传入的输入值。

对于需要使用基于字符串的 URL 的现有调用,必须确定是否已使用编码或解码的 URL 提供代码路径来促成基于字符串的 URL 调用,以及这些代码路径是否也允许定位标记链接(即在文件 URL 基础之上添加 # 书签。)

注意

不要简单地使用 ResourcePath.FromDecodedUrl 查找和替换基于字符串的 URL API 的现有用法。 使用 ResourcePath.FromDecodedUrl(string) API 前,需要先正确确定 URL 并解码 URL(若可能)。

如果促成基于字符串的 SharePoint API 调用的代码路径使用了已解码 URL(例如“FY17 Report.docx”),可以直接使用本文稍后介绍的 ResourcePath.FromDecodedUrl(string) 以及等效 ResourcePath 方法替代这些调用。

请注意,在许多情况下,通过 API 从 SharePoint 读取 URL 时,还会提供 ResourcePath 使这些代码模式更加一致,从而可使用 ResourcePath 替代 URL。

另请注意,此更改同样适用于基于 SharePoint REST 的调用。 请阅读下面的方案,以查看 REST 中这些新 API 的示例。

基于 ResourcePath 且支持 # 和 % 的新 API

下表展示我们引入以替代现有 API 的新 API,用于支持 # 和 %。 为便于对比,我们并排列出了旧 API 和新 API。 这些 API 的核心功能不会改变,但是表示项目位置的方式会。

有关新 API 的文档,请参阅 SharePoint Online 的 .NET 客户端 API 参考。 我们列出了 .NET CSOM API,但是我们的 JavaScript CSOM 库中也提供了相同形式的新方法。

程序集 Microsoft.SharePoint.Client.dll

类型 旧方法 新方法
Microsoft.SharePoint.SPList AddItem(Microsoft.SharePoint.SPListItemCreationInformation) AddItemUsingPath(SPListItemCreationInformationUsingPath parameters)
Microsoft.SharePoint.SPFile MoveTo(System.String, Microsoft.SharePoint.SPMoveOperations) MoveToUsingPath(SPResourcePath newPath, SPMoveOperations moveOperations)
Microsoft.SharePoint.SPFile CopyTo(System.String, Boolean) CopyToUsingPath(SPResourcePath newPath, bool overwrite)
Microsoft.SharePoint.SPFileCollection GetByUrl(System.String) GetByPath(SPResourcePath)
Microsoft.SharePoint.SPFileCollection Add(System.String, Microsoft.SharePoint.SPTemplateFileType) AddUsingPath(SPResourcePath, SPFileCollectionAddWithTemplateParameters)
Microsoft.SharePoint.SPFolder MoveTo(System.String) MoveToUsingPath(SPResourcePath newPath)
Microsoft.SharePoint.SPFolder AddSubFolder(System.String) AddSubFolderUsingPath(SPResourcePath leafPath)
Microsoft.SharePoint.SPFolderCollection GetByUrl(System.String) GetByPath(SPResourcePath path)
Microsoft.SharePoint.SPFolderCollection Add(System.String) AddUsingPath(SPResourcePath path, SPFolderCreationInformation parameters)
AddWithOverwrite(string url, bool overwrite)
Microsoft.SharePoint.SPRemoteWeb GetFileByServerRelativeUrl(System.String) GetFileByServerRelativePath(SPResourcePath path)
Microsoft.SharePoint.SPWeb GetList(System.String) GetListUsingPath(SPResourcePath)
Microsoft.SharePoint.SPWeb GetListItem(System.String) GetListItemUsingPath(SPResourcePath)

以下 CSOM 对象返回可以在这些 API 中使用的 ResourcePath 属性。 虽然旧属性也返回已解码 URL,但为了方便、简单和明确地调用这些 API,提供了新的 ResourcePath 属性。 一个例外情况是 SPFolder.WelcomePage 属性,它以前返回已编码和未编码的 URL;现在明确通过 WelcomePagePath 属性返回。


类型 旧属性 新属性
Microsoft.SharePoint.SPList DefaultViewUrl DefaultViewPath
Microsoft.SharePoint.SPList DefaultEditFormUrl DefaultEditFormPath
Microsoft.SharePoint.SPList DefaultNewFormUrl DefaultNewFormPath
Microsoft.SharePoint.SPList DefaultDisplayFormUrl DefaultDisplayFormPath
Microsoft.SharePoint.SPAttachment ServerRelativeUrl ServerRelativePath
Microsoft.SharePoint.SPFile ServerRelativeUrl ServerRelativePath
Microsoft.SharePoint.SPFolder ServerRelativeUrl ServerRelativePath
Microsoft.SharePoint.SPFolder WelcomePage WelcomePagePath/WelcomePageParameters
Microsoft.SharePoint.SPView ServerRelativeUrl ServerRelativePath
Microsoft.SharePoint.SPDocumentLibraryInformation ServerRelativeUrl ServerRelativePath

确定 URL 格式并可支持 # 和 % 的现有 API

以下 API 仅接受正确编码的 URL 作为输入。 它们还支持正在编码的 URL,只要 URL 可以在没有任何歧义的情况下使用即可。 也就是说,至少要对 URL 路径中的 # 或 % 字符进行 % 编码。 这些 API 将以现有方式继续运行。 URL 中的 # 被视为片段分隔符,但不是 URL 路径的一部分。

类型 旧属性
Microsoft.SharePoint.SPWeb GetFileByUrl(System.String)
Microsoft.SharePoint.SPWeb GetFileByWOPIFrameUrl(System.String)
Microsoft.SharePoint.SPWeb GetFileByLinkingUrl(System.String)

添加以下 C# 属性以返回明确编码的 System.Uri,以与上述 API 一起使用。 以下 API 的较旧变体返回已解码的 URL,由于它们从不包含 # 或 % 字符,因此 URL 非常明确。 我们不想在以后中断较旧变体的已解码行为,以在路径中编码 % 和 # 字符。 因此创建了新的 API。

类型 旧属性 新属性
Microsoft.SharePoint.SPFile LinkingUrl LinkingUri
Microsoft.SharePoint.SPFile ServerRedirectedEmbedUrl ServerRedirectedEmbedUri

示例代码

CSOM 方案

将文件添加到文件夹 (.net)

 ClientContext context = new ClientContext("http://site");
 Web web = context.Web;
 // Get the parent folder
 ResourcePath folderPath = ResourcePath.FromDecodedUrl("/Shared Documents");
 Folder parentFolder = web.GetFolderByServerRelativePath(folderPath);
 
 // Create the parameters used to add a file
 ResourcePath filePath = ResourcePath.FromDecodedUrl("/Shared Documents/hello world.txt");
 byte[] fileContent = System.Text.Encoding.UTF8.GetBytes("sample file content");
 FileCollectionAddParameters fileAddParameters = new FileCollectionAddParameters();
 fileAddParameters.Overwrite = true;
 using (MemoryStream contentStream = new MemoryStream(fileContent))
 {
  // Add a file
  Microsoft.SharePoint.Client.File addedFile = parentFolder.Files.AddUsingPath(filePath, fileAddParameters, contentStream);
 
  // Select properties of added file to inspect
  context.Load(addedFile, f => f.UniqueId, f1 => f1.ServerRelativePath);
 
  // Perform the actual operation
  context.ExecuteQuery();
 
  // Print the results
  Console.WriteLine(
   "Added File [UniqueId:{0}] [ServerRelativePath:{1}]",
   addedFile.UniqueId,
   addedFile.ServerRelativePath.DecodedUrl);
 }


将子文件夹添加到文件夹 (.net)

  ClientContext context = new ClientContext("http://site");
  Web web = context.Web;
  // Get the parent folder
  ResourcePath folderPath = ResourcePath.FromDecodedUrl("Shared Documents");
  Folder parentFolder = web.GetFolderByServerRelativePath(folderPath);
 
  // Create the parameters used to add a folder
  ResourcePath subFolderPath = ResourcePath.FromDecodedUrl("Shared Documents/sub folder");
  FolderCollectionAddParameters folderAddParameters = new FolderCollectionAddParameters();
  folderAddParameters.Overwrite = true;
 
  // Add a sub folder
  Folder addedFolder = parentFolder.Folders.AddUsingPath(subFolderPath, folderAddParameters);
 
  // Select properties of added file to inspect
  context.Load(addedFolder, f => f.UniqueId, f1 => f1.ServerRelativePath);
 
  // Perform the actual operation
  context.ExecuteQuery();
 
  // Print the results
  Console.WriteLine(
    "Added Folder [UniqueId:{0}] [ServerRelativePath:{1}]",
    addedFolder.UniqueId,
    addedFolder.ServerRelativePath.DecodedUrl);

获取 Web 中的文件 (.net)

  ClientContext context = new ClientContext("http://site");
  Web web = context.Web;
  // Get the file
  ResourcePath filePath = ResourcePath.FromDecodedUrl("/Shared Documents/hello world.txt");
  File file = web.GetFileByServerRelativePath(filePath);
 
  // Select properties of the file
  context.Load(file, f => f.UniqueId, f1 => f1.ServerRelativePath);
 
  // Perform the actual operation
  context.ExecuteQuery();
 
  // Print the results
  Console.WriteLine(
    "File Properties [UniqueId:{0}] [ServerRelativePath:{1}]",
    file.UniqueId,
    file.ServerRelativePath.DecodedUrl);

REST 方案

获取文件夹

url: http://site url/_api/web/GetFolderByServerRelativePath(decodedUrl='library name/folder name')
method: GET
headers:
  Authorization: "Bearer " + accessToken
  accept: "application/json;odata=verbose" or "application/atom+xml"


创建文件夹

url: http://site url/_api/web/Folders/AddUsingPath(decodedurl='/document library relative url/folder name')
method: POST
headers:
  Authorization: "Bearer " + accessToken
  X-RequestDigest: form digest value
  accept: "application/json;odata=verbose"
  content-type: "application/json;odata=verbose"
 

获取文件

url: http://site url/_api/web/GetFileByServerRelativePath(decodedUrl='folder name/file name')
method: GET
Headers:
  Authorization: "Bearer " + accessToken
  accept: "application/json;odata=verbose" or "application/atom+xml"


添加文件

url: http://site url/_api/web/GetFolderByServerRelativePath(decodedUrl='folder name')/Files/AddStubUsingPath(decodedurl='testfile.txt')
methods: POST
Headers:
  Authorization: "Bearer " + accessToken
  X-RequestDigest: form digest value
  accept: "application/json;odata=verbose"
  content-type: "application/json;odata=verbose"
  Content-Length: length
 

另请参阅