SharePoint 2013本地环境C# CSOM下载历史版本报404错误
问题背景
我需要通过C#控制台程序从SharePoint 2013本地文档库下载文档历史版本,连同列属性按时间顺序迁移至SharePoint Online(SPO)。当前最新版本可正常下载,但旧历史版本下载始终失败,核心问题如下:
- 标准CSOM方法受限:SharePoint 2013 v15 SDK中不存在
ListItem.Versions; - 直接调用HTTP端点持续报错,收到的具体异常信息:
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
可复现代码示例
using System; using System.IO; using System.Net.Http; using Microsoft.SharePoint.Client; using SP = Microsoft.SharePoint.Client; class Program { static void Main() { string siteUrl = "https://example.com"; using (ClientContext sourceCtx = new ClientContext(siteUrl)) { sourceCtx.Credentials = System.Net.CredentialCache.DefaultCredentials; List sourceList = sourceCtx.Web.Lists.GetByTitle("MyLibrary"); CamlQuery query = CamlQuery.CreateAllItemsQuery(); ListItemCollection items = sourceList.GetItems(query); sourceCtx.Load(items, ic => ic.Include( item => item.Id, item => item.File, item => item.File.Versions, item => item.File.ServerRelativeUrl )); sourceCtx.ExecuteQuery(); foreach (ListItem item in items) { if (item.File == null || !item.File.Exists) continue; foreach (FileVersion ver in item.File.Versions) { string absoluteVersionUrl = siteUrl.TrimEnd('/') + "/" + ver.Url.TrimStart('/'); using (var handler = new HttpClientHandler { Credentials = sourceCtx.Credentials }) using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0"); // CRITICAL FAILURE HERE: Always throws 404 Not Found HttpResponseMessage response = client.GetAsync(absoluteVersionUrl).Result; response.EnsureSuccessStatusCode(); using (Stream fileStream = response.Content.ReadAsStreamAsync().Result) { // Upload logic to SPO goes here } } } } } } }
版本URL属性返回格式:_vti_history/512/Folder/Doc.docx
问题解答
1. 为何直接访问_vti_history路径会返回404?
SharePoint 2013的_vti_history路径存在多个限制,导致直接访问失败:
- 权限拦截:即使拥有文档库读写权限,访问
_vti_history默认需要站点管理员级别的权限,普通权限会被拒绝并返回404; - 路径有效性问题:
ver.Url中的版本号(如512)是站点内部的版本标识,并非文档的实际版本号,且如果站点是Web应用下的子站点,直接拼接的URL可能忽略了子站点路径,导致路径无效; - 功能禁用:若站点管理员通过Web应用程序设置禁用了
_vti_history路径的访问,也会返回404。
2. SharePoint 2013 v15的替代解决方案
方案1:使用CSOM的FileVersion.OpenBinaryDirect
SharePoint 2013 CSOM支持通过File.Versions获取历史版本对象,直接调用OpenBinaryDirect获取流,无需拼接URL:
foreach (ListItem item in items) { if (item.File == null || !item.File.Exists) continue; foreach (FileVersion ver in item.File.Versions) { using (ClientContext ctx = new ClientContext(siteUrl)) { ctx.Credentials = sourceCtx.Credentials; // 根据文件相对路径获取文件,再获取指定版本 File file = ctx.Web.GetFileByServerRelativeUrl(item.File.ServerRelativeUrl); FileVersion targetVersion = file.Versions.GetById(ver.VersionId); ctx.Load(targetVersion); ctx.ExecuteQuery(); // 打开历史版本的二进制流 using (Stream stream = Microsoft.SharePoint.Client.File.OpenBinaryDirect(ctx, targetVersion.Url).Stream) { // 此处添加上传至SPO的逻辑 } } } }
注意:需确保使用的是SharePoint 2013对应的CSOM包(版本v15.x),避免版本不兼容。
方案2:使用官方REST API端点
通过SharePoint 2013 REST API的专用端点获取历史版本二进制内容,无需访问_vti_history路径:
foreach (ListItem item in items) { if (item.File == null || !item.File.Exists) continue; foreach (FileVersion ver in item.File.Versions) { // 构建REST请求URL,注意转义文件路径 string restEndpoint = $"{siteUrl.TrimEnd('/')}/_api/web/getfilebyserverrelativeurl('{Uri.EscapeDataString(item.File.ServerRelativeUrl)}')/versions({ver.VersionId})/$value"; using (var handler = new HttpClientHandler { Credentials = sourceCtx.Credentials }) using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Add("Accept", "application/octet-stream"); client.DefaultRequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f"); HttpResponseMessage response = client.GetAsync(restEndpoint).Result; response.EnsureSuccessStatusCode(); using (Stream fileStream = response.Content.ReadAsStreamAsync().Result) { // 此处添加上传至SPO的逻辑 } } } }
该端点通过官方API验证权限,路径格式稳定,不会出现404问题。
内容的提问来源于stack exchange,提问作者Kumbhar Kanchan
相关产品推荐
相关产品推荐

