WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

.NET Framework 版の Web API にファイルアップロード

by WebSurfer 9. March 2023 10:23

.NET Framework 版の ASP.NET Web API はファイルアップロードの受信に対応してません。正確に言うと multipart/form-data 形式で送信されてきたデータのフォーマッターが実装されていません。

Web API にファイルアップロード

なので、アクションメソッドの引数を Post(HttpPostedFileBase file) というようして、それに multipart/form-data 形式でファイルを送信すると UnsupportedMediaTypeException がスローされ、

"メディアの型 'multipart/form-data' のコンテンツから型 'HttpPostedFileBase' のオブジェクトを読み取るために使用可能な MediaTypeFormatter がありません。"

・・・というエラーになります。

その対処方法を書きます。基本的には Microsoft のドキュメント「ASP.NET Web APIでの HTML フォーム データの送信: ファイルのアップロードとマルチパート MIME」に書いてあった方法です。

例えば以下のような View で HTML5 fetch API を使ってファイルをアップロードするとします。

<form id="form1" method="post" enctype="multipart/form-data">
    <input name="caption" type="text" value="Summer Vacation" />

    <input type="file" name="file" />
</form>

<button type="button" id="button1" class="btn btn-primary">
    アップロード
</button>

<div id="result1"></div>

@section Scripts {
    <script type="text/javascript">
        let resultDiv, uploadButton;

        window.addEventListener('DOMContentLoaded', () => {
            uploadButton = document.getElementById("button1");
            resultDiv = document.getElementById("result1");
            uploadButton.addEventListener('click', uploadFile);
        });

        const uploadFile = async () => {
            let fd = new FormData(document.getElementById("form1"));
            const param = {
                method: "POST",
                body: fd
            }
            const response = await fetch("/api/FileUpload", param);
            if (response.ok) {
                const message = await response.text();
                resultDiv.innerText = message;
            } else {
                resultDiv.innerText = "アップロード失敗";
            }
        };
    </script>
}

送信されるデータは以下のように multipart/form-data 形式になります。

------WebKitFormBoundaryoasm5HIwy0wijTSo
Content-Disposition: form-data; name="caption"

Summer Vacation
------WebKitFormBoundaryoasm5HIwy0wijTSo
Content-Disposition: form-data; name="file"; filename="0305Result.jpg"
Content-Type: image/jpeg

それを .NET Framework 版の ASP.NET Web API のアクションメソッドで取得してサーバーのフォルダにファイルとして書き込むコードを書いてみました。以下の通りです。

説明はコメントに書きましたのでそれを見てください。手抜きでスミマセン。

using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.Text;
using System.Net.Http.Headers;

namespace WebAPI.Controllers
{
    public class FileUploadController : ApiController
    {
        // アクションメソッドは引数無しにする。フォーマッタを呼び出さ
        // ずアクションメソッド内で要求本文を処理するため
        public async Task<HttpResponseMessage> Post()
        {
            // 要求にマルチパート MIME メッセージが含まれているか確認
            if (!Request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(
                               HttpStatusCode.UnsupportedMediaType);
            }

            // アプリケーションルート直下に UploadedFiles というフォル
            // ダを設け、そこにアップロードされてきたファイルを書き込
            // む。以下のコードでそのフォルダの物理パスを取得
            string root = HttpContext.Current.Server
                                     .MapPath("~/UploadedFiles");

            // アップロードされたファイルにファイルストリームを割り当て
            var provider = new MultipartFormDataStreamProvider(root);

            // マルチパート MIME メッセージのすべてのファイル部分を
            // 抽出し、上の root で指定されたフォルダに書き込む。
            // ファイル名は ReadAsMultipartAsync が自動的に一意に
            // なるように生成する
            await Request.Content.ReadAsMultipartAsync(provider);

            // ReadAsMultipartAsync メソッドが完了すると、FileData
            // プロパティで MultipartFileData オブジェクトのコレ
            // クションを取得できる。MultipartFileData オブジェクト
            // からファイルに関する情報を取得できる
            foreach (MultipartFileData file in provider.FileData)
            {
                // ReadAsMultipartAsync メソッドが自動的に生成した
                // ファイル名をフルパスで取得。UploadedFiles フォル
                // ダにはその名前でアップロードされてきたファイルが
                // 書き込まれている
                string source = file.LocalFileName;

                // 要求ヘッダに含まれるファイル名を取得。取得した
                // 文字列は何故か " で囲われるのでそれを除去
                var name = file.Headers.ContentDisposition
                                       .FileName.Replace("\"", "");
                string dist = root + "\\" + name;

                // ReadAsMultipartAsync メソッドが生成したファイル
                // 名を要求ヘッダに含まれるファイル名に書き換える。
                // 同名のファイルが存在する場合は先に削除する
                File.Delete(dist);
                File.Move(source, dist);
            }

            // <input name="caption" type="text" /> で送信されて
            // きたデータは以下のようにして取得できる
            NameValueCollection col = provider.FormData;
            foreach (string s in col.AllKeys)
            {
                Trace.WriteLine($"Key: {s}, Value: {col[s]}");
            }

            string result = "アップロード完了";

            // .NET Framework 版の Web API は JSON を返すのが基本
            // らしく、アクションメソッドの戻り値の型を string にし
            // て return result; とすると送信される文字列は " で囲
            // われてしまうので以下のようにする
            return new HttpResponseMessage
            {
                Content = new StringContent(result,
                                            Encoding.UTF8,
                                            "text/plain")
            };
        }
    }
}

何か見落としがあるような気もしますが、自分が検証した限り期待通りに動きました。

ちなみに、Web API ではなくて MVC5 のコントローラーや ASP.NET Core の Web API には multipart/form-data 形式に対応するフォーマッターが組み込まれているので、上記のようなことをする必要はありません。なので、そちらの方向に進んだ方が正解かもしれません。

Tags: ,

Web API

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  July 2024  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar