Web Form で中心となるのは Web フォームクラスです。そのため、画面の数分の Web フォームクラスを作成することから作業が始まります。
一方 Web MVC で中心的な役割を果たすのはコントローラクラスです。画面の数が何枚あっても、処理単位はコントローラの数で決まります。
MVC 版のフィールドワークデータ操作画面は、すべて一つのコントローラが担当することにしました。フィールドワークデータの検索・追加・更新・削除などの操作ですから、
一つのコントローラで実装するのが最適と思います。
それでは、状態遷移図の議論から始めましょう。
フィールドワークデータを操作する状態遷移図を示します。
上図で示したように、6つの状態を持ちます。
この特別編で取り上げるのは、ページングの機能に関してです。 Web Form の場合には、標準でページングの機能が提供されます。しかし MVC では、ページングは自前で用意する必要があります。 ページングを利用している画面を次に示します。フィールドワークデータを検索して、その結果が複数ページにわたる場合に必要になります。(画像をクリックすると、拡大表示します)
この自前のページングをどのように実装するか?次のようにすることにしました。
ページングの主役となるのは、このモデルクラスです。このクラスは、次のようにしました。
PagingModel
└ ConditionAndMaterials
PagingModel がページングに必要な機能を網羅した汎用クラスです。 ConditionAndMaterials クラスは派生クラスとなり、ページングの機能をそのまま利用するクラスです。 そして、このクラスはフィールドワークデータを検索した後のビューで使用するモデルとなります。ページングが必要なビューでは、PagingModel から派生したクラスをモデルとするだけで、ページングの機能を利用できるようになります。
ただし、条件があります。あまりないとは思いますが、1画面で異なるページングデータを複数操作したい場合は、継承を利用したこのやり方では対応できません。1画面で利用できるページングは1つに限定されます。
もちろん、テーブルの上下にそれぞれページング用ボタンなどを配置するのはかまいません。全く同じデータをもとにしてページングしているだけですので、問題なく動作します。
異なるページングを複数利用したい場合はどうするか?そのためには、必要なだけの PagingModel を変数にもつクラスを定義して、それぞれの PagingModel 変数に処理を委譲するようにする必要があるでしょう。工夫してみてください。
PagingModel の実装を示します。豊富なコメントが付いていますので、プログラムは長いですが、理解は難しくないと思います。
1 public class PagingModel
2 {
3 private static int PAGEITEMS = 10;
4 public PagingModel()
5 {
6 numbers = null;
7 Mode = ModeValue.ALL;
8 Count = 0;
9 Page = 0;
10 PageItems = PAGEITEMS;
11 NumberItems = PAGEITEMS;
12 Url = "";
13 Param = "page";
14 }
/*
ページングで使用するナビゲーション用のボタンとリンク
デフォルトでは、ALL
FIRST 先頭ページに移動するボタン
PREV 前頁に移動するボタン
NEXT 次頁に移動するボタン
LAST 最終ページに移動するボタン
NUMBER ページ番号で直接移動するページ番号
ALL 全てのナビゲーション用ボタンとリンクを有効にする
*/
15 public enum ModeValue { FIRST = 0x01, PREV = 0x02, NEXT = 0x04, LAST = 0x08, NUMBER = 0x10, ALL = FIRST | PREV | NEXT | LAST | NUMBER };
/*
現在のナビゲーションボタンとリンク
デフォルトではALL
*/
16 public ModeValue Mode { get; set; }
/*
データの総数
*/
17 public int Count { get; set; }
/*
1ページに表示するデータの件数
デフォルトでは10件
*/
18 public int PageItems { get; set; }
/*
表示モードにNUMBERが含まれている場合に、何件のページ番号を表示するか
デフォルトではページ番号は10件
*/
19 public int NumberItems { get; set; }
/*
リンク先のURL
*/
20 public string Url { get; set; }
/*
ページ番号をGETで指定するためのパラメータ名
paramがpageであった場合、?page=1のような書式でURLが呼び出される
デフォルトのパラメータ名は「"page"」
*/
21 public string Param { get; set; }
/*
GETのパラメータで渡されたページ番号にバインドする変数
ページは1を基準にする
*/
22 public int Page { get; set; }
23 public IEnumerable<Object> Objects
24 {
25 get; set;
26 }
27 public string FirstUrl()
28 {
29 return Url + "?" + Param + "=1";
30 }
31 public string PrevUrl()
32 {
33 if (Page < 1)
34 return "";
35 int index = Page - 1;
36 if (index <= 0)
37 index = 1;
38 return Url + "?" + Param + "=" + index;
39 }
40 public string NextUrl()
41 {
42 if (Page < 1)
43 return "";
44 int index = Page - 1;
45 index += 2;
46 int max = Count / PageItems;
47 if (Count % PageItems != 0)
48 ++max;
49 index = index > max ? max : index;
50 if (index <= 0)
51 ++index;
52 return Url + "?" + Param + "=" + index;
53 }
54 public string LastUrl()
55 {
56 int max = Count / PageItems;
57 if (Count % PageItems != 0)
58 ++max;
59 if (max <= 0)
60 ++max;
61 return Url + "?" + Param + "=" + max;
62 }
63 public string NumberUrl(int val)
64 {
65 return Url + "?" + Param + "=" + val.ToString();
66 }
67 private List<int> numbers;
68 public List<int> Numbers()
69 {
70 if (Count <= 0 || PageItems <= 0 || NumberItems <= 0 || (Mode & ModeValue.NUMBER) == 0)
71 return new List<int>();
72 if (numbers != null)
73 return numbers;
74 int pages = Count / PageItems;
75 if (Count % PageItems != 0)
76 ++pages;
77 int start, current = Page, half = NumberItems / 2;
78 if (current <= half)
79 start = 1;
80 else if (current + half > pages)
81 start = pages - NumberItems + 1;
82 else
83 start = current - half;
84 List<int> values = new List<int>();
85 for (int i = 0; start <= pages && i <= NumberItems - 1; ++i, ++start)
86 values.Add(start);
87 numbers = values;
88 return numbers;
89 }
90 public bool IsMode(ModeValue mode)
91 {
92 return ((int)mode & (int)Mode) != 0;
93 }
94 public string IsModeString(ModeValue mode)
95 {
96 return IsMode(mode) ? "" : "hidden";
97 }
98 public bool IsValid()
99 {
100 return Count > 0;
101 }
102 public string IsValidString()
103 {
104 return IsValid() ? "visible" : "hidden";
105 }
106 public string IsPageString(int num)
107 {
108 return num == Page ? "text-danger" : "text-primary";
109 }
110 public void Paging(int startPage, EnumerableTable table)
111 {
112 Page = 0;
113 Count = table.GetRows();
114 if (Count <= PageItems)
115 {
116 Page = 1;
117 Objects = table.ToEnumerable();
118 return;
119 }
120 int index = 0;
121 int cnt = Count;
122 DataRow row;
123 for (int max = (startPage - 1) * PageItems; index < cnt && index < max; ++index)
124 {
125 row = table.GetRow(index);
126 row.Delete();
127 }
128 index += PageItems;
129 for (; index < cnt; ++index)
130 {
131 row = table.GetRow(index);
132 row.Delete();
133 }
134 table.DataTable.AcceptChanges();
135 Page = startPage;
136 Objects = table.ToEnumerable();
137 }
138 }
3行目:1ページに表示する項目数のデフォルト値です。デフォルトでは1ページに10件のデータを表示します
4~14行目:コンストラクタです
15~22行目:各種のプロパティです。内容はコメントやプロパティ名から自明と思います
23~26行目:現在のページ内容となるデータを示します。Paging(int, EnumerableTable) メソッドで計算されます
27~66行目:ページを切り替えるボタンに対応したメソッドです
68~89行目:表示に使用するページ番号を返すメソッドで、以下で説明します
110~137行目:開始ページ番号と検索結果の全データから、そのページに該当するデータだけを抽出するメソッドです。このメソッド呼び出しが必ず必要です
PagingModel クラスの中で分かりにくいのは、 Numbers() メソッドだと思います。このメソッドは、ページ番号を直接クリックして遷移する画面を作成するときに、
どのページ番号から、どのページ番号までが必要となるかを計算するメソッドになります。
説明が簡単となるように、次の条件で図解します。全ページ数が9ページとします。1つの画面には、1度に最大5ページ分(デフォルトでは10です)の数字が並ぶものとします。
以下の図では、その5ページ分のページ番号が[ ]で囲まれています。そして、今表示されているページ番号が( )で囲まれています。
今表示されているページが1 → 2 → … → 9と変化していくと、計算される5ページ分のページ番号がどのようになっていくのかを示しています。
Numbers() メソッドはこの[ ]で囲まれている数字のリストを計算するメソッドになります。
[ (1) 2 3 4 5 ] 6 7 8 9
[ 1 (2) 3 4 5 ] 6 7 8 9
[ 1 2 (3) 4 5 ] 6 7 8 9
1 [ 2 3 (4) 5 6 ] 7 8 9
1 2 [ 3 4 (5) 6 7 ] 8 9
1 2 3 [ 4 5 (6) 7 8 ] 9
1 2 3 4 [ 5 6 (7) 8 9 ]
1 2 3 4 [ 5 6 7 (8) 9 ]
1 2 3 4 [ 5 6 7 8 (9) ]
1ページに表示するページ番号は最大5個ですから、5個の数字のリストを返します。しかし、全ページ数9ページの最初と最後では、返す数字のリストは 変わりませんが、現在のページ番号は変化していくことになります。
ページング用のビューはモデルと比較すると単純です。
_PagingView.cshtml のソース
1 @model FieldworkForMVC.Models.PagingModel
2 <div class="text-center @Model.IsValidString()">
3 <a type="button" href="@Model.FirstUrl()" class="btn btn-default @Model.IsModeString(FieldworkForMVC.Models.PagingModel.ModeValue.FIRST)">
4 <span class="glyphicon glyphicon-fast-backward"></span>
5 </a>
6 <a type="button" href="@Model.PrevUrl()" class="btn btn-default @Model.IsModeString(FieldworkForMVC.Models.PagingModel.ModeValue.PREV)">
7 <span class="glyphicon glyphicon-step-backward"></span>
8 </a>
9 <span style="margin: 5px;font-size: 1.5em;">
10 @foreach (var item in Model.Numbers())
11 {
12 <a href="@Model.NumberUrl(item)" class="@Model.IsPageString(item)">@item.ToString()</a>
13 }
14 </span>
15 <a type="button" href="@Model.NextUrl()" class="btn btn-default @Model.IsModeString(FieldworkForMVC.Models.PagingModel.ModeValue.NEXT)">
16 <span class="glyphicon glyphicon-step-forward"></span>
17 </a>
18 <a type="button" href="@Model.LastUrl()" class="btn btn-default @Model.IsModeString(FieldworkForMVC.Models.PagingModel.ModeValue.LAST)">
19 <span class="glyphicon glyphicon-fast-forward"></span>
20 </a>
21 </div>
1行目:モデル PagingModel の指定です
2行目:ページングそのものを表示するかどうかの判定です
3~5行目:先頭ページに移動の部分です。「<a href="ターゲットのURL?page=1">先頭のアイコン</a>」を出力するのが目的です
6~8行目:前ページに移動の部分です。「<a href="ターゲットのURL?page=前ページ番号">前項のアイコン</a>」を出力するのが目的です
9~14行目:ページ番号で直接移動する部分です。「<a href="ターゲットのURL?page=ページ番号">数字</a>」を出力するのが目的です
15~17行目:次ページに移動の部分です。「<a href="ターゲットのURL?page=次ページ番号">次項のアイコン</a>」を出力するのが目的です
18~20行目:最終ページに移動の部分です「<a href="ターゲットのURL?page=最終ページ番号">最終のアイコン</a>」を出力するのが目的です
この部分ビューを利用する方法を示します。利用は、2段階の部分ビューの階層となっています。
_PagingView.cshtml
└ _MaterialView.cshtml
└ Search.cshtml
_PagingView は PagingModel を直接使用するビューです。 _MaterialView は部分ビューとして _PagingView を使用します。
フィールドワークデータのリストに該当するテーブルの上と下にページングの部分を部品として貼り付けたビューになります。
Search はフィールドワークデータの検索後の画面に相当するビューになり、 _MaterialView を部品として貼り付けたビューになります。
ここで、 _MaterialView をなくすことも可能です。ただし、一覧のテーブル部分を部品化しておくことで、再利用性が高まると考えて、このような構成をとりました。
さて、 Search と _MaterialView はモデルとして、 ConditionAndMaterials を利用します。一方、 _PagingView が使用する モデルは PagingModel です。しかし ConditionAndMaterials クラスは PagingModel から派生していますので、このままで正しく動作します。
_MaterialView の一部分
1 @model FieldworkForMVC.Models.ConditionAndMaterials
2 @Html.Partial("_PagingView")
3 <table class="table table-condensed table-bordered p100">
略
4 </table>
5 @Html.Partial("_PagingView")
1行目:モデルを指定しています
2行目:テーブルの上のページング部分です
3~4行目:テーブルの本体部分です
5行目:テーブルの下のページング部分です
ページングが必要な3行目のテーブルタグの上下に、ページング用の部分ビューを配置してあります。Search ビューでは、この部分ビューをさらに利用しているだけです。
フィールドワークデータの検索画面を検索前の画面(下記図面3)-状態遷移図の初期状態-と検索後の画面(下記図面4)-状態遷移図の検索-を示します。 (画像をクリックすると、拡大表示します)
以下では、次の項目に関して議論します。
初期状態に入ってくるメソッドの実装を示します。
MaterialController の一部分
1 public ActionResult Index()
2 {
3 ReadTable();
4 ViewBag.Majors = majorTable.ToSelectList();
5 ViewBag.Minors = new List<SelectListItem>();
6 ViewBag.Areas = areaTable.ToSelectList();
7 MaterialWindowCondition condition = new MaterialWindowCondition();
8 return View(condition);
9 }
1行目:Index GET用のメソッド定義です
3行目:初期画面に必要な、大分類・小分類・エリア・単位の各テーブルを読み出します
4行目:大分類データからコンポボックス用データに変換します。詳細は こちら を参照してください
5行目:小分類用コンポボックスを空状態にします(大分類が未選択なので、小分類は選択できません)
6行目:エリアデータからコンボボックス用データに変換します。詳細は こちら を参照してください
7行目:検索条件のインスタンスを生成します(初期状態なので、条件は空状態です)
8行目:初期状態のビューを返します
初期状態から出ていくメソッドの実装を示します。
MaterialController の一部分
1 [HttpPost]
2 public ActionResult Index(string button, FormCollection collection)
3 {
4 if (button == null)
5 {
6 string mid = collection.GetValue("majorId").AttemptedValue;
7 ReadTable(mid);
8 MaterialWindowCondition condition = ToCondition(collection);
9 ViewBag.Majors = majorTable.ToSelectList(condition.MajorId);
10 ViewBag.Minors = minorTable.ToSelectList();
11 ViewBag.Areas = areaTable.ToSelectList(condition.AreaId);
12 return View(condition);
13 }
14 else if (button.Equals("追 加"))
15 {
16 return RedirectToAction("Create");
17 }
18 else if (button.Equals("検 索"))
19 {
20 MaterialWindowCondition condition = ToCondition(collection);
21 Session["FieldworkCondition"] = condition;
22 return RedirectToAction("Search");
23 }
24 return RedirectToAction("Index");
25 }
1行目: POST の宣言です
2行目:メソッドの定義です。第一引数でクリックされたボタンを、第二引数で画面項目の値を受け取ります
4行目:ボタンが無効な場合(大分類が変更されてポストバックされたとき)の処理です
6行目:選択された大分類の値を知ります
7行目:選択された大分類を指定して、大分類;小分類・エリア・単位テーブルを検索します
8行目:ブラウザの画面をもとに、検索条件を作成します
9行目:大分類の値を指定して、大分類用コンボボックスのリストを作成します
10行目:小分類用コンポボックスのリストを作成します
11行目:エリア用コンポボックスのリストを作成します
12行目:初期状態のビューを返します
14行目:追加ボタンがクリックされた場合の処理です
16行目:追加画面に遷移します
18行目:検索ボタンがクリックされた場合の処理です
20行目:ブラウザの画面をもとに、検索条件を作成します
21行目:検索条件をセッションに格納します。次の画面で検索条件が必要になるためです
22行目:検索後の画面に遷移します
24行目:初期状態に遷移します(クリアボタンが押された場合です)
検索後の状態に入ってくるメソッドの実装を示します。
MaterialController の一部分
1 public ActionResult Search()
2 {
3 Session["selectedId"] = "";
4 string val = Request.QueryString.Get("page");
5 int page = 1;
6 if (! string.IsNullOrEmpty(val))
7 int.TryParse(val, out page);
8 MaterialWindowCondition condition = (MaterialWindowCondition)Session["FieldworkCondition"];
9 string mid = condition.MajorId;
10 ReadTable(mid);
11 ViewBag.Majors = majorTable.ToSelectList(mid);
12 ViewBag.Minors = minorTable.ToSelectList(condition.MinorId);
13 ViewBag.Areas = areaTable.ToSelectList(condition.AreaId);
14 materialTable = new MaterialTable(database);
15 if (! materialTable.Search(condition))
16 {
17 ModelState.AddModelError("dbErrorMaterial", minorTable.Result);
18 }
19 else if (! string.IsNullOrEmpty(condition.AreaId))
20 {
21 Area area = areaTable.ToObject(condition.AreaId);
22 Position point;
23 Polygon polygon = new Polygon(area.Positions);
24 DataRow row;
25 Material item;
26 String lat, lon;
27 for (int i = 0, cnt = materialTable.GetRows(); i < cnt; ++i)
28 {
29 row = materialTable.GetRow(i);
30 item = materialTable.ToObject(row);
31 lat = item.Latitude;
32 lon = item.Longitude;
33 if ((!String.IsNullOrEmpty(lat)) && (!String.IsNullOrEmpty(lon)))
34 {
35 point = new Position(lat, lon);
36 if (!polygon.IsInclude(point))
37 row.Delete();
38 }
39 else
40 {
41 row.Delete();
42 }
43 }
44 materialTable.DataTable.AcceptChanges();
45 }
46 ViewBag.PhotoModels = GetPhotoModels(materialTable);
47 Material material = new Material();
48 ConditionAndMaterials model = new ConditionAndMaterials(condition, material);
49 model.Url = "Search";
50 if (page >= 1)
51 model.Paging(page, materialTable);
52 return View(model)
53 }
1行目:メソッドの定義です
3行目:検索結果で選択されている項目が無い状態にします
4~7行目:URL のパラメータからページ番号を得ます。ページング指定で、ページ番号を指定される場合があるためです
8~9行目:直前の検索条件をセッションから読み出します
11~13行目:画面のコンポボックスで使用するリストを用意します
15行目:検索に失敗した場合です
19行目:検索条件にエリアを指定した場合です。エリアは SQL で実行することができず、コードで検査することが必要です
21~44行目:エリアの条件の検査を実施し、該当するデータだけを検索結果に残します
46行目:検索条件に該当するすべての写真データを集めてきます
47~48行目:ビュー用のモデルを生成します
49行目:ページング用のビューの名前を登録しますk
50~51行目:すべてのデータから現在のページ分のデータだけを抽出します
52行目:検索後のビューを返します
検索後の状態から出ているくメソッドの実装を示します。
MaterialController の一部分
1 [HttpPost]
2 public ActionResult Search(string button, FormCollection collection)
3 {
4 if (button == null)
5 {
6 string mid = collection.GetValue("majorId").AttemptedValue;
7 ReadTable(mid);
8 MaterialWindowCondition condition = ToCondition(collection);
9 ViewBag.Majors = majorTable.ToSelectList(condition.MajorId);
10 ViewBag.Minors = minorTable.ToSelectList();
11 ViewBag.Areas = areaTable.ToSelectList(condition.AreaId);
12 }
13 else if (button.Equals("追 加"))
14 {
15 return RedirectToAction("Create");
16 }
17 else if (button.Equals("参 照"))
18 {
19 Session["selectedId"] = collection.GetValue("selectedId").AttemptedValue;
20 return RedirectToAction("Refer");
21 }
22 else if (button.Equals("更 新"))
23 {
24 Session["selectedId"] = collection.GetValue("selectedId").AttemptedValue;
25 return RedirectToAction("Update");
26 }
27 else if (button.Equals("削 除"))
28 {
29 Session["selectedId"] = collection.GetValue("selectedId").AttemptedValue;
30 return RedirectToAction("Delete");
31 }
32 return RedirectToAction("Index");
33 }
1行目: POST の宣言です
2行目:メソッドの定義です。第一引数でクリックされたボタンを、第二引数で画面項目の値を受け取ります
4行目:ボタンが無効な場合(大分類が変更されてポストバックされたとき)の処理です
6行目:選択された大分類の値を知ります
7行目:選択された大分類を指定して、大分類;小分類・エリア・単位テーブルを検索します
8行目:ブラウザの画面をもとに、検索条件を作成します
9行目:大分類の値を指定して、大分類用コンボボックスのリストを作成します
10行目:小分類用コンポボックスのリストを作成します
11行目:エリア用コンポボックスのリストを作成します
13行目:追加ボタンがクリックされた場合の処理です
15行目:追加画面に遷移します
17行目:参照ボタンがクリックされた場合の処理です
19行目:選択状態のフィールドワークデータのIDを記録します
20行目:参照画面に遷移します
22行目:更新ボタンがクリックされた場合の処理です
24行目:選択状態のフィールドワークデータのIDを記録します
25行目:更新画面に遷移します
27行目:削除ボタンがクリックされた場合の処理です
29行目:選択状態のフィールドワークデータのIDを記録します
30行目:削除画面に遷移します
32行目:初期画面に遷移します
Index.cshtml はフィールドワークデータを操作する初期状態に対応したビューです。このビューには、大分類コンポボックスがあります。大分類コンボボックスを変更すると、小分類コンボボックスは
その大分類に所属する小分類だけに限定される必要があります。
この仕様満たすためには、少なくとも3種類の実装が考えられます。以下、順番に議論したいと思います。最初の実装は、大分類のコンボボックスを変更すると、 Web サーバにサブミットするものです。
Index.cshtml の一部分
1 <script>
2 $(function () {
3 $("#majorId").change(function () {
4 $("#mainForm").submit();
5 });
6 });
7 </script>
8 >body<
略
9 @using (Html.BeginForm("Index", "Materials", FormMethod.Post, new { id = "mainForm", @class = "form-horizontal", @role = "form" }))
10 {
略
11 @Html.DropDownList("majorId", (IEnumerable<SelectListItem>)ViewBag.Majors, " -------------------- ", new { @class = "form-control" })
略
12 }
13 </body>
この処理の中心は3~4行目になります。大分類コンボボックス(majorId を id 属性に持ちます)の値が変更されたときに、 form をサブミットしています。
ここで <form> タグを見ましょう。 method 属性に post を指定しています。そのため、このやり方では POST で Web サーバにサブミットされることになります。
このままでは、サーバ側で対処しない限り Post-Redirect-Get に違反する遷移となります。たとえ違反したとしても、実害は起こりませんから、どう考えるかに依存しそうです。
しかし、次のように GET に変更することができます。これが、2つ目の実装になります。
Index.cshtml の一部分
1 <script>
2 $(function () {
3 $("#majorId").change(function () {
4 $("#mainForm").attr("method", "get");
5 $("#mainForm").submit();
6 });
7 });
8 </script>
4行目で、jQury を使用して、動的に<form> タグの method 属性を get に書き換えています。こうすることで通常は POST でサブミットするけれども、大分類のコンボボックスが変更された場合だけ、 GET によるサブミットに変更できます。
ただし、この方法は GET のパラメータ長に依存するという問題はあります。今回程度のパラメータ長であれば問題ありませんが、余りにも長いパラメータの場合には、
ブラウザや Web サーバなどのどこかで GET のパラメータが打ち切られる可能性があります。
3つ目の実装は、Web サーバにサブミットせずに、 Ajax を使用して小分類のコンボボックス部分だけを非同期更新するというものです。 Web サーバにサブミットすると画面全体を再描画することになりますから、ブラウザの画面がごく短い時間ですがチラツキます。再描画の必要な部分だけ表示を更新すれば、 画面のチラツキを最小限に抑えることができます。この場合のサンプルは省略します。次のような用意が必要になります。
3つの実装方法のうち、どの方法を採用するかを検討する必要があります。フィールドワークデータの追加画面を次に示します。(画像をクリックすると、拡大表示します)
追加状態に入ってくるメソッドの実装を示します。
MaterialController の一部分
1 public ActionResult Create()
2 {
3 Session["NewFieldworkPointMVC"] = "";
4 ReadTable();
5 ViewBag.Majors = majorTable.ToSelectList();
6 ViewBag.Minors = new List<SelectListItem>();
7 ViewBag.Units = unitTable.ToSelectList();
8 Material material = new Material();
9 return View(material);
10 }
1行目:メソッドの定義です
3行目:フィールドワークデータの緯度経度を未入力にします
4~7行目:画面のコンポボックスで使用するリストを用意します
8行目:ビュー用のモデルを生成します
9行目:追加画面用のビューを返します
追加状態から出ていくメソッドの実装を示します。
MaterialController の一部分
1 [HttpPost]
2 public ActionResult Create(string button, FormCollection collection, HttpPostedFileBase[] fileUpload)
3 {
4 Material material = ToMaterial(collection);
5 if (button == null)
6 {
7 material.MinorId = "";
8 ReadTable(material.MajorId);
9 ViewBag.Majors = majorTable.ToSelectList(material.MajorId);
10 ViewBag.Minors = minorTable.ToSelectList();
11 ViewBag.Units = unitTable.ToSelectList(material.UnitId);
12 return View(material);
13 }
14 else if (button.Equals("保 存"))
15 {
16 database = new FieldworkDB();
17 materialTable = new MaterialTable(database);
18 imageTable = new ImageTable(database);
19 int id = -1;
20 string sql = materialTable.AppendSql(material);
21 realAction = (() =>
22 {
23 if ((id = database.ExecuteThenReadId(sql)) < 0)
24 {
25 throw new Exception(database.Result);
26 }
27 string lat = "", lon = "";
28 UploadImages(ref lat, ref lon, id, imageTable, fileUpload);
29 if (string.IsNullOrEmpty(material.Latitude) &&
30 string.IsNullOrEmpty(material.Longitude) &&
31 ! string.IsNullOrEmpty(lat) &&
32 ! string.IsNullOrEmpty(lon))
33 {
34 if (database.Execute(materialTable.UpdateLatLonSql(id, lat, lon)) < 0)
35 {
36 throw new Exception(database.Result);
37 }
38 }
39 });
40 Execute();
41 if (id < 0)
42 {
43 return RedirectToAction("Index");
44 }
45 }
46 else if (button.Equals("クリア"))
47 {
48 return Create();
49 }
50 return RedirectToAction("Index");
51 }
1行目: POST の宣言です
2行目:メソッドの定義です。第一引数でクリックされたボタンを、第二引数で画面項目の値を、第三引数で添付ファイルの情報を受け取ります
4行目:ブラウザの画面からフィールドワークデータを作成します
5行目:ボタンが無効な場合(大分類が変更されてポストバックされたとき)の処理です
7~11行目:画面のコンボボックス用リストを用意します
12行目:追加画面のビューを返します
14行目:保存ボタンがクリックされたときの処理です
16~18行目:データベースの準備です
20行目:追加用の SQL の準備です
21行目:トランザクションの本体を格納するラムダ式を用意します
23行目:フィールドワークデータの追加に失敗した場合です
25行目:トランザクションをロールバックするために例外を投げます
28行目:イメージファイルの情報をデータベースに格納します。エラーがあった場合、その別メソッド中で例外を投げます
29~32行目:フィールドワークデータの緯度経度が空で、イメージファイルに緯度経度が設定されている場合です
34行目:イメージファイルの緯度経度でフィールドワークデータを更新し、その更新に失敗した場合です
36行目:トランザクションをロールバックするために例外を投げます
40行目:トランザクションを開始するメソッド Execute() を呼び出します。その後、 DoAction() メソッドが呼ばれます。その DoAction() メソッドの内部で、21行目で定義したラムダ式を呼び出しています
41行目:トランザクションが失敗した場合、初期状態に遷移します
別のプロセスで使用されているため、XXX にアクセスできません
Visual Studio で動作確認をしていて、タイトルのようなエラーに遭遇することがありました。
添付ファイルからイメージファイルを指定して保存、そのまま次にそのイメージファイルを削除する処理を繰り返すと、このエラーが発生します。
このエラーはファイルを保存したプロセスがそのままファイルハンドルを握ったまま解放せず、次に削除の処理を行ったため発生したと考えています。
つまりこのエラーは、デバッグ環境が一体となった Visual Studio の簡易 IIS を使用しているためと予想します。 Web アプリケーションの性格から、Web サーバはブラウザに
応答を返すと、使用したリソースはすべて解放するのが仕様です。つまり、実運用下では発生しないはずです。
もし、デバッグ時にこのエラーに遭遇した場合は、いったんデバッグ用に起動したブラウザを終了すると回避することができます。 しかしながら、本質的にこの問題を解決する方法は見つかっていません。