WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

FileUpload と form 要素 の enctype

by WebSurfer 2015年1月3日 13:54

ファイルアップロードに用いる ASP.NET サーバーコントロールの FileUpload をページに配置すると、ASP.NET がブラウザに送信する html コードをレンダリングする際、form 要素に enctype="multipart/form-data" を追加します。

下の画像で、赤線で囲った部分がそれです。ちなみに、ソースコードでは form 要素は <form id="form1" runat="server"> となっています。

enctype の追加

FileUpload コントロールに限らず、HtmlInputFile や Ajax Control Toolkit の AjaxFileUpload, AsyncFileUpload を使ったり、<input type="file" ... /> 要素に runat="server" 属性を追加(結果、HtmlInputFile と同じになる)しても同様です。

普通の html 要素の <input type="file" ... /> を使った場合はそういうことはなく、自力で enctype="multipart/form-data" を form 要素に追加してやらなければなりません。

form 要素に enctype="multipart/form-data" 属性の設定がないと、ファイルはアップロードされません。当然、サーバー側でファイルを取得できません。

知ってました? 実は、サーバーコントロールしか使ったことがない自分は、知らなかったです。なので、そこに気がつかず、ちょっとハマってしまいました。(汗)

Tags: ,

Upload Download

ブラウザーモードとドキュメントモード

by WebSurfer 2014年12月29日 14:56

Internet Explorer (IE) で F12 キーを押して開発者ツールを起動すると、下の画像のようにブラウザーモードとドキュメントモードというメニューが表示されます。これについていろいろ調べましたので、後日役に立ちそうなことを忘れないように書いておきます。

ブラウザーモードの設定

ブラウザーモード

F12 開発者ツールのみで設定が可能なオプションで、IE 自体の動きが選択されたバージョンになります。

例えば、実際に使っているブラウザが IE9 でも、ブラウザーモードで他のバージョンを選択するとそのバージョンの動きになります。

