WebMatrix でファイルのアップロード

執筆日時:

f:id:daruyanagi:20120819110408p:plain

今日は「WebMatrix 2」でファイルのアップロードを試してみた。なお、このサンプルは「Empty Sites」テンプレートを元に作成している。

Delault.cshtml

<!DOCTYPE html>

<html lang="ja"> <head> <meta charset="utf-8" /> <title>マイ サイトのタイトル</title> </head> <body> <form action="~/Upload" method="post" enctype="multipart/form-data"> <input type="file" name="upload" /><br /> <input type="submit" name="submit" /> </form> </body> </html>

拡張子を html にすれば、ただの HTML ドキュメントだね! ファイルのアップロードを行うので、 multipart/form-data をつけるのを忘れないように。

f:id:daruyanagi:20120819110559p:plain

Upload.cshtml

アップロード処理を行う cshtml はこんな感じにしてみた。

ほんとは path が存在しなければ例外、 file のサイズが 0 ならば例外、 file が image/*** でなければ例外、といったチェックを入れるのだけれど、ソースが長くなるので割愛している。あと、最初から複数ファイルのアップデートに対応できるように記述している。

@using System.IO

@functions { enum Result { Success, Error }; }

@{ var result = Result.Error; var message = "You can use only POST method."; var link = string.Empty;

if (IsPost) { foreach (var key in Request.Files.AllKeys) { var file = Request.Files[key];

try { const string OUTPUT = "~/Files/"; var path = Server.MapPath(OUTPUT);

var src = Path.GetFileName(file.FileName); var dst = string.Format( "{0:yyyyMMdd-HHmmssfff}{1}", DateTime.Now, Path.GetExtension(src).ToLower() );

file.SaveAs(Path.Combine(path, dst));

result = Result.Success; message = string.Format( "{0} is uploaded as {1}.", src, dst ); link = VirtualPathUtility.ToAbsolute(OUTPUT + dst); } catch (Exception e) { result = Result.Error; message = e.Message; } } } }

<h1>@result</h1> <p>@message</p> if (!string.IsNullOrEmpty(link)) { <p><img src="@link" /></p> } <p>&raquo; Back to <a href="~/">home</a></p>

基本的には、 Request.Files でファイルを取得し、 SaveAs() で保存するだけ。そのほかはファイル名の決定だのエラー処理だのといったことをしているに過ぎない。

Default.cshtml から画像ファイルを POST すると、

f:id:daruyanagi:20120819111843p:plain

エラーが出たらこんな感じで……

f:id:daruyanagi:20120819111723p:plain

成功したらこんな感じになる。

f:id:daruyanagi:20120819112043p:plain

"~/Files/“フォルダが夢のようになっておるな!

ステップアップ

ヘルパーで楽をしよ……ぅ?

f:id:daruyanagi:20120819114311p:plain

ASP.NET Web Helpers Library という NuGet をインストールすると、複数ファイルのアップロードに対応した Form タグを簡単に生成できる。

@FileUpload.GetHtml()

でも、個人的にはあんまり好きじゃなかったので今回は使わなかった。

<!DOCTYPE html>

@{ if (IsPost) { foreach (var key in Request.Files.AllKeys) { var file = Request.Files[key];

try { file.SaveAs( System.IO.Path.Combine( Server.MapPath("~/Files/"), file.FileName) ); } catch (Exception e) {

} } } }

<html lang="ja"> <head> <meta charset="utf-8" /> <title>マイ サイトのタイトル</title> </head> <body> @FileUpload.GetHtml() </body> </html>

f:id:daruyanagi:20120819115634p:plain

なんか動的に生成されるノードの名前がカブってるし*1、あんまりよくわかんなかった。

ビューでつかう変数をまとめる

Upload.cshtml のソースコードがなんだか冗長なのは、HTML の出力に使う result、message、link という3つの変数を処理するためだけど、こいつらって匿名クラスでまとめてもいいよね。

@using System.IO

@functions { enum Result { Success, Error }; }

@{ const string OUTPUT = "~/Files/"; dynamic model = null;

if (IsPost) { foreach (var key in Request.Files.AllKeys) { var file = Request.Files[key];

try { var path = Server.MapPath(OUTPUT);

var src = Path.GetFileName(file.FileName); var dst = string.Format( "{0:yyyyMMdd-HHmmssfff}{1}", DateTime.Now, Path.GetExtension(src).ToLower() );

file.SaveAs(Path.Combine(path, dst));

model = new { Result = Result.Success, Message = string .Format("{0}‘s uploaded as {1}", src, dst), Link = VirtualPathUtility .ToAbsolute(OUTPUT + dst), }; } catch (Exception e) { model = new { Result = Result.Error, Message = e.Message, Link = string.Empty, }; } } } else { model = new { Result = Result.Error, Message = "You can use only POST method", Link = string.Empty, }; } }

<h1>@model.Result</h1> <p>@model.Message</p> @if (!string.IsNullOrEmpty(model.Link)) { <p><img src="@model.Link" /></p> } <p>&raquo; Back to <a href="~/">home</a></p>

記述量はかえって多くなったけど、「何かの処理 → 結果(モデル)の生成」という流れが明確になった気がする。この @model っていうのが MVVM の ViewModel じゃない ViewModel という理解でいいんでしょうか。

Ajax には Json で応える

ViewModel を返すことの利点は、可読性だけじゃない。たとえばこんなこともできる。

@if (IsAjax)
{
// Response.ContentType = "application/json";
Response.Write(Json.Encode(model));
}
else
{
<h1>@model.Result</h1>
<p>@model.Message</p>
if (!string.IsNullOrEmpty(model.Link))
{
<p><img src="@model.Link" /></p>
}
<p>&raquo; Back to <a href="~/">home</a></p>
}

f:id:daruyanagi:20120819125626p:plain

Ajax リクエストに Json で応えるなんてことも簡単にできる!

拡張メソッドのお時間です

あとさ、これダサいよね。

foreach (var key in Request.Files.AllKeys)
{
var file = Request.Files[key];
:
:

拡張メソッドを書いて、シンプルにしましょう。

foreach (var file in Request.Files.ToEnumerable())
{
:
:

~/App_Code/HttpFileCollectionBaseExtension.cs を作成してこのように書いてみました。

using System.Collections.Generic;
using System.Web;

public static class HttpFileCollectionBaseExtension { public static IEnumerable<HttpPostedFileBase> ToEnumerable( this HttpFileCollectionBase target) { foreach (var key in target.AllKeys) { yield return target[key]; } } }

*1:JavaScriptの不具合かなぁ