【问题标题】:Download version file of SharePoint office 365下载 SharePoint office 365 版本文件
【发布时间】:2020-01-09 07:52:52
【问题描述】:

我尝试使用 c# 下载 SharePoint 的早期版本文件。我使用This article 作为参考。该链接正在使用 chrome 工作文件。现在,当我尝试使用 c# 上的 URL 部分下载文件时,它给了我 远程服务器返回错误:(401) Unauthorized. 错误。

我什至提供了用于函数头的访问令牌。

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            WebHeaderCollection header = new WebHeaderCollection();
            request.Headers.Add(System.Net.HttpRequestHeader.Authorization, $"Bearer {token}");
            request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");

在这里, uri类似于地址:http://yoursite/yoursubsite/_vti_history/512/Documents/Book1.xlsx

如何使用 c# 下载以前的版本文件?

【问题讨论】:

    标签: c# sharepoint office365 token httpwebrequest


    【解决方案1】:

    这是我的测试代码供你参考。

    var login = "user@xxx.onmicrosoft.com";
                var password = "Password";
    
                var securePassword = new SecureString();
    
                foreach (char c in password)
                {
                    securePassword.AppendChar(c);
                }
                SharePointOnlineCredentials onlineCredentials = new SharePointOnlineCredentials(login, securePassword);
    
                string webUrl = "https://xxx.sharepoint.com/sites/lee";
                string requestUrl = "https://xxx.sharepoint.com/sites/lee/_vti_history/512/MyDoc2/testdata.xlsx";
                Uri uri = new Uri(requestUrl);
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.Method = "GET";
                request.Credentials = onlineCredentials;
                request.Headers[HttpRequestHeader.Cookie] = onlineCredentials.GetAuthenticationCookie(new Uri(webUrl), true);  // SPO requires cookie authentication
                request.Headers["X-FORMS_BASED_AUTH_ACCEPTED"] = "f";  // disable interactive forms-based auth            
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                Stream stream = response.GetResponseStream();
    

    【讨论】:

    • 感谢您的解决方案,如何在没有用户名和密码的情况下获取流?
    • 有没有办法用访问令牌做到这一点?
    【解决方案2】:

    以下是使用客户端 ID 和客户端密码从 SharePoint 网站下载文件的完整示例。

    遇到的主要问题:

    1. 获取实际上会被接受的访问令牌。获取访问令牌非常容易,但尝试使用它下载文件会导致 401 并显示消息 invalid_client

    这些是无效的 authUrls。 x 的值是tenantId(例如guid)或tenantDomain(例如company.com)。将返回访问令牌,但返回的资源始终用于 Microsoft Graph (00000003-0000-0000-c000-000000000000)。

    String authUrl = "https://login.microsoftonline.com/" + x + "/oauth2/token";
    String authUrl = "https://login.microsoftonline.com/" + x + "/oauth2/v2.0/token";
    String authUrl = "https://login.windows.net/" + x + "/oauth2/token";
    String authUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
    

    实际起作用的 authUrl 是:

    String authUrl = "https://accounts.accesscontrol.windows.net/" + tenantId + "/tokens/OAuth/2";
    
    1. 第二个问题是使用直接 URL 下载文件, 例如https://tenant.sharepoint.com/Shared Documents/Data.xlsx

    显然直接网址不起作用。理论是内部服务器重定向从请求标头中去除访问令牌。因此,必须使用以下任一方式下载文件:

    a)https://tenant.sharepoint.com/_api/web/getfilebyserverrelativeurl('/Shared Documents/FileName.xlsx')/$value

    末尾的/$value 表示下载实际的二进制数据。如果省略,则会下载一个带有文件属性的 XML 文件(created datemodified datelength 等)。

    b)https://tenant.sharepoint.com/_layouts/15/download.aspx?SourceUrl=/Shared Documents/FileName.xlsx

    注意:如果使用选项 a),则“文件名”中的任何单引号都必须连续替换为两个单引号。可以在此处阅读有关转义问题的更多信息:https://sharepoint.stackexchange.com/questions/154590/getfilebyserverrelativeurl-fails-when-the-filename-contains-a-quote

    1. 忘记设置: System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;

    这会导致类似的消息,连接被强制关闭。

    1. 确保-DisableCustomAppAuthenticationfalse。这可以通过 PowerShell 进行设置。

      PS> install-module -name "PnP.PowerShell"

      PS> Connect-PnPOnline -Url "https://tenant.sharepoint.com"

      PS> Set-SPOTenant -DisableCustomAppAuthentication $false

    注意:我看到很多设置了headers的代码,比如:

    req.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
    req.Headers.Add("Accept", "application/json;odata=verbose");
    req.Headers.Add("cache-control", "no-cache");
    req.Headers.Add("Use-Agent", "Other");
    

    ClientId/ClientSecret 身份验证不需要这些标头。但是,使用旧版 SharePointOnlineCredentials 时需要 "X-FORMS_BASED_AUTH_ACCEPTED"

    有用的链接:

    下面的代码提供了几种不同的方法来获取访问令牌和下载文件。

    using System;
    using System.Collections;
    using System.IO;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using System.Web;
    
    namespace SharePointDemo {
    
    public class Program {
    
        const String SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000";
    
        public static void Main(String[] args) {
            System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // required.
    
            // Replace these 6 values:
            Uri url = new Uri("https://tenant.sharepoint.com"); // with or without an ending '/' both work
            String clientId = "... guid ...";
            String clientSecret = "... generated using the SharePoint AppRegNew.aspx ..."; // see useful links above
            String tenantId = "... guid ..."; // aka realm. Can be found in the SharePoint admin site settings, or by using GetTenantId(url);
            String fileRelativeUrl = "/Shared Documents/Data.xlsx";
            String filename = @"C:\temp\Data.xlsx"; // local file name
            
            String access_token_key = "access_token"; // variable name in headers to look for in authUrl's response.
            Uri fileUrl = new Uri(url.ToString() + "_layouts/15/download.aspx?SourceUrl=" + fileRelativeUrl);
            //Uri fileUrl = new Uri(url.ToString() + "_api/web/getfilebyserverrelativeurl('" + fileRelativeUrl.Replace("'", "''") + "')/$value"); // also works
    
            String clientIdPrincipal = clientId + "@" + tenantId;
            String resource = SharePointPrincipal + "/" + url.Host + "@" + tenantId;
    
            // Note: a 'scope' parameter is not required for this authUrl, but 'resource' is required.
            String authUrl = "https://accounts.accesscontrol.windows.net/" + tenantId + "/tokens/OAuth/2";
            String content = "grant_type=client_credentials&client_id=<username>&client_secret=<password>&resource=<resource>";
            content = content.Replace("<username>", clientIdPrincipal);
            content = content.Replace("<password>", clientSecret);
            content = content.Replace("<resource>", resource);
    
            AuthResult result = GetAuthResult(authUrl, content, access_token_key);
            String accessToken = result.access_token;
            DownloadFile1(fileUrl, accessToken, filename); // pick whichever DownloadFile method floats your boat
        }
    
        private static AuthResult GetAuthResult(String authUrl, String content, String access_token_key = "access_token", int timeoutSeconds = 10) {
            HttpContent data = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
            using (data) {
                using (HttpClient c = new HttpClient()) {
                    c.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
                    using (HttpResponseMessage res = c.PostAsync(authUrl, data).Result) {
                        HttpStatusCode code = res.StatusCode;
                        String message = res.Content.ReadAsStringAsync().Result;
                        if (code == HttpStatusCode.OK)
                            return ParseMessage(message, access_token_key);
    
                        throw new Exception("Auth failed. Status code: " + code + " Message: " + message);
                    }
                }
            }
        }
    
        // alternative way using HttpWebRequest
        private static AuthResult GetAuthResult2(String authUrl, String content, String access_token_key = "access_token", int timeoutSeconds = 10) {
            HttpWebRequest req = WebRequest.CreateHttp(authUrl);
            req.AuthenticationLevel = System.Net.Security.AuthenticationLevel.None;
            req.ContentLength = content.Length;
            req.ContentType = "application/x-www-form-urlencoded";
            req.Method = "POST";
            req.Timeout = timeoutSeconds * 1000;
            using (StreamWriter sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) {
                sw.Write(content);
                sw.Close();
            }
    
            using (WebResponse res = req.GetResponse()) {
                using (StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.ASCII)) {
                    String message = sr.ReadToEnd();
                    return ParseMessage(message, access_token_key);
                }
            }
        }
    
        // this could also be done using a Json library
        private static AuthResult ParseMessage(String message, String access_token_key) {
            Hashtable ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
            char[] trimChars = new [] { '{', '"', '}' };
            String[] arr = message.Split(',');
            for (int i = 0; i < arr.Length; i++) {
                String t = arr[i];
                int x = t.IndexOf(':');
                if (x < 0)
                    continue;
                String varName = t.Substring(0, x).Trim(trimChars);
                String value = t.Substring(x + 1).Trim(trimChars);
                ht[varName] = value;
            }
    
            String accessToken = (String) ht[access_token_key];
            if (accessToken == null)
                throw new Exception(String.Format("Could not find '{0}' in response message: ", access_token_key) + message);
    
            AuthResult result = new AuthResult();
            result.access_token = accessToken;
            result.resource = (String) ht["resource"];
            result.token_type = (String) ht["token_type"];
            int val = 0;
            if (int.TryParse((String) ht["expires_in"], out val)) result.expires_in = val;
            if (int.TryParse((String) ht["expires_on"], out val))
                result.expires_on = val;
            else
                result.expires_on = (int) (DateTime.UtcNow.AddSeconds(result.expires_in) - AuthResult.EPOCH).TotalSeconds;
    
            if (int.TryParse((String) ht["not_before"], out val)) result.not_before = val;
            return result;
        }
    
        private class AuthResult {
            public static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            public DateTime NotBefore { get { return EPOCH.AddSeconds(not_before).ToLocalTime(); } }
            public DateTime ExpiresOn { get { return EPOCH.AddSeconds(expires_on).ToLocalTime(); } }
    
            public int not_before { get; set; }
            public int expires_on { get; set; }
            public String resource { get; set; }
    
            ///<summary>Indicates the token type value. The only type that the Microsoft identity platform supports is bearer.</summary>
            public String token_type { get; set; }
            ///<summary>The amount of time that an access token is valid (in seconds).</summary>
            public int expires_in { get; set; }
            ///<summary>The access token generated by the authentication server.</summary>
            public String access_token { get; set; }
        }
    
        public static void DownloadFile1(Uri fileUrl, String accessToken, String filename, int timeoutSeconds = 60) {
            using (var c = new HttpClient()) { // requires reference to System.Net.Http
                c.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
                var req = new HttpRequestMessage();
                req.Headers.Add("Authorization", "Bearer " + accessToken);
                req.Method = HttpMethod.Get;
                req.RequestUri = fileUrl;
                using (HttpResponseMessage res = c.SendAsync(req).Result) {
                    if (res.StatusCode == HttpStatusCode.OK) {
                        using (Stream s = res.Content.ReadAsStreamAsync().Result) {
                            using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
                                s.CopyTo(fs);
                                fs.Flush();
                            }
                        }
                    }
                    else {
                        String message = "Error. Server returned status code: " + res.StatusCode + " (" + (int) res.StatusCode + "). Headers: " + res.Headers.ToString();
                        throw new Exception(message);
                    }
                }
            }
        }
    
        // slight variation to DownloadFile1, but basically the same
        public static void DownloadFile2(Uri fileUrl, String accessToken, String filename, int timeoutSeconds = 60) {
            using (var c = new HttpClient()) {
                c.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
                c.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
                using (HttpResponseMessage res = c.GetAsync(fileUrl).Result) {
                    if (res.StatusCode == HttpStatusCode.OK) {
                        using (Stream s = res.Content.ReadAsStreamAsync().Result) {
                            using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
                                s.CopyTo(fs);
                                fs.Flush();
                            }
                        }
                    }
                    else {
                        String message = "Error. Server returned status code: " + res.StatusCode + " (" + (int) res.StatusCode + "). Headers: " + res.Headers.ToString();
                        throw new Exception(message);
                    }
                }
            }
        }
    
        public static void DownloadFile3(Uri fileUrl, String accessToken, String filename, int timeoutSeconds = 60) {
            HttpWebRequest req = WebRequest.CreateHttp(fileUrl);
            //req.ContinueTimeout = ...;
            //req.ReadWriteTimeout = ...;
            req.Timeout = timeoutSeconds * 1000;
            req.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + accessToken);
            req.Method = "GET";
            try {
                using (var res = (HttpWebResponse) req.GetResponse()) {
                    using (Stream s = res.GetResponseStream()) {
                        using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
                            s.CopyTo(fs);
                            fs.Flush();
                        }
                    }
                }
            } catch (Exception ex) {
                if (ex is WebException) {
                    var we = (WebException) ex;
                    String headers = we.Response.Headers.ToString();
                    throw new WebException(we.Message + " headers: " + headers, we);
                }
                throw;
            }
        }
    
        public static void DownloadFile4(Uri fileUrl, String accessToken, String filename) {
            using (WebClient c = new WebClient()) {
                c.Headers.Add("Authorization", "Bearer " + accessToken);
                using (Stream s = c.OpenRead(fileUrl)) {
                    using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
                        s.CopyTo(fs);
                        fs.Flush();
                    }
                }
            }
        }
    
        // Helper methods, not used:
    
        ///<summary>
        ///Makes an http request to the site url in order to read the GUID tenant-id (also called the realm) from the response headers.
        ///</summary>
        public static Guid? GetTenantId(Uri siteUrl, int timeoutSeconds = 10) {
            // the code: url = url.TrimEnd('/') + "/_vti_bin/client.svc"; is not needed
            String url = siteUrl.GetLeftPart(UriPartial.Authority);
    
            // use HttpClient to avoid Exception when using HttpWebRequest
            using (var c = new HttpClient()) { // requires reference to System.Net.Http
                c.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
                var req = new HttpRequestMessage();
    
                // "Bearer " without an access token results in StatusCode = Unauthorized (401) and the response headers contain the tenant-id
                req.Headers.Add("Authorization", "Bearer ");
                req.Method = HttpMethod.Get;
                req.RequestUri = new Uri(url);
                using (HttpResponseMessage res = c.SendAsync(req).Result) {
                    // HttpStatusCode code = res.StatusCode; // typically Unauthorized
                    System.Net.Http.Headers.HttpResponseHeaders h = res.Headers;
                    foreach (String s in h.GetValues("WWW-Authenticate")) { // should only have one
                        Guid? g = TryGetGuid(s);
                        if (g.HasValue)
                            return g;
                    }
                }
            }
            return null;
        }
    
        public static Guid? GetTenantId_old(Uri siteUrl, int timeoutSeconds = 10) {
            String url = siteUrl.GetLeftPart(UriPartial.Authority);
    
            HttpWebRequest req = WebRequest.CreateHttp(url);
            req.Timeout = timeoutSeconds * 1000;
            req.Headers["Authorization"] = "Bearer ";
    
            String header = null;
            try {
                using (req.GetResponse()) {}
            } catch (WebException e) {
                if (e.Response != null)
                    header = e.Response.Headers["WWW-Authenticate"];
            }
            return TryGetGuid(header);
        }
    
        private static Guid? TryGetGuid(String header) {
            if (String.IsNullOrEmpty(header))
                return null;
    
            const String bearer = "Bearer realm=\"";
            int bearerIndex = header.IndexOf(bearer, StringComparison.OrdinalIgnoreCase);
            if (bearerIndex < 0)
                return null;
    
            int x1 = bearerIndex + bearer.Length;
            int x2 = header.IndexOf('"', x1 + 1);
            String realm = (x2 < 0 ? header.Substring(x1) : header.Substring(x1, x2 - x1));
    
            Guid guid;
            if (Guid.TryParse(realm, out guid))
                return guid;
    
            return null;
        }
    }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      • 2019-01-08
      • 2018-09-08
      • 2013-10-08
      相关资源
      最近更新 更多