試しに IE9 で[Internet Explorer 8(8)]を選択してみると、サーバーに送信される User Agent は Mozilla/4.0 (compatible; MSIE 8.0; ... というように IE8 のものになりました。(サーバーに送られる User Agent は、IE の互換表示の設定によっても変わってきます。詳しくは下の「2016/3/1 追記」を見てください)

また、<!--[if IE 8]> のような条件付コメントも、ブラウザーモードの選択によって影響を受けるとのことです(未検証です)。

ドキュメントモード

これはレンダリングエンジンのみを変更します。ブラウザーモードの切り替えのように IE 自体の動きは変更しません。

F12 開発者ツールを使う以外にも、(1) meta タグ (X-UA-Compatible) で指定、(2) IE の[ツール(T)]⇒[互換表示設定(B)]、(3) IE のアドレスバー右横の互換表示ボタン (IE11 では無くなった) で設定可能です。

ただし、IE9 で試した限りですが、上記の (2) と (3) では[Internet Explorer 7 標準(7)]相当になり、IE8 や Quirks モードを選ぶというようなことはできませんでした。

Quirks モードというのは IE6 で導入された旧バージョン(ということは IE5 以前のはず)のブラウザーと同じようにページが表示されるモードです。CSS の IE 独自実装である expression 関数が使えるのが Quirks モードです。

その他

IE10 以降では条件付コメントはサポートされなくなったそうです。

Conditional comments are no longer supported (条件付きコメントがサポートされなくなった)

IE10 以降では Quirks モードの既定の動作が修正されているそうです。

Interoperable (HTML5) quirks mode (相互運用可能な (HTML5) Quirks モード)

IE11 以降ではエッジモードが使われており、ドキュメントモードは廃止される可能性があるとのことで、将来的には切り替えることができなくなる可能性があるようです。ただし、現時点では F12 開発者ツールや meta タグ (X-UA-Compatible) での設定は可能な様子です。

Compatibility changes in IE11 (IE11 の互換性の変更点)

Web制作者は注意! Internet Explorer 11で変更された「互換性」

meta タグ (X-UA-Compatible) で指定する content="IE=7" とか content="IE=EmulateIE7" の違いは、以下のページが参考になると思います。

Defining document compatibility (ドキュメント互換性の定義)

Specifying legacy document modes

上の後者の記事によると、以下のように edge を指定した場合、IE6 ~ IE11 では "the highest standards mode supported by Internet Explorer" になるとのことです。

<meta http-equiv="x-ua-compatible" content="IE=edge" />

ただし、この先 meta タグで X-UA-Compatible は無視されるという話もあります。

"living" Edge ドキュメント モード

一体何を信じたら良いのかって感じですが、とにかく X-UA-Compatible でドキュメントモードを古いものに指定するようなことをしなければ今後も問題が起こることはなさそうです。

------- 2016/3/1 追記 -------

サーバーに送られる User Agent は、ブラウザモードの設定だけでなく、IE が互換表示するサイトと認識すると旧バージョンのものになるようです。

IE9 で試してみましたが、[ツール(T)]⇒[互換表示設定(B)]で表示されるダイアログで、要求するページのサイトが[互換表示に追加した Web サイト(W):]に含まれている場合とか、[すべての Web サイトを互換表示で表示する(E)]にチェックを入れた場合は IE7 の UA が Web サーバーに送られます。

[イントラネット サイトを互換表示で表示する(I)]にはデフォルトでチェックマークが入っているので要注意ですね。

Tags:

その他

親子関係のあるデータの編集・削除

by WebSurfer 2014年12月22日 12:08

先に、(1) Entity Framework Code First の機能を利用して MVC4 アプリケーション用 SQL Server DB のテーブル作成、(2) Create アクションメソッドとビューを追加して作成したテーブルに親子関係のあるデータを登録する方法・・・という記事を書きました。

今回はそれに続いて Edit, Delete アクションメソッドとビューを追加し、先に追加した Create アクションメソッドで登録したデータの編集・削除を行う方法を書きます。(下の画像は Delete 操作のときのものです)

データの削除画面

先の記事 MVC4 EF Code First で書きましたように、Code First の機能を用いて生成した SQL Server DB の Parents, Children テーブルの間に外部キー制約が設定されています(Parents の Id ← Children の Parent_Id)。

なので、階層更新が必要になります。つまり、登録する場合は Parents テーブルに親レコードを INSERT した後 Children テーブルに子レコードを INSERT する、削除する場合は先に Children テーブルの関連する子レコードを全部 DELETE してから Parents テーブルの親レコードを DELETE するという操作が必要になります。

そのために Entity Framework 上で必要な操作としては、対象となるレコードの エンティティオブジェクトの状態 を、登録なら Added、編集なら Modified、削除なら Deleted としてマークし、DbContext.SaveChanges メソッド を適用すればよさそうです。

階層更新(上の例で言うと、INSERT, DELETE するときの順番)や、INSERT 時に Parents テーブルの親レコードの Id(IDENTITY 列)から値を取得して Children テーブルの子レコードの Parent_Id に設定するという操作は Entity Framework が面倒を見てくれるようです。(それを書いた公式文書が見つけられず、検証した結果だけ見てそう言っているので、100% の自信はないですけど・・・)

従って、プログラマが行うべきことで重要なのは、対象エンティティオブジェクトの状態を正しく Added, Modified, Deleted に設定してやると言うことになります。

具体的な方法は、文章で書くよりはコードを示した方がわかりやすいと思いますので、Edit, Delete 操作用のアクションメソッドと View のコードを下にアップしました。

自分が犯した失敗例や、必要な(と自分が思った)コメントも書いておきました。参考になれば幸いです。

Edit アクションメソッド

注意事項はコード内のコメントに書きましたので、それを参照してください。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mvc4App2.Models;

namespace Mvc4App2.Controllers
{
  public class ParentChildController : Controller
  {
    private ParentChildContext db = new ParentChildContext();
        
    //・・・中略・・・

    //
    // GET: /ParentChild/Edit/5
    public ActionResult Edit(int id)
    {
      Parent parent = db.Parents.Find(id);
      if (parent == null)
      {
        return HttpNotFound();
      }
      return View(parent);
    }

    //
    // POST: /ParentChild/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(int id, Parent postedParent)
    {
      if (ModelState.IsValid)
      {
        // 以下のコードはダメ。
        // ポストされた Name の子レコードが新手に作られて
        // INSERT され、その Parent_Id が親の Id に設定さ
        // れる。既存の子レコードの Parent_Id は NULL に
        // 書き換えられる。

        //Parent parent = db.Parents.Find(id);
        //UpdateModel<Parent>(parent);
        //db.SaveChanges();

  
        // 以下のコードもダメ。
        // 親レコードしか書き換えられない。

        //db.Entry(postedParent).State = EntityState.Modified;
        //db.SaveChanges();


        // 以下のように子の方のエンティティ状態も 'Modified'
        // に設定すると親も子も期待通り更新される。

        for (int i = 0; i < postedParent.Children.Count; i++)
        {                    
          db.Entry(postedParent.Children[i]).State = 
                                       EntityState.Modified;
        }
        db.Entry(postedParent).State = EntityState.Modified;
        db.SaveChanges();

        return RedirectToAction("Index");
      }
      return View(postedParent);
    }

    //・・・中略・・・

  }
}

View (Edit.cshtml)

Parents, Children とも Id が隠しフィールド(@Html.HiddenFor)に設定されている点に注意してください。

コードの最後の方の @Scripts.Render("~/bundles/jqueryval") は入力検証用の jQuery ライブラリを登録するためのものです。これがないとクライアント側での検証はかかりませんので注意してください。

@model Mvc4App2.Models.Parent

@{
  ViewBag.Title = "Edit";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
  @Html.AntiForgeryToken()
  @Html.ValidationSummary(true)

  <fieldset>
    <legend>Parent</legend>

    @Html.HiddenFor(model => model.Id)

    <div class="editor-label">
      @Html.LabelFor(model => model.Name)
    </div>
    <div class="editor-field">
      @Html.EditorFor(model => model.Name)
      @Html.ValidationMessageFor(model => model.Name)
    </div>

    <hr />

    @for (int i = 0; i < Model.Children.Count; i++)
    {       
      @Html.HiddenFor(model => model.Children[i].Id)
            
      <div class="editor-label">
        @Html.LabelFor(model => 
                        model.Children[i].Name)
      </div>
      <div class="editor-field">
        @Html.EditorFor(model => 
                        model.Children[i].Name)
        @Html.ValidationMessageFor(model => 
                        model.Children[i].Name)
      </div>
                
      <hr />
    }

    <p>
      <input type="submit" value="Save" />
    </p>
  </fieldset>
}

<div>
  @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
  @Scripts.Render("~/bundles/jqueryval")
}

