SharePoint Online のドキュメントライブラリにある Excel(.xlsx)を、C#(.NET Framework 4.8)から「URL指定で読み込みたい」――しかし Web のURLをそのまま渡しても失敗することが多いです。認証を含めた取得方法と、DataTable化までの実装パターンを、現場でつまずきやすいポイント込みで整理します。
SharePoint Online の「Web のURL」をそのまま Excel として読めない理由
SharePoint Online でファイルを開いたときに表示される “Web のURL” は、たいてい次のような性質を持っています。
- ブラウザでの閲覧・編集(Office for the web)を前提にした URL である
- サインイン(Cookie / セッション / 条件付きアクセス / MFA など)を前提としている
- アクセスすると 302 リダイレクトや認証ページへ遷移することがある
- Excel ファイル本体(.xlsx バイナリ)が素直に返ってくるとは限らない
そのため、C# 側で WebClient や HttpClient に URL を渡しても、返ってくるのが HTML(ログイン画面)だったり、権限エラー(401/403)だったりして、Excel ライブラリが「壊れたファイル」として扱ってしまいます。
| URL の種類 | 例 | プログラムでの扱い | つまずきポイント |
|---|---|---|---|
| Web 表示用 URL | https://tenant.sharepoint.com/sites/xxx/Shared%20Documents/MyDataFile.xlsx | そのままでは Excel として読めないことが多い | 認証・リダイレクト・Office Web 画面に流れる |
| 共有リンク(ダウンロード可能な場合) | https://tenant.sharepoint.com/:x:/r/sites/... | 設定次第で読めるが組織では無効なことが多い | 匿名不可 / 有効期限 / ポリシー制約 |
| Graph の content エンドポイント | .../drive/root:/...:/content | Excel バイナリが取得しやすい(推奨) | Entra ID アプリ登録と権限付与が必須 |
解決の基本方針:取得(認証付き)と解析(Excel読み取り)を分離する
実装を成功させるコツは、処理をきれいに 2 段階に分けることです。
(段階1)SharePoint から認証付きで .xlsx を取得(ダウンロード/ストリーム)
(段階2)取得した .xlsx を Excel 解析ライブラリで読み、DataTable / List に変換
この分離をすると、次のメリットが出ます。
- 認証・権限エラー(401/403/404)と、Excel パースエラー(シート名違い・ヘッダー不整合)を切り分けやすい
- ダウンロードしたファイルを一時保存して検証できる(Excel で手開きして確認可能)
- 後で「ファイルは OneDrive だった」「保存先が別サイトになった」など構造変更があっても、取得部分だけ差し替えられる
3つの選択肢を比較:Graph / CSOM / 直URL
| 方式 | 認証 | MFA/条件付きアクセス | 将来性 | 実装難易度 | 向いているケース |
|---|---|---|---|---|---|
| Microsoft Graph API(推奨) | モダン(Entra ID / OAuth2) | 対応しやすい | 高い | 中 | 企業標準・長期運用・サービス連携 |
| SharePoint CSOM / REST | 方式により差が大きい | 環境によって厳しい | 中 | 中 | 既存資産が CSOM ベース、限定環境 |
| 共有リンク等での直取得 | 不要(匿名)または簡易 | ポリシー次第 | 低〜中 | 低 | 匿名ダウンロードが許可されている特殊ケース |
多くの企業テナントでは「匿名共有」や「認証なしダウンロード」が抑止されているため、現実的には Microsoft Graph API を第一候補にするのが安定です。
推奨:Microsoft Graph API で SharePoint Online の Excel(.xlsx)を取得する
全体像:コンソール(.NET Framework 4.8)での典型構成
- Entra ID(旧 Azure AD)でアプリ登録
- MSAL(Microsoft.Identity.Client)でアクセストークン取得
- Graph API でファイルをダウンロード(Stream)
- ClosedXML / EPPlus / NPOI / Open XML SDK / ExcelDataReader などで読み取り
.NET Framework 4.8 のコンソールアプリで「ユーザーとしてサインインして読む(Delegated)」のが一番導入しやすいことが多いです。一方、夜間バッチなど “無人実行” が必要なら「アプリのみ(Application / app-only)」を検討します。
どの認証方式にするべきか(Delegated と app-only)
| 方式 | 概要 | メリット | 注意点 |
|---|---|---|---|
| Delegated(ユーザー認証) | 実行ユーザーがサインインして、その権限で読む | 現場導入が早い/権限が利用者に沿う | 無人実行に不向き/ユーザー操作が必要になりがち |
| Application(app-only) | アプリが単独でアクセス(証明書/シークレット等) | バッチ向き/運用が安定 | 管理者同意が必要/権限が強くなりやすいので最小権限設計が重要 |
アプリ登録(Entra ID)で準備するもの
最低限、次の情報が必要になります。
- Tenant ID(ディレクトリ ID)
- Client ID(アプリケーション ID)
- (Delegated の場合)リダイレクト URI や Public client 設定
- (app-only の場合)クライアントシークレット or 証明書
- Microsoft Graph のアクセス許可(例:
Sites.Read.All,Files.Read.Allなど)
権限は「動けばよい」で強いものを付けると後で監査・棚卸しで詰みがちです。可能なら対象サイトに限定する(例:Sites.Selected を使う)など、最小権限の方向で設計すると運用が楽になります。
.NET Framework 4.8 側の NuGet(代表例)
Microsoft.Identity.Client(MSAL)ClosedXML(Excel を扱いやすい形で読み取る)- (代替)
ExcelDataReader/ExcelDataReader.DataSet(DataSet/DataTable に寄せたい場合)
通信が失敗する環境では TLS の影響が出ることがあります。古い設定が残る現場では、起動時に TLS1.2 を明示しておくと切り分けに役立ちます(ただし OS/環境依存なので必要な場合のみ)。
// 必要なときだけ(環境により)
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
Graph API で「SharePoint のファイル本体(.xlsx)」をダウンロードする
Graph のパス指定は「ドライブ(Drive)とアイテム(DriveItem)」が基本
SharePoint のドキュメントライブラリ上のファイルは、Graph 的には “Drive(ドキュメントライブラリ)” の中の “DriveItem(ファイル)” です。よく使うのは次の 2 パターンです。
- サイトの既定ドライブを使って、パスで指定して content を取る
- drive-id / item-id を先に取得してから content を取る(確実だが手順が増える)
よく使う content エンドポイント例(パス指定)
次の形が読みやすく、検証もしやすいです(実際は URL エンコードが必要になります)。
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/{ライブラリ以降のパス}:/content
たとえばドキュメントライブラリが “Shared Documents” で、そこに MyDataFile.xlsx があるなら概念的にはこうなります。
/drive/root:/Shared Documents/MyDataFile.xlsx:/content
日本語サイトではライブラリ表示名が「ドキュメント」でも、内部的なパスが異なる場合があります。確実にするには、Graph Explorer 等で対象ファイルの DriveItem を確認してからパスを固めるのが安全です。
最小構成コード例:MSAL でトークン取得 → Graph でダウンロード → Sheet1 を DataTable 化(ClosedXML)
ここでは「コンソールアプリでユーザーとしてサインインし、ユーザー権限でファイルを読む(Delegated)」の例を示します。MFA がある環境でも通しやすいのが利点です。コンソールでは Device Code フローが扱いやすいことが多いです。
設定値(例)
tenantId:あなたのテナント IDclientId:アプリ(クライアント)IDsiteId:対象サイトの site-id(後述の方法で取得)filePath:ドライブの root からのパス(例:Shared Documents/MyDataFile.xlsx)
サンプル(Device Code でログインして取得)
using System;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using ClosedXML.Excel;
using Microsoft.Identity.Client;
namespace SpoExcelReader
{
internal class Program
{
static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
// ====== 設定 ======
var tenantId = "YOUR_TENANT_ID";
var clientId = "YOUR_CLIENT_ID";
var siteId = "YOUR_SITE_ID"; // 例: tenant.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
// SharePoint のドキュメントライブラリ配下のパス(URLエンコードは HttpClient 側で任せず、自分で安全に)
var filePath = "Shared Documents/MyDataFile.xlsx";
// Delegated 権限(例)
// 実運用では最小権限に寄せる:読み取りなら Files.Read / Sites.Read.All など要件に合わせる
string[] scopes = new[] { "Files.Read.All", "Sites.Read.All" };
// ====== MSAL(Device Code)でトークン取得 ======
var app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
.WithDefaultRedirectUri()
.Build();
var authResult = await app
.AcquireTokenWithDeviceCode(scopes, code =>
{
Console.WriteLine(code.Message);
return Task.CompletedTask;
})
.ExecuteAsync();
// ====== Graph でファイルをダウンロード(Stream) ======
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
// パスはエンコードして安全に
var encodedPath = string.Join("/", filePath.Split('/')
.Select(seg => Uri.EscapeDataString(seg)));
var url = $"https://graph.microsoft.com/v1.0/sites/{siteId}/drive/root:/{encodedPath}:/content";
using var resp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
resp.EnsureSuccessStatusCode();
using var xlsxStream = await resp.Content.ReadAsStreamAsync();
// ====== ClosedXML で Sheet1 を DataTable 化 ======
var dt = ReadWorksheetToDataTable(xlsxStream, "Sheet1", firstRowAsHeader: true);
Console.WriteLine($"Rows: {dt.Rows.Count}, Cols: {dt.Columns.Count}");
Console.WriteLine("Done.");
}
static DataTable ReadWorksheetToDataTable(Stream xlsxStream, string sheetName, bool firstRowAsHeader)
{
// 重要:ClosedXML はシーク不可の Stream を嫌う場合があるため、必要なら MemoryStream にコピーする
using var ms = new MemoryStream();
xlsxStream.CopyTo(ms);
ms.Position = 0;
using var wb = new XLWorkbook(ms);
var ws = wb.Worksheets.FirstOrDefault(x => string.Equals(x.Name, sheetName, StringComparison.OrdinalIgnoreCase));
if (ws == null)
throw new InvalidOperationException($"Worksheet not found: {sheetName}");
var used = ws.RangeUsed();
var dt = new DataTable(sheetName);
if (used == null)
return dt; // 空シート
var firstRow = used.FirstRowUsed();
var firstDataRowNumber = firstRow.RowNumber();
// 列定義
var headerRow = firstRow;
var colCount = used.ColumnCount();
for (int c = 1; c <= colCount; c++)
{
string colName;
if (firstRowAsHeader)
{
colName = headerRow.Cell(c).GetValue<string>();
if (string.IsNullOrWhiteSpace(colName))
colName = $"Column{c}";
}
else
{
colName = $"Column{c}";
}
// 同名列対策
var original = colName;
int suffix = 1;
while (dt.Columns.Contains(colName))
{
suffix++;
colName = $"{original}_{suffix}";
}
dt.Columns.Add(colName, typeof(string)); // まずは string で取り込むのが安全
}
// データ行
int startRow = firstRowAsHeader ? firstDataRowNumber + 1 : firstDataRowNumber;
for (int r = startRow; r <= used.LastRowUsed().RowNumber(); r++)
{
var row = ws.Row(r);
// 行が全空ならスキップ(必要に応じて調整)
bool allEmpty = true;
var values = new object[colCount];
for (int c = 1; c <= colCount; c++)
{
var cell = row.Cell(used.FirstColumn().ColumnNumber() + (c - 1));
string v = cell.GetFormattedString(); // 表示形式で取得(数値/日付の見え方を揃える)
values[c - 1] = v;
if (!string.IsNullOrWhiteSpace(v))
allEmpty = false;
}
if (allEmpty) continue;
dt.Rows.Add(values);
}
return dt;
}
}
}
ポイントは次の通りです。
- まずは string で DataTable に入れる:Excel の型推定は地雷になりがちなので、後段で必要な列だけ変換する方が安定します。
GetFormattedString()を使う:日付や小数点など、Excel の表示に近い形で取れます(要件によりGetValue<T>に切り替え)。- 空行スキップ:Excel にありがちな “見た目は空だが範囲に含まれる” 行で行数が膨れないようにします。
site-id をどうやって手に入れるか(よくあるつまずき)
Graph の URL を組むには site-id が必要になることが多いです。取得方法はいくつかありますが、実務で使いやすい考え方をまとめます。
方法:hostname と site のパスから site を引く
サイトURLが次のような場合:
https://tenant.sharepoint.com/sites/YourSite
概念的には Graph で次の問い合わせを行い、返ってきた id を使います。
GET https://graph.microsoft.com/v1.0/sites/tenant.sharepoint.com:/sites/YourSite
この応答の id が、サンプルコードで使っている siteId です。開発時は Graph Explorer で試すと早いです。
方法:Drive から辿って item-id を確定させる(パスの揺れが怖い場合)
ライブラリ名が日本語だったり、スペース・記号が混ざるパスでトラブルが多い場合は、先に一覧 API で対象フォルダを辿り、最終的に driveItem-id を取ってから /items/{id}/content で落とすと確実です(手順は増えますが安定します)。
Excel 解析ライブラリの選び方(ClosedXML / ExcelDataReader など)
「DataTable にしたい」のか「ドメインモデル(List<T>)にしたい」のかで、向き不向きがあります。
| ライブラリ | 得意 | 注意点 | 向くケース |
|---|---|---|---|
| ClosedXML | Excel を直感的に扱える/シート指定が簡単 | 大容量だとメモリを使いやすい | 中規模までの帳票、扱いやすさ優先 |
| ExcelDataReader | 読み取りが軽い/DataSet 化が簡単 | Encoding.RegisterProvider が必要なことがある | 読み取り特化、DataTable に寄せたい |
| EPPlus | 多機能(生成も強い) | ライセンスや利用条件の確認が必要な場合がある | 社内規約・ライセンス確認が取れる環境 |
| Open XML SDK | 低レベルで確実/依存が少ない | 実装が重くなりやすい | ライブラリ制約が厳しい、最小依存 |
ExcelDataReader で “そのまま DataTable” に寄せる例
「とにかく DataTable を早く作りたい」場合はこの系が便利です。
using System.Data;
using System.IO;
using ExcelDataReader;
// ExcelDataReader は .NET Framework だとエンコーディング登録が必要なことがある
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
using var reader = ExcelReaderFactory.CreateReader(xlsxStream);
var ds = reader.AsDataSet(new ExcelDataReader.ExcelDataSetConfiguration
{
ConfigureDataTable = _ => new ExcelDataReader.ExcelDataTableConfiguration
{
UseHeaderRow = true
}
});
DataTable sheet1 = ds.Tables["Sheet1"]; // シート名で取得(見つからない場合は index)
SharePoint CSOM / REST で取得する場合(環境により有効)
Graph を採用できない事情(既存の CSOM 資産がある、ネットワーク制約がある等)がある場合、SharePoint CSOM(Client-Side Object Model)でファイルを取得し、その後に Excel 解析へ回す手もあります。
ただし、SharePoint Online では認証周り(MFA 必須、条件付きアクセス、基本認証の廃止)で詰まりやすいのが現実です。特に「ユーザー名+パスワードをコードに埋め込んで接続」のようなやり方は、セキュリティ上も運用上も避けるべきです。
CSOM を使うなら、以下の観点で “環境の認証方式と噛み合うか” を先に確認するのが安全です。
- MFA が必須か(必須なら従来型の資格情報方式はほぼ詰む)
- アプリ専用の認証(証明書ベース等)を許容できるか
- 社内標準が Graph なのか、SharePoint REST/CSOM が許可されているのか
共有リンクで「匿名ダウンロード可能」な場合だけ成立する直取得
もし組織の設定として、共有リンク(“誰でもリンク” など)が許可され、かつ “ダウンロードが認証なしで可能” であれば、URL を直にダウンロードして Excel 解析に渡せるケースがあります。
ただし企業テナントでは次の理由で成立しないことが多いです。
- 匿名共有が無効
- リンクに有効期限がある/失効しやすい
- 監査上、匿名リンク運用が禁止
「一時的に動いたから採用する」と、後からリンク失効で障害になりがちです。長期運用の前提なら Graph(または統制された認証方式)をおすすめします。
DataTable / List 化で失敗しやすいポイントと対策
ヘッダー行の取り扱い
- 1行目が必ずヘッダーとは限らない(タイトル行や注意書き行が混ざる帳票がある)
- 空の列名があると DataTable が扱いにくい(
Column1補完などが必要) - 同名列があると例外になりやすい(サンプルのように
_2などで回避)
型変換は “後段で必要な列だけ” が安全
Excel のセルは見た目と内部値が一致しないことがあります(たとえば日付に見えて実は文字列、数値に見えて実は文字列など)。取り込み時に強制変換すると、1セルの異常で全体が落ちることがあります。
| 要件 | おすすめ戦略 | 理由 |
|---|---|---|
| まず一覧を取得して後処理で整形 | 取り込みは string 統一 → 後段で列ごとに parse | 例外耐性が高い/障害調査がしやすい |
| 数値・日付が厳密に必要 | 列ごとに TryParse、失敗はログ+既定値 | “1件の不正値で全停止” を避ける |
| Excel の表示値が重要 | 表示形式(Formatted)で取得 | 小数点桁や日付表現が揃う |
シート名が固定でない場合の現実的な対処
帳票のテンプレが変わると、Sheet1 が sheet1 になったり、入力 など別名になったりします。安定運用したい場合は次のどれかを採用します。
- シート名を設定ファイル化して変更に追従できるようにする
- 先頭シート(index 0)を読む(テンプレが固定なら有効)
- ヘッダー行の特徴(列名セット)でシートを判定する
Graph でのエラー原因の切り分け(401/403/404 が出たら)
401 Unauthorized
- トークンが取れていない / 期限切れ
- スコープ(権限)が不足している
- テナントや Authority の指定が間違っている
403 Forbidden
- ユーザー(Delegated)の権限で対象ファイルにアクセスできない
- 管理者同意が必要な権限を付与していない
- app-only の場合、サイトへの許可付与設計が不足(最小権限の設定ミス含む)
404 Not Found
- site-id / パスが違う(ライブラリ名や日本語パス、スペースのエンコード漏れ)
- ファイル移動・リネームが起きている
- 別サイト(別コレクション)を見ている
この段階では「URL が間違っているのか」「権限がないのか」の切り分けが重要です。Graph Explorer などで同じユーザーで叩いて再現する、または一度 item-id を取得してから content を取ると、原因を特定しやすくなります。
運用で差がつく実装の工夫(オリジナルの実務ポイント)
ETag を使って “変更があった時だけ” ダウンロードする
SharePoint 上の Excel を定期的に取り込む場合、毎回フルダウンロードすると無駄が増えます。Graph は DriveItem のメタデータに ETag が付くので、前回の ETag を保持し、変化がある時だけダウンロードする設計にすると負荷と時間を減らせます。
ファイル取得と Excel 解析を別ログにする
「取得は成功したが解析で失敗」「解析は成功したがデータが空」など、障害の種類が分かれます。ログを次のように分けると、現場での復旧が速くなります。
- 取得ログ:サイト/パス、HTTP ステータス、レスポンスサイズ、ETag、所要時間
- 解析ログ:シート名、行数/列数、ヘッダー内容、必須列の有無、型変換エラー件数
Excel 側の “運用ルール” を最初に決めると安定する
技術的に読めるようにしても、Excel 側の運用が揺れると取り込みは壊れます。取り込み対象の Excel は、次のような最低限のルールを合意しておくと強いです。
- データ範囲のヘッダーは固定(列名を勝手に変えない)
- 空行・空列の混入ルール(許容するならスキップ条件を定義)
- 日付・数値の入力規則(文字列混入時の扱い)
- シート名の固定(または設定で追従できるようにする)
まとめ:SharePoint Online の Excel を C# で URL 指定で読むなら「Graph で取得 → Excel ライブラリで解析」
- SharePoint の “Web のURL” は、そのまま Excel バイナリの URL ではないことが多い
- まずは認証付きでファイル本体(.xlsx)を取得する仕組みを確立する
- Microsoft Graph API はモダン認証で将来性が高く、SharePoint Online では第一候補になりやすい
- 取得した Stream を ClosedXML / ExcelDataReader などで読み、DataTable / List に変換する
- 運用では権限設計(最小権限)、パス揺れ対策、ログとエラー切り分けが重要
「ユーザーとして実行するのか(Delegated)」「完全無人で動かすのか(app-only)」「MFA や条件付きアクセスがあるか」で最適解は変わりますが、基本のアーキテクチャを押さえれば、SharePoint Online の Excel 取り込みは安定して作れます。

コメント