SharePoint OnlineのリストImage列にCSOM(C#)でjpgをアップロードする方法|JSONと添付ファイル対応

SharePoint Online のモダン リストで使える「画像 (Image) 列」は、CSOM(C#)から直接バイナリを流し込む列ではなく、画像ファイルを指す JSON を保存します。本記事では、jpg をアップロードして Image 列に表示させる2つの実装パターン(ライブラリ保存/添付ファイル保存)を、実務で使える形に落とし込んで解説します。

目次

SharePoint Online の「画像 (Image) 列」が扱いにくく見える理由

SharePoint Online のモダン UI で追加できる「画像 (Image) 列」は、従来の “画像を置いて URL を貼る” という感覚と少し異なります。CSOM でつまずきやすいポイントは、次の2点です。

  • Image 列そのものに画像データを格納するわけではない(保存されるのは「画像を指す JSON 文字列」)
  • UI 側の保存場所・JSON の形が変わることがある(特にモダン UI は内部仕様が変わりやすい)

以前は「サイト アセット」など特定のライブラリ(リスト GUID 配下)に画像が置かれ、Image 列の値も比較的読みやすい JSON であるケースが多く、開発者側で JSON を組み立てて列にセットしやすい状況がありました。しかし最近の挙動では、画像がリスト アイテムの添付ファイル フォルダーに保存されるケースが増え、UI が生成する値に「数字が混ざったファイル名」などが含まれ、意図を読み取りにくくなっています。

観点以前よく見られた挙動最近よく見られる挙動
画像ファイルの保存場所サイト アセット等のライブラリ(特定フォルダー)アイテムの添付ファイル フォルダー(Attachments 配下)
Image 列の値比較的読みやすい JSON(推測しやすい)追加プロパティや独特なファイル名が混ざり、意味が読みづらい
実装側の安定性JSON を自前生成しやすいUI 生成値の完全再現は難しいことがある

結論として、CSOM で安定して実装するには「UI が生成する “全部入り” の値を無理に再現しようとしない」ことが重要です。Image 列は画像ファイルの場所を示す JSONを受け取る列なので、自分が管理できるパス(URL)を用意して、その参照を JSON でセットするのが最も堅実です。

Image 列の正体:保存されるのは JSON 文字列

モダンな Image 列は、内部的に「画像ファイルの URL 情報などを持つ JSON オブジェクト」を文字列として保存します。CSOM では、この JSON を文字列として列にセットします。

最低限押さえるべきプロパティ

環境や時期によって追加プロパティが含まれることはありますが、実装の芯として押さえるのは次の3つです。

プロパティ意味注意点
fileName画像ファイル名img.jpg表示や UI の参照に使われることがあります。拡張子も含めます。
serverUrlテナントのルート(スキーム+ホスト)https://tenant.sharepoint.com/sites/... は含めず、ルートの形に揃えると安定しやすいです。
serverRelativeUrlサーバー相対 URL/sites/MySite/Shared%20Documents/images/img.jpgスペース等は URL エンコードされている必要があります。

JSON の例(最小構成)

{
  "fileName": "img.jpg",
  "serverUrl": "https://tenant.sharepoint.com",
  "serverRelativeUrl": "/sites/MySite/Shared%20Documents/images/img.jpg"
}

ここで重要なのは、Image 列に「画像本体」を流すのではなく、画像の置き場所を指す JSON を流すという点です。つまり実装の選択肢は大きく2つあります。

  • 方法A:画像をドキュメント ライブラリ(共有ドキュメント等)にアップロードし、その URL を JSON にして Image 列へ
  • 方法B:UI と同じように画像をアイテムの添付ファイルとしてアップロードし、その URL を JSON にして Image 列へ

方法A:画像をドキュメント ライブラリにアップロードして Image 列に設定する

まずは最も安定しやすいパターンです。画像の保管場所をライブラリ側に寄せることで、運用・権限・再利用・編集の面でメリットが出やすくなります。

この方法が向いているケース

  • 同じ画像を複数アイテムで使い回したい
  • 画像の版管理(バージョン管理)や差し替え運用をしっかりやりたい
  • 添付ファイルを無効にしているリストでも使いたい
  • UI の内部挙動に引っ張られず、実装を安定させたい

全体の流れ

  1. 画像を置くライブラリとフォルダーを決める(例:共有ドキュメントの /images
  2. CSOM で jpg をライブラリへアップロードする
  3. アップロードしたファイルの ServerRelativeUrl を取得する
  4. fileName / serverUrl / serverRelativeUrl を持つオブジェクトを JSON シリアライズ
  5. リスト アイテムの Image 列(内部名)に JSON 文字列をセットして更新

C#(CSOM)サンプル:ライブラリへアップロード → Image 列へ反映

以下は「すでに ClientContext が認証済みで取得できている」前提のサンプルです。認証の方式(証明書認証/対話ログイン等)はテナントの要件に合わせてください。

using System;
using System.IO;
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;

public class ImageValue
{
    public string fileName { get; set; }
    public string serverUrl { get; set; }
    public string serverRelativeUrl { get; set; }
}

public class SharePointImageColumnSample
{
    public static void UploadJpgToLibraryAndSetImageColumn(
        ClientContext ctx,
        string listTitle,
        int itemId,
        string imageColumnInternalName,
        string libraryFolderServerRelativeUrl,
        string localJpgPath)
    {
        if (ctx == null) throw new ArgumentNullException(nameof(ctx));
        if (string.IsNullOrWhiteSpace(listTitle)) throw new ArgumentException("listTitle is required.");
        if (string.IsNullOrWhiteSpace(imageColumnInternalName)) throw new ArgumentException("imageColumnInternalName is required.");
        if (string.IsNullOrWhiteSpace(libraryFolderServerRelativeUrl)) throw new ArgumentException("libraryFolderServerRelativeUrl is required.");
        if (!File.Exists(localJpgPath)) throw new FileNotFoundException("localJpgPath not found.", localJpgPath);

        // 1) 画像をライブラリ(フォルダー)へアップロード
        var fileName = Path.GetFileName(localJpgPath);
        var folder = ctx.Web.GetFolderByServerRelativeUrl(libraryFolderServerRelativeUrl);

        ctx.Load(folder, f => f.ServerRelativeUrl);
        ctx.ExecuteQuery();

        Microsoft.SharePoint.Client.File uploadedFile;
        using (var fs = new FileStream(localJpgPath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            var fileInfo = new FileCreationInformation
            {
                Url = fileName,           // フォルダー配下のファイル名
                Overwrite = true,
                ContentStream = fs
            };

            uploadedFile = folder.Files.Add(fileInfo);
            ctx.Load(uploadedFile, f => f.Name, f => f.ServerRelativeUrl);
            ctx.ExecuteQuery();
        }

        // 2) Image 列に入れる JSON を作る
        // serverUrl は「https://tenant.sharepoint.com」のようにルートに揃えるのが無難
        var origin = new Uri(ctx.Url).GetLeftPart(UriPartial.Authority);

        var img = new ImageValue
        {
            fileName = uploadedFile.Name,
            serverUrl = origin,
            serverRelativeUrl = uploadedFile.ServerRelativeUrl
        };

        string json = JsonConvert.SerializeObject(img);

        // 3) リスト アイテムへセット
        var list = ctx.Web.Lists.GetByTitle(listTitle);
        var item = list.GetItemById(itemId);

        item[imageColumnInternalName] = json;
        item.Update();
        ctx.ExecuteQuery();
    }
}

実装のポイント(ライブラリ保存)

  • 内部名を使う:列名(表示名)ではなく、Image 列の内部名item[internalName] を指定すると事故が減ります。
  • URL エンコードに注意serverRelativeUrl はスペースが %20 になっている必要があります(CSOM が返す ServerRelativeUrl は通常この点が安全です)。
  • 権限がないと壊れた画像になる:閲覧者が画像ファイルにアクセスできないと、Image 列は「画像が表示できない」状態になります。画像保管場所の権限設計を先に決めておくとスムーズです。
  • ファイル名の衝突を避けたい場合img_<guid>.jpg のようにユニーク名にすると運用が安定します(後述の添付ファイル方式でも有効です)。

方法B:UI と同じく「アイテムの添付ファイル」にアップロードして Image 列に設定する

「モダン UI から画像を入れたときと同じ場所(添付ファイル フォルダー)に保存したい」場合は、この方法が近い挙動になります。CSOM でも通常の添付ファイルとして jpg を追加できるため、最終的にその URL を JSON に入れて Image 列にセットします。

この方法が向いているケース

  • アイテム単位で画像を完結させたい(データの持ち運び・移行を意識したい)
  • 「Attachments 配下」に集約される運用が都合良い
  • UI の操作感に寄せたい(保存場所だけでも合わせたい)

注意:添付ファイル方式は “アイテム ID が必要”

添付ファイルは基本的に .../Attachments/<ItemId>/ に格納されます。つまり、新規アイテムの場合は先にアイテムを作って ID を確定させる必要があります。そのため「新規作成+画像添付+Image 列反映」を一発で完結させるのは難しく、処理が複数回の ExecuteQuery() になりがちです。

全体の流れ

  1. (新規の場合)アイテムを先に作成して Id を確定させる
  2. 添付ファイルとして jpg をアップロードする
  3. 添付ファイルの ServerRelativeUrl を取得する
  4. その URL を使って JSON を組み立て、Image 列にセットする

C#(CSOM)サンプル:添付ファイルへアップロード → Image 列へ反映

using System;
using System.IO;
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;

public class ImageValue
{
public string fileName { get; set; }
public string serverUrl { get; set; }
public string serverRelativeUrl { get; set; }
}

public class SharePointImageAttachmentSample
{
public static void UploadJpgAsAttachmentAndSetImageColumn(
ClientContext ctx,
string listTitle,
int itemId,
string imageColumnInternalName,
string localJpgPath)
{
if (ctx == null) throw new ArgumentNullException(nameof(ctx));
if (string.IsNullOrWhiteSpace(listTitle)) throw new ArgumentException("listTitle is required.");
if (string.IsNullOrWhiteSpace(imageColumnInternalName)) throw new ArgumentException("imageColumnInternalName is required.");
if (!File.Exists(localJpgPath)) throw new FileNotFoundException("localJpgPath not found.", localJpgPath);


    var list = ctx.Web.Lists.GetByTitle(listTitle);
    var item = list.GetItemById(itemId);

    // 添付ファイルを使うには、リスト側で添付が有効である必要があります
    ctx.Load(list, l => l.EnableAttachments);
    ctx.ExecuteQuery();
    if (!list.EnableAttachments)
    {
        throw new InvalidOperationException("This list has attachments disabled (EnableAttachments = false).");
    }

    // 1) 添付ファイルとしてアップロード
    var fileName = Path.GetFileName(localJpgPath);
    Attachment addedAttachment;

    using (var fs = new FileStream(localJpgPath, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        var attInfo = new AttachmentCreationInformation
        {
            FileName = fileName,
            ContentStream = fs
        };

        addedAttachment = item.AttachmentFiles.Add(attInfo);
        ctx.Load(addedAttachment, a => a.FileName, a => a.ServerRelativeUrl);
        ctx.ExecuteQuery();
    }

    // 2) Image 列の JSON を作る
    var origin = new Uri(ctx.Url).GetLeftPart(UriPartial.Authority);

    var img = new ImageValue
    {
        fileName = addedAttachment.FileName,
        serverUrl = origin,
        serverRelativeUrl = addedAttachment.ServerRelativeUrl
    };

    string json = JsonConvert.SerializeObject(img);

    // 3) Image 列へ反映
    item[imageColumnInternalName] = json;
    item.Update();
    ctx.ExecuteQuery();
}

// (参考)新規作成アイテムに対して行う場合の骨組み
public static int CreateItemThenAttachAndSetImageColumn(
    ClientContext ctx,
    string listTitle,
    string imageColumnInternalName,
    string localJpgPath)
{
    var list = ctx.Web.Lists.GetByTitle(listTitle);

    // A) 先にアイテムを作って ID を確定
    var itemCreateInfo = new ListItemCreationInformation();
    var newItem = list.AddItem(itemCreateInfo);

    // 例:タイトルを入れる(必要に応じて)
    newItem["Title"] = "Created by CSOM";
    newItem.Update();
    ctx.ExecuteQuery();

    int newId = newItem.Id;

    // B) 確定した ID に対して「添付→Image 列反映」
    UploadJpgAsAttachmentAndSetImageColumn(ctx, listTitle, newId, imageColumnInternalName, localJpgPath);

    return newId;
}


}

添付ファイル方式のメリット・デメリット

項目メリットデメリット
保存場所アイテムに紐づき、データがまとまりやすい添付が無効だと使えない/ライブラリより運用機能が少ない
URL の安定性Attachments/<ItemId>/ の形で分かりやすいアイテム ID が必要で、処理が複数ステップになりやすい
UI 編集との相性「保存場所」だけは UI と同じに寄せられる環境によっては UI 上で再編集・置換が期待通り動かないことがある

「UI が作る JSON(数字入りファイル名)」を無理に再現しない方がいい理由

モダン UI は内部的に、ファイル名にランダム文字列や数値を付けたり、追加のメタ情報(サムネイル用途の情報など)を JSON に持たせたりすることがあります。これを目視で “同じ形にしよう” としても、次の理由で安定しません。

  • 公式に固定仕様として公開されていない部分が混ざりやすい(変更されても検知が難しい)
  • 同じサイトでも列の設定や UX 更新で値の形が変わる可能性がある
  • 「表示に必要な最小要件」より多くを再現しようとして壊れやすくなる

そのため、開発側は「自分がコントロールできる場所(ライブラリ or 添付)」へ画像を置き、その URL を参照する最小限の JSONを Image 列へ入れる、という方針が実務では成功しやすいです。

“UI 上で後から編集できない” ときの考え方と回避策

質問の文脈でもよく出てくるのが「添付ファイルとしてアップロードした画像は、後で UI から Image 列の編集ができない(または期待通りに差し替えられない)」というケースです。これには環境依存の要素があり、確実な万能解は作りづらいのが正直なところです。

ただし運用上の回避策は用意できます。

回避策A:編集可能性を重視するなら “ライブラリ保存” を採用する

  • 画像をドキュメント ライブラリに置けば、SharePoint の標準機能(バージョン、権限、プレビュー等)を活用できます。
  • Image 列は「参照先 URL」と割り切り、差し替え時はライブラリ側のファイルを置換(同名上書き)する設計にすると、一覧表示の見た目を変えずに更新できます。

回避策B:添付ファイル方式なら “置換は CSOM でやる” と割り切る

  • UI 編集が不安定な場合でも、CSOM なら「既存添付を削除 → 新しい添付を追加 → Image 列更新」ができます。
  • 運用ルールとして「画像の差し替えはシステム(バッチ)経由で行う」を決めると、ユーザーが迷いにくくなります。

回避策C:UI 編集の要件が強いなら “設計段階で検証項目に入れる”

  • 本番テナント/同等環境で「UI から Image 列編集が可能か」「添付ファイル方式で差し替えできるか」は事前に検証してください。
  • 同じ Microsoft 365 でも、機能の展開タイミングやリリースリングで挙動が変わることがあります。

よくあるハマりどころ(トラブルシューティング)

最後に、CSOM で Image 列を扱うときに現場で起きやすいトラブルと、切り分けの方向性を表にまとめます。

症状原因の候補対処
画像が表示されず、空欄になるJSON のキー名が違う/文字列として入っていないfileName, serverUrl, serverRelativeUrl のスペル・大小・キャメルケースを確認。CSOM では JSON “文字列” をセット。
壊れた画像(読み込みエラー)になる参照先 URL が間違っている/権限がないserverRelativeUrl をブラウザーで直接開いて表示できるか確認。閲覧者権限で画像にアクセスできるかも確認。
スペースを含むパスで失敗するURL エンコード不足原則、CSOM が返す ServerRelativeUrl をそのまま使う。自前生成する場合はスペースを %20 に。
添付ファイル追加で例外になるリストで添付が無効list.EnableAttachments を確認し、有効化するか、ライブラリ保存方式へ切替。
新規作成で添付がうまくいかないアイテム ID 未確定先にアイテムを作成し ExecuteQuery() して Id を確定させてから添付を追加。
列にセットしたのに一覧に反映されない内部名の取り違え/列の種類が違う列の内部名を再確認。Image 列以外(ハイパーリンク、複数行テキスト等)に JSON を入れていないか確認。

運用で迷わないためのおすすめ設計

最後に、実装だけでなく運用面で失敗しにくい判断軸をまとめます。

  • 安定性重視:画像はドキュメント ライブラリに集約し、Image 列は参照(URL)として使う
  • アイテム完結重視:添付ファイルとして保持し、差し替えは CSOM/バッチで統制する
  • 権限設計:「一覧を見る人」が画像にもアクセスできる権限を必ず担保する(表示できない原因の多くは権限)
  • 命名規則:衝突回避のために GUID やタイムスタンプを含める(例:img_20251221_101530.jpg
  • 仕様変更耐性:UI の内部 JSON を完全再現するより、最小 JSON を安定運用する

まとめ

SharePoint Online の Image 列に CSOM(C#) で jpg を表示させるコツは、「列に画像データを入れる」のではなく、画像ファイルをどこかにアップロードして、その参照情報を JSON で列に保存するという考え方に切り替えることです。

  • 最も堅実なのは、画像をライブラリにアップロードして URL を JSON でセットする方法
  • UI と同じ場所に寄せたいなら、添付ファイルとしてアップロード → 添付の URL を JSON に入れる方法
  • UI が生成する “数字入り” の値を無理に再現せず、最小構成の JSON で安定運用するのが実務向き

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次