Delete アクションメソッド

注意事項はコード内のコメントに書きましたので、それを参照してください。


    //・・・前略・・・

    //
    // GET: /ParentChild/Delete/5
    public ActionResult Delete(int id)
    {
      Parent parent = db.Parents.Find(id);
      if (parent == null)
      {
        return HttpNotFound();
      }
      return View(parent);
    }

    //
    // POST: /ParentChild/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
      // 以下のコードはダメ。
      // 子のデータががある場合、FK 制約に引っかかって
      // SqlException がスローされる。

      //Parent parent = db.Parents.Find(id);
      //db.Parents.Remove(parent);

            
      // 以下のコードもダメ。
      // 親と1 つ目の子レコードは削除されるが 2 つ目が残ってし
      // まう。(ただし残った子レコードの Parent_Id は NULL に
      // 書き換えられるので FK 制約には引っかからない。何故?)
      // Remove すると、その度 parent.Children.Count が一つ減っ
      // てしまう。そのため 1 つ目の子レコードを Remove した後
      // ループを抜けてしまい、2 つ目が Remove できないので、
      // db.SaveChanges() しても 2 つ目が残ってしまう。

      //Parent parent = db.Parents.Find(id);
      //for (int i = 0; i < parent.Children.Count; i++)
      //{
      //    db.Children.Remove(parent.Children[i]);
      //}
      //db.Parents.Remove(parent);            
      //db.SaveChanges();


      // 以下のように一旦 Child のコレクションを保持しておき、
      // それを使って Remove すれば OK。

      Parent parent = db.Parents.Find(id);

      List<Child> children = new List<Child>();
      foreach (Child child in parent.Children)
      {
        children.Add(child);
      }

      foreach (Child child in children)
      {
        db.Children.Remove(child);
      }
      db.Parents.Remove(parent);            
      db.SaveChanges();

      return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
      db.Dispose();
      base.Dispose(disposing);
    }
  }
}

