You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SharePoint 2013本地环境C# CSOM下载历史版本报404错误

SharePoint 2013本地文档库历史版本下载问题及解决方案

问题背景

我需要通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 01:54:52