View (Delete.cshtml)

form 要素には action="/ParentChild/Delete/21" というように設定されます(21 は親レコードの Id)。なので、[Delete]ボタンをクリックして POST すると、アクションメソッド DeleteConfirmed(int id) の引数には親レコードの Id が渡されます。

@model Mvc4App2.Models.Parent

@{
  ViewBag.Title = "Delete";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<fieldset>
  <legend>Parent</legend>
  <div class="display-label">
     @Html.DisplayNameFor(model => model.Id)
  </div>
  <div class="display-field">
    @Html.DisplayFor(model => model.Id)
  </div>
  <div class="display-label">
     @Html.DisplayNameFor(model => model.Name)
  </div>
  <div class="display-field">
    @Html.DisplayFor(model => model.Name)
  </div>

  <hr />

  @for (int i = 0; i < Model.Children.Count; i++)
  {       
    <div class="display-label">
      @Html.DisplayNameFor(model => 
                      model.Children[i].Id)           
    </div>
    <div class="display-field">
      @Html.DisplayFor(model => 
                      model.Children[i].Id)
    </div>
        
    <div class="display-label">
      @Html.DisplayNameFor(model => 
                      model.Children[i].Name)           
    </div>
    <div class="display-field">
      @Html.DisplayFor(model => 
                      model.Children[i].Name)
    </div>
    <hr />
  }

</fieldset>
@using (Html.BeginForm()) {
  @Html.AntiForgeryToken()
  <p>
    <input type="submit" value="Delete" /> |
    @Html.ActionLink("Back to List", "Index")
  </p>
}

次の課題は、(1) 既存の親に属する子の全部または一部を DELETE、(2) 既存の親に子を追加・・・をどう実装するかですね。やる気が湧いてきたら書いてみます。(笑)

さらなる課題は、同時実行制御や、エラーの際のロールバックをどう実装するかでしょうか。そこのところは勉強不足でまだ見当さえついてません。先はずいぶん長そうです。(汗)

2016/9/12 追記:
Microsoft の文書「Code First の規約」に従って、Child クラスにナビゲーションプロパティと外部キープロパティを定義するとどのような影響があるかを別の記事「Code First で外部キープロパティの定義」に書きました。int 型の外部キープロパティを Child クラスに追加にしたのですが、それによる大きな影響は以下の 2 点です。他にも影響はありますが、詳しくは上にリンクを張った別記事を見てください。
  • 編集・更新: Child クラスに外部キープロパティを追加したので、編集結果を送信して更新をかける際、外部キープロパティの値も送信する必要がある。そうしないと参照整合性制約違反でエラー。
  • 削除: 外部キープロパティを int 型にしたので、外部キーフィールドが NULL 不可になり、連鎖削除が可能になる。

Tags: ,

MVC

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar