2012/09/28

【ASP.Net】個人的な検証機能のおさらい サーバーサイド編【MVC】

 今回は個人的にMVCの検証機能をちょっと復習したいなと思いまして…。長ったらしくなりそうなのでサーバーサイド編とクライアントサイド編で分けて載せます。

 というわけで今回はサーバーサイド編です。サーバーサイドでの基本的な検証はモデルに検証用の属性をつけることで実装します。独自に定義したい場合は「ValidationAttribute」 を継承した属性を作成するか、モデルに「IValidatableObject」インターフェースを実装します。

 とりあえず基本的な属性付けから。前回使用したUserモデルを流用します。…といか元々はこれように作ったモデルだったりする。

Userモデル
public class User
{
 
    public int UserId { get; set; }
 
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    public string Email { get; set; }
 
    public string CompareEmail { get; set; }
 
    public string Tell { get; set; }
        
}

 これに検証を設ける場合、ぱっと見思いつくのは…


  • UserNameの文字列数
  • Emailの正規表現
  • CompareEmailのEmailとの差異
  • Tellの正規表現

 ってとこでしょうか?

Userモデル(検証属性追加)
public class User
{
 
    public int UserId { get; set; }
 
    [StringLength(50)]
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    [EnumDataType(typeof(Genders))]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "アドレスが不正です。")]
    public string Email { get; set; }
 
    [Compare("Email", ErrorMessage = "入力されたアドレスと異なっています。")]
    public string CompareEmail { get; set; }
 
    [RegularExpression(@"[0-9]*$", ErrorMessage = "半角数値のみで入力して下さい。")]
    public string Tell { get; set; }
        
}

 こんな感じになります。各属性の詳細はこちら(System.ComponentModel.DataAnnotations 名前空間)。んで下図が全部引っかかってみた例です。

Createビュー実行結果(Post発生後)

image

 当然ながらサーバーサイドなのでPost発生後に検証されます。実にシンプルで簡単です。次に「ValidationAttribute」は置いておいて「IValidatableObject」を使ってみます。検証内容は「入力されたUserNameが既にデータベースに登録されていないか?」です。

IValidatableObject実装
public class User : IValidatableObject
{
 
    //・・・中略
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        using (var context = new MyContext())
        {
            var overlap = context.Users.Where((p) => p.UserName == this.UserName).FirstOrDefault();
 
            if(overlap !=null)
            {
                yield return new ValidationResult("そのユーザー名は既に使われています。", new[] { "UserName" });
            }
        }
    }
}

 中身は見ての通りです。この検証は必ず実行され、下図の通りに検証されます。

Createビュー実行結果(Post発生後)

image

 これまた期待通りに動作しています。このインターフェースを使えば異なるメンバの比較等の検証を行うことも可能ですね。

 さて、本日はこの辺でお仕舞いで、ValidationAttributeに関してはサーバーサイドでの検証にも利用するのでサーバーサイド編で…。






いきなりPDF Ver.2

2012/09/27

【ASP.Net】ビューで列挙型のドロップダウンリストを表示【MVC】

 EntityFrameworkで列挙型もサポートされることだし、モデルに列挙型使ってドロップダウンリストで表示してみようぜってな話です。早速、モデルと列挙型を用意します。

モデル:User
public class User
{
 
    public int UserId { get; set; }
 
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    public string Email { get; set; }
 
    public string CompareEmail { get; set; }
 
    public string Tell { get; set; }
        
}


列挙型:Genders
public enum Genders
{
    Male = 1,
    Female
}

 Gendersって何やねんって感じですが、分かりやすくするため無理矢理複数形にしました。EntityFramework対応でモデルの方にColumn属性をつけてます。これでアプリ側ではGenders列挙型、データベース側ではint型で扱われるわけです。
 ではモデルを元にコントローラーとビューを作ります。今回は0からではなく「EntityFrameworkを使用した…」テンプレートを使ってちゃっちゃっと作ってしまいましょう(この辺の課程は省略)。ウィザードによってIndexビューやEditビューができあがると思います。ここでやってみて分かったんですがモデルに列挙型を使っているとそのメンバだけテンプレートから弾かれちゃうみたいです。

実際のEditビューの実行結果

image

 

 見ての通りGenderの項目がありません。まぁこれだけの話だったらHtmlヘルパーの「EditorFor()」とか使ってテキストボックスを自力で用意すればいい話なんですが、せっかく列挙型使ってんだからドロップダウンリストにしたらどうなのって思ってしまったわけです。

 ここでやっと本題です。ドロップダウンリスト自体はHtmlヘルパーの「DropdownListFor()」で作れるんですが、どうやって列挙型の値をデータソースして渡すことができるのかってのが問題になってきます。ユーザー定義型つくってList化して渡すってのも出来なくもないですが、ちょっと回りくどいので今回は却下します。まぁ、長々書いても仕方ないので答えから書いちゃいますが以下をビューに追加します。

ビュー(Edit)に追加
<div class="editor-label">
    @Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Gender, new SelectList(Enum.GetValues(Model.Gender.GetType())))
</div>

 簡単に解説すると「Enumクラス」の「GetValues」メソッドで列挙型の値を取得し、リストを渡してます。

追加後の実行結果

image

 ばっちり、列挙型でドロップダウンリストが出来ました。でもこれで調子に乗ってCreateビューも同じようにやってみると…。

Createビューの実行結果

image

 NullだよNullってわけです。モデルがインスタンス化されてない模様です。これの原因はコントローラー側も見れば一目瞭然です。

Createメソッド
public ActionResult Create()
{
    return View();
}

 うーん、まさに何もしてないわけです。ここで頑張ってモデル側がNullでもどうにかしちゃう拡張ヘルパーを作ってもいいかもしれませんが、ここは手っ取り早くCreateメソッドの方に手を加えて解決にしちゃいます。

Createメソッドの修正
public ActionResult Create()
{
    var model = new User();
 
    return View(model);
}

 当たり前ですがこれでモデルはインスタンス化されビューに渡されます。一応、実行結果を貼っておきます。

修正後の実行結果

image

 以上で、今回は終了です。列挙型についてはもっと応用が効きそうな気がしますが、まだまだ実力不足でアイデアが…。






サンワダイレクト リングマウス

2012/09/26

【ASP.Net】MVC4で何か作ってみる その6(最終回)【MVC】

 前回の続きです。

 前回はjqueryのDataTablesプラグインで簡単に並べ替えとフィルターを実装することができました。今回はリストをドラッグアンドドロップで並べ替えしてみたいと思います。

 いやぁー、ぶっちゃけ今回が一番苦労しました。ドラッグアンドドロップで並べ替えたまでは良かったんですが、その後のデータベースへの保存にかなり手こずりました。正直、諦めようかと思ったくらいでしたが、何とか出来てよかったです。

 では早速、最初のモデル作成とコントローラー作成は今まで同じなのでちゃちゃっとやっちゃいます。

社員順序モデル
public class EmployeeSort
{
 
    /// <summary>
    /// 社員ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// ソート順
    /// </summary>
    public int SortNumber { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
}

Indexメソッド(コントローラー)
public ActionResult Index()
{
    var resultList = new List<EmployeeSort>();
 
    using (var context = new MvcTestContext())
    {
        var items = from p in context.Employees
                    orderby p.SortNumber
                    select p;
 
        foreach (var item in items)
        {
            resultList.Add(new EmployeeSort
                {
                    Id= item.EmployeeId,
                    SortNumber = (int)item.SortNumber,
                    EmployeeName = item.EmployeeName
                });
        }
    }
 
    return View(resultList);
}

 ここまでは特に問題ないでしょう。次にビューですが、今回はリスト上でのドラッグアンドドロップで並べ替えを実現するためにjqueryUIの「Sortableプラグイン」を利用します。細かな使い方はこちらを見てもらうとして、ビューで使用する準備をしましょう。

Sortableプラグイン使用準備(ビューに追加)
<script src="~/Scripts/jquery-ui-1.8.23.min.js"></script>
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#sortable').sortable();
 
    });
</script>

 ついでだったんでthemeも突っ込んどきました。これで<li>タグをドラッグアンドドロップで並べ替えできます。後は並べ替えた後、どう保存するかってとこなんですが、とりあえず単純に保存ボタンを押したら並べ替えた順に社員テーブルのIdを受け取ってテーブルを更新しちゃえばよいかと考えました。というわけで一端ビューは置いといて、Post時のIndexメソッドです。

Post時のIndexメソッド
[HttpPost]
public ActionResult Index(int[] id)
{
    using (var context = new MvcTestContext())
    {
        for (var i = 0; i < id.Length; i++)
        {
            var overlap = context.Employees.Find(id[i]);
 
            if (overlap != null)
            {
                overlap.SortNumber = i + 1;
            }
        }
 
        context.SaveChanges();
 
        return RedirectToAction("Index");
    }
}

 コードはいたってシンプルです。これで行けるはず。…とこの時点では余裕で考えてて、何気なしにビューの続きも作成しました。

ビュー全景
@model IEnumerable<MVCTestProject.Models.EmployeeSort>
 
@{
    ViewBag.Title = "Sort";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<script src="~/Scripts/jquery-ui-1.8.23.min.js"></script>
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#sortable').sortable();
 
    });
</script>
 
<h2>並べ替え</h2>
 
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.HiddenFor(model => item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
@using (Html.BeginForm()) {
        <input type="submit" value="保存" />
}

 見ての通りモデルをリスト化し、最後の<input>タグでPostが発生するようにしています。これで「おぉー、出来た出来た」と早速、実行してみました。

実行結果(並び替え)
image

 ちょっと地味ですがプラグインは正常に動作し、ドラッグアンドドロップで並べ替えが効いています。次は保存ボタンをクリックしてみます。

実行結果(保存)
image

 はい、こけました。お久しぶりの「NullReferenceException」さんです。引数の「id」がNullだとおっしゃってます。どうやらビューからコントローラーに引数が渡ってない模様。…ここから長い旅路が始まりました。

 何故でしょう?「配列は渡せません」なんてことはないはずなんですが…。基本的にMVCではコントローラに引数を渡す際に、自動的に引数と同名のデータを収集・変換してくれてるはずです。それが渡ってないってことは収集されてないのか?ってな具合に試行錯誤しました。まぁ、1から試したこと書いてたらキリが無いので、途中経過はすっとばしますが、1つ目の間違いは「Html.BeginForm()」の位置でした。この辺、今までPHPとか全然構ってない人間なのでピンときてませんでしたが、対象となるデータが<form>タグに入ってなかったら送れないみたいです。ってこう書くと当たり前じゃねーかって気もしますね…。では修正です。

ビューを修正(部分抜粋)
@using (Html.BeginForm()) {
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.HiddenFor(model => item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
    <input type="submit" value="保存" />
}

 「Html.BigenForm()」を<ul>タグの前に持ってきました。これでどうだって感じで実行してみます。

実行結果
image

 …駄目でした。でも「Html.BeginForm()」位置の修正に関しては間違ってないはずなのでこのままにしときます。それにしても何で「id」が収集されんのだろうか…とPage Inspectorの<li>タグをじっくり見直してみました。

Page Inspector(部分抜粋)
<li class="ui-state-default" style="padding: 5px; width: 200px;">
<input name="item.Id" id="item_Id" type="hidden" data-val-required="Id フィールドが必要です。" data-val-number="フィールド Id には数字を指定してください。" data-val="true" value="83"></input>
</input>
        1 : 0子
</li>

 「!!」っと、ここでようやく気がつきました。…今まで自分は何を見てたんでしょう。<input>タグのname属性にご注目。「name=”item.Id”」となってるんですよ。上にも書きましたがMVCはコントローラーの引数名でデータを収集するってことは「item.Id ≠ id」ってなわけで…。自分、name属性なんて指定せんでも勝手にやってくれるだろうと思ってたんです(ビュー側のモデルがIEnumerableやIListでなく単体の場合はそのままでOKっぽいです)…。これに気がつくのに数時間…。

 何はともあれ原因っぽいのは分かったんで、ビューをまたもや修正します。

ビューの再修正(部分抜粋)
@using (Html.BeginForm()) {
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.Hidden("id", item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
    <input type="submit" value="保存" />
}

 ちょっと違いが分かりにくいですが、「@Html.HiddenFor」から「@Html.Hidden」に変更し、「id」という名前をつけてます。最初はname属性の付け方が分からなくてまたもや時間を喰いました。「@Html.HIddenFor」の方、MSDN(InputExtensions.HiddenFor(TModel, TProperty) メソッド (HtmlHelper(TModel), Expression(Func(TModel, TProperty)), IDictionary(String, Object)) (System.Web.Mvc.Html))にはHtml属性を定義できるとありますが、自分はどうしてもname属性をつけることができませんでした(他の属性はできました)。

実行結果
image

 ちょっと分かりにくいですが、並べ替えを保存に成功した後です。結果自体は地味でしたがようやくドラッグアンドドロップで並べ替えを実装することができました。…わりとまじめにしんどかったです。

 今回で「MVC4で何か作ってみる」は終了です。作ってみた感想としてはやっぱりまだまだ関係資料が少ないなぁと感じました。MSDNもまだほとんどがMVC3のヘルプを見ろって感じです(ぶっちゃけ見ても分かりづらいですが)。それでも個人的にはRazorの方がASPXより好みだし、jqueryとの連携もやっぱり面白いです。今回は見た目の方はやっつけで済ましましたが、もっとリッチなインターフェースを作成できそうな気がします(jqueryの勉強も必要ですが…)。








デル株式会社

2012/09/25

【Asp.Net】MVC4で何か作ってみる その5【MVC】

 前回の続きです。

 前回は追加・更新・削除とメインとなる機能を作成しました。今回からは見た目の部分をやっていこうかと思います。まずは今後jqueryやcssを使うにあたってレイアウトページがあった方が定義をまとめたりしやすそうなのでレイアウトページを作成します。

 プロジェクトにレイアウトページを追加します。

レイアウトページの追加
 2012092301

 この名前の_(アンダーバー)って何かルール的なもんがあるんでしょうか?とりあえず試しにレイアウトにヘッダーとフッターを設け、ついでにCSSも適用してみます。

レイアウト
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="~/Content/css/default.css" rel="stylesheet" type="text/css" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <header id="header">
        <h1>MVCTestProject</h1>
    </header>
    <div id="main">
        @RenderBody()
    </div>
    <footer id="footer">
        <address>
            これはMVCTestProjectです
        </address>
    </footer>
</body>
</html>

default.css
#header h1 {
    text-align:center;
    background-color: #333;
    color: #FFF;
}

#footer address {
    text-align:center;
    background-color: #333;
    color: #FFF;
}

#main h2 {
    color: #00F;
}

 次にHomeのIndexビューにレイアウト適用してみます。

Indexビュー(Home)
@{
    ViewBag.Title = "Index";
    
    <!-- レイアウトの適用 下記を追加 -->
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<h2>Index</h2>

実行結果

2012092302

 レイアウト、CSSともに適用されてますね。それにしてもVS2012の「Page Inspector」は本当に便利です。結果やデザイン確認するのにいちいちブラウザ立ち上げてページ移動してってのが軽減されるのは大きいです。

 個人的に多少は満足出来たので次はいよいよ課題(その1を参照)にあげてたjqueryでテーブルをあれこれにチャレンジしてみます。まずはレイアウトからjqueryを呼び出せるようにします。

レイアウト
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="~/Content/css/default.css" rel="stylesheet" type="text/css" />
    
    <!-- jqueryへの参照を追加 -->
    <script src="~/Scripts/jquery-1.8.1.min.js" type="text/javascript"></script>
    
    <title>@ViewBag.Title</title>
</head>
<body>
    <header id="header">
        <h1>MVCTestProject</h1>
    </header>
    <div id="main">
        @RenderBody()
    </div>
    <footer id="footer">
        <address>
            これはMVCTestProjectです
        </address>
    </footer>
</body>
</html>


 次はテーブル操作系のプラグインに頼ろうかな…といろいろ探してみたんですが、すごくちょうどいいのがありました。「DataTables (table plug-in for jQuery)」です。何か見た感じこれ1つでほとんど解決しちゃいそうです。

 公式からzipをダウンロードし、展開したフォルダ内で必要そうなのだけ抜き出して社員リストのIndexビューにスクリプトとともに設置します。抜き出したファイルは「jquery.dataTables.min.js」「demo_table_jui.css」です。よく分かんなかったり、抜き出すのが面倒な方はmediaというフォルダをまるっと持ってくればOKです。あと、jqueryUIとも連携可能のでjqueryUIのテーマも持ってきました。スクリプトではパラメータの部分で更新列と削除列の検索と並べ替えを禁止してます。

Indexビュー(EmployeeList)
@model IEnumerable<MVCTestProject.Models.EmployeeList>
 
@{
    ViewBag.Title = "社員リスト";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<script src="~/Scripts/jquery.dataTables.min.js"></script>
<link href="~/Content/css/demo_table_jui.css" rel="stylesheet" />
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#employeelist').dataTable({
            "bJQueryUI": true,
            "sPaginationType": "full_numbers",
            "aoColumnDefs": [
                { "bSearchable": false, "bSortable": false, "aTargets": [2] },
                { "bSearchable": false, "bSortable": false, "aTargets": [3] }
            ]
        });
    });
</script>
 
<h2>社員リスト</h2>
 
<div>
    @Html.ActionLink("追加", "Update", -1)
</div>
 
<table id="employeelist" class="display">
    <thead>
        <tr>
            <th>
                社員名         
            </th>
            <th>
                配属先
            </th>
            <th>
                更新
            </th>
            <th>
                削除
            </th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model)
{
        <tr>
            <td>
                @Html.DisplayFor(modelitem => item.EmployeeName)
            </td>
            <td style="text-align: center;">
                @Html.DisplayFor(modelitem => item.AssignName)
            </td>
            <td style="text-align: center;">
                @Html.ActionLink("更新", "Update", new { id = item.Id })
            </td>
            <td style="text-align: center;">
                @Html.ActionLink("削除", "Delete", new { id = item.Id })
            </td>
        </tr>       
}
    </tbody>
</table>

 テーブルに<thead>タグと<tbody>タグも追加しています。どうやらこれがないとプラグインが動作しないみたいです。他のプラグインでも動かないケースがあるので覚えておいた方が良さそうです。これで準備OK。さっそく実行してみます。

実行結果

image

 おぉー、中々スマートになったじゃないですか?ちょっと英語の部分を日本語に変えたい気もしますが、それはまた今度で…。

 今回はこれまで。次回はドラッグアンドドロップで並べ替え編です。






HP Directplus オンラインストア

2012/09/22

【ASP.Net】MVC4で何か作ってみる その4【MVC】

 前回の続きです。

 前回は自作の社員リストモデル(EmployeeList)をビューで表示しました。今回は表示したデータを利用し、社員テーブルの追加・更新・削除の機能を実装してみましょう。

 まずは面倒な方からいきます。追加・更新です。これらは別々に作っても良いのですがページ自体が同じイメージになるので一緒にしちゃいます。処理の流れとしてはIndexビューのActionLinkからIdを受け取り、Idが「-1」等の存在しない値だったら新規用を、それ以外なら社員テーブルからデータを抽出して更新用を表示、保存時は既存Idだったら更新、なければ新規として扱うって感じでどうでしょう?

 あとせっかくなんで配属先IDの入力はドロップダウンリストにしてみようかと思います。…なんて簡単に考えてたんですが、実際やってみるとちょっとモデルをよく考えないといけなそうです。MVCでないASP.Net等だったら別にドロップダウンリストのソースだけ配属先テーブルから取ってきてバインドなりすりゃすんだわけなんですが、MVCはModelViewControllerなわけでモデルに無いものは扱えませんよっていうか扱っちゃいけませんよってのが基本ですよね。当然、配属先テーブルは社員テーブルには無いわけです。なので追加・更新用に新たに配属先テーブルを持つモデルを作成します。

 追記:別の方法もありました(ViewBagを追加ってDropDownListを作ってみる

社員編集用モデル
public class EmployeeUpdate
{
 
    /// <summary>
    /// 社員ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
    /// <summary>
    /// 配属先ID
    /// </summary>
    public int AssignId { get; set; }
 
    /// <summary>
    /// 配属先テーブルリスト
    /// </summary>
    public List<Assign> Assigns { get; set; }
 
}

 モデルは見ての通り、社員テーブルのデータと配属先テーブルのリストで作られています。それではこれを使ってコントローラーに表示用メソッドを追加します。

Updateメソッド
public ActionResult Update(int id = -1)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(id);
 
        if (overlap == null)
        {
            var result = new EmployeeUpdate
                {
                    Assigns = context.Assigns.ToList()
                };
 
            return View(result);
        }
        else
        {
            var result = new EmployeeUpdate
                {
                    Id = overlap.EmployeeId,
                    EmployeeName = overlap.EmployeeName,
                    AssignId = overlap.AssignId,
                    Assigns = context.Assigns.ToList()
                };
 
            return View(result);
        }
    }
}

 いまいちスマートじゃないけど中身はシンプルなメソッドなので解説なしで、一端これを元にビューを作成しますか。

Updateビュー
@model MVCTestProject.Models.EmployeeUpdate
 
@{
    ViewBag.Title = "Update";
}
 
@if (Model.Id != 0)
{
    <h2>更新</h2>       
}else
{
    <h2>追加</h2>
}
 
@using (Html.BeginForm())
{
    <!-- IdはPost時に利用する必要があるので隠しときます -->
    @Html.HiddenFor(model => model.Id)
 
    <div class="editor-label">社員名</div>
    <div class="editor-field">
        @Html.EditorFor(model => model.EmployeeName)
    </div>
    <div class="editor-label">配属先</div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.AssignId, new SelectList(Model.Assigns,"AssignId", "AssignName"))
    </div>
        
    <p>
        <input type="submit" value="保存" />
    </p>
}
 
<div>
    @Html.ActionLink("戻る", "Index")
</div>

 ドロップダウンリストの書き方、資料がなさ過ぎて苦労しました。MSDN(SelectExtensions.DropDownListFor(TModel, TProperty) メソッド (HtmlHelper(TModel), Expression(Func(TModel, TProperty)), IEnumerable(SelectListItem)) (System.Web.Mvc.Html))分かりにくいです。「@Html.DropDownListFor(モデルの値, 使用するリスト(値フィールド, 表示フィールド))」って感じです。これで保存ボタンをクリックするとPostが発生するようになるので次はコントローラーに戻り、Post時のメソッドを書きます。

Post時のUpdateメソッド
[HttpPost]
public ActionResult Update(EmployeeUpdate employeeUpdate)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(employeeUpdate.Id);
 
        if (overlap == null)
        {
            context.Employees.Add(new Employee
                {
                    EmployeeName = employeeUpdate.EmployeeName,
                    AssignId = employeeUpdate.AssignId,
                    SortNumber = (from p in context.Employees
                                    select p.SortNumber).Max() + 1
                });
        }
        else
        {
            overlap.EmployeeName = employeeUpdate.EmployeeName;
            overlap.AssignId = employeeUpdate.AssignId;
        }
 
        context.SaveChanges();
        return RedirectToAction("Index");
    }
}

 [HttpPost]を忘れないよう。これないとPost時に動いてくれません。あれPost時にモデルって渡したっけ?って感じですがどうやら自動的に渡してくれてる模様です。処理自体は最初に考えてたとおりに処理しています。IDの重複があれば更新、なければ追加です。んで最終的にIndexに移動します。深い意味はありませんがSortNumberフィールドは追加時は最大値を取るようにしてます。

 以下、実行結果です。

実行結果
追加

更新


 追加時、更新時でURLのパラメータの違うのが分かりますね。さて追加・更新ができたので次は削除です。ちょっと疲れてきましたが一気にやってしまいましょう。

社員削除用モデル
public class EmployeeDelete
{
 
    /// <summary>
    /// ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
    /// <summary>
    /// 配属先名
    /// </summary>
    public string AssignName { get; set; }
 
}

 Indexで使ったEmployeeListモデルと内容が同じになっちゃいましたが一応使い分けが効くように別にモデルを作りました。

Deleteメソッド
public ActionResult Delete(int id = -1)
{
    using (var context = new MvcTestContext())
    {
        var overlap = (from p in context.Employees
                        join g in context.Assigns
                        on p.AssignId equals g.AssignId into gg
                        from ggg in gg.DefaultIfEmpty()
                        where p.EmployeeId == id
                        select new
                        {
                            p.EmployeeId,
                            p.EmployeeName,
                            AssignName = (ggg != null ? ggg.AssignName : string.Empty)
                        }).FirstOrDefault();
 
        if (overlap == null)
        {
            return HttpNotFound();
        }
        else
        {
            var result = new EmployeeDelete
                {
                    Id = overlap.EmployeeId,
                    EmployeeName = overlap.EmployeeName,
                    AssignName = overlap.AssignName
                };
 
            return View(result);
        }
    }
}

 対象IDが見つからなかった場合、「HttpNotFund」を呼び出し、オブジェクトが見つからなかったぞと通知するようにしてます。

Deleteメソッド(Post時)
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(id);
        context.Employees.Remove(overlap);
        context.SaveChanges();
        return RedirectToAction("Index");
    }
}

 前述のDeleteメソッドとメソッド名とパラメータが被っちゃうので別名にし、ActionName属性でアクション(”Delete")を指定してます。

Deleteビュー
@model MVCTestProject.Models.EmployeeDelete
 
@{
    ViewBag.Title = "Delete";
}
 
<h2>このデータを削除してもよろしいですか?</h2>
 
<fieldset>
    <legend>社員データ</legend>
    <div class="display-label">社員名</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.EmployeeName)
    </div>
    <div class="display-label">配属先</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.AssignName)
    </div>
</fieldset>
@using (Html.BeginForm()) {
    <input type="submit" value="Delete" />
    @Html.ActionLink("戻る", "Index")
}

 これは特に言うことも無いでしょう。次に実行結果です。

実行結果

 あー、疲れた。とりあえずは思った通りにできたので今回はここまでにします。それにしても予想以上にシンプルな流れで作れるので素直にMVCに感動しました。






1616 / arita japan

2012/09/21

【ASP.Net】MVC4で何か作ってみる その3【MVC】

 前回の続きです。

 前回はCodeFirstでデータベースを作成し、テンプレートを使ってEntityFrameworkと連動したページを作成しました。今回はテンプレートを使わず、社員テーブルと配属先テーブルを結合したモデルを表示し、それを元にデータを編集するページを作成してみたいと思います。
 
 まずはモデルを定義します。
社員リスト
public class EmployeeList
{
 
    /// <summary>
    /// ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }

  /// <summary>
    /// 配属先名
    /// </summary>
    public string AssignName { get; set; }

}

 次はコントローラーを作成します。モデルを指定しない空のコントローラーです。

空のコントローラーの作成

2012092101

 では早速、作成したクラスのIndexメソッドをいじくってみます。

Indexメソッド
public ActionResult Index()
{
    var resultList = new List<EmployeeList>();
            
    using(var context = new MvcTestContext())
    {
        var items = from p in context.Employees
                    join g in context.Assigns
                    on p.AssignId equals g.AssignId into gg 
                    from ggg in gg.DefaultIfEmpty()
                    orderby p.SortNumber ascending
                    select new
                    {
                        p.EmployeeId,
                        p.EmployeeName,
                        AssignName = (ggg != null ? ggg.AssignName:string.Empty)
                    };
        
        //どうでもいいけどParallel使うと順序が狂うよ!
    foreach (var item in items)
        {
            resultList.Add(new EmployeeList
            {
                Id = item.EmployeeId,
                EmployeeName = item.EmployeeName,
                AssignName = item.AssignName
            });
        }
    }
 
    return View(resultList);
}

 コード自体は単純なので特に解説は不要でしょう。続けて、ビューも作ります。ビューもせっかくですから空のビューを作ります。

ビューの作成

2012092102

 ではRazorに挑戦します。挑戦とは言っても前回作成したEntityFrameworkと連動したテンプレートを見本にすれば特に難しいこともないです。モデルを宣言し、受けたデータをぶん回してデーブルで表示するだけです。

Index.cshtml
@model IEnumerable<MVCTestProject.Models.EmployeeList>
 
@{
    ViewBag.Title = "社員リスト";
}
 
<h2>社員リスト</h2>
 
<table>
    <tr>
        <th>
            社員名         
        </th>
        <th>
            配属先
        </th>
    </tr>
 
@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelitem => item.EmployeeName)
        </td>
        <td>
            @Html.DisplayFor(modelitem => item.AssignName)
        </td>
    </tr>       
}
</table>

 ここで一端ブラウザで表示してみます。

実行結果

2012092103

 どうやら希望通りの結果が表示されてるみたいです(IDは表示する必要ないので省きました。)。

 内容は簡単だったんですが、ちょっと長くなりすぎたのでこの辺で切ります。編集機能は次回にします。






ソフマップ・ドットコム

2012/09/20

【ASP.Net】MVC4で何か作ってみる その2【MVC】

 前回の続き。

 今回はCodeFirstでデータベースを準備します。さて早速ひとつ問題が…。MVC上のModelとEntityFramework上のModelを同一視しても良いものか?当然ながら連携面を考えた場合、同一のModelとして扱ってしまった方が楽なのは間違いなりませんし、チャートリアル等でもそのように扱われています。じゃあそうりゃいいじゃんって話なんですが、個人的にMVCとEntityFrameworkのどちらからしか使わない可能性のあるModelを作ることに抵抗があったりするわけで…。とりあえず今回はプロジェクトのModelsフォルダにDbフォルダを設け、EntityFrameworkのModelはそちらに書くことにします。

 さっそくModelの定義です。

社員テーブル
using System.ComponentModel.DataAnnotations;
 
namespace MVCTestProject.Models.Db
{
    /// <summary>
    /// 社員テーブル
    /// </summary>
    public class Employee
    {
 
        /// <summary>
        /// ID
        /// </summary>
        public int EmployeeId { get; set; }
 
        /// <summary>
        /// 社員名
        /// </summary>
        [MaxLength(50)]
        public string EmployeeName { get; set; }
 
        /// <summary>
        /// 配属先ID
        /// </summary>
        public int AssignId { get; set; }
 
        /// <summary>
        /// ソート順
        /// </summary>
        public decimal SortNumber { get; set; }
 
    }
}

配属先テーブル
using System.ComponentModel.DataAnnotations;
 
namespace MVCTestProject.Models.Db
{
    /// <summary>
    /// 配属先テーブル
    /// </summary>
    public class Assign
    {
 
        /// <summary>
        /// ID
        /// </summary>
        public int AssignId { get; set; }
 
        /// <summary>
        /// 配属先名
        /// </summary>
        [MaxLength(50)]
        public string AssignName { get; set; }
 
    }
}

コンテキスト
using System.Data.Entity;
using MVCTestProject.Models.Db;
 
namespace MVCTestProject
{
    /// <summary>
    /// データベースコンテキスト
    /// </summary>
    public class MvcTestContext : DbContext
    {
 
        /// <summary>
        /// MvcTestDbという名前でDbを作成
        /// </summary>
        public MvcTestContext()
            : base("MvcTestDb")
        { }
 
        public DbSet<Employee> Employees { get; set; }
 
        public DbSet<Assign> Assigns { get; set; }
 
    }
}

 データベースはとりあえずローカルに置くのでWeb.configは放置。変更が必要であればConnectionStringsタブなりEntityFrameworkタブなりに追記してください。

 あと、テストデータを用意したいのでConfigurationクラスを追加します。分からなければこの辺を参照してください。

ConfigurationクラスのSeedメソッド
protected override void Seed(MVCTestProject.MvcTestContext context)
{
    var employeeList = new List<Employee>();
 
    Parallel.For(0, 101, (n) =>
        {
            employeeList.Add(new Employee
                {
                    EmployeeName = (n % 2 != 0 ? n + "男" : n + "子"),
                    AssignId = (n % 2 != 0 ? 1 : 2),
                    SortNumber = n
                });
        });
 
    var target = employeeList.ToArray();
    context.Employees.AddOrUpdate(target);
 
    context.Assigns.AddOrUpdate(
        new Assign
            {
                AssignName = "A"
            },
        new Assign
            {
                AssignName = "B"
            });
}

 Emloyee型のリストを作成した後、わざわざ変数に突っ込んでるのは実行時にAddOrUpdateメソッドに「非静的メソッドはターゲットがうんぬん」と叱られるからです。これで下準備が完成です。

 今後、自力でビューを作っていくにあたってMVCがデータベースに対してどうゆう動作をするか知りたいと思います。なのでとりあえずコントローラーの作成時にEntityFrameworkと連携したテンプレートを選択してみます。

テンプレートやらモデル等を選択

2012092002

作成されたコントローラー
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCTestProject.Models.Db;
 
namespace MVCTestProject.Controllers
{
    public class EmployeeController : Controller
    {
        private MvcTestContext db = new MvcTestContext();
 
        //
        // GET: /Employee/
 
        public ActionResult Index()
        {
            return View(db.Employees.ToList());
        }
 
        //
        // GET: /Employee/Details/5
 
        public ActionResult Details(int id = 0)
        {
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        //
        // GET: /Employee/Create
 
        public ActionResult Create()
        {
            return View();
        }
 
        //
        // POST: /Employee/Create
 
        [HttpPost]
        public ActionResult Create(Employee employee)
        {
            if (ModelState.IsValid)
            {
                db.Employees.Add(employee);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
 
            return View(employee);
        }
 
        //
        // GET: /Employee/Edit/5
 
        public ActionResult Edit(int id = 0)
        {
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        //
        // POST: /Employee/Edit/5
 
        [HttpPost]
        public ActionResult Edit(Employee employee)
        {
            if (ModelState.IsValid)
            {
                db.Entry(employee).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(employee);
        }
 
        //
        // GET: /Employee/Delete/5
 
        public ActionResult Delete(int id = 0)
        {
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        //
        // POST: /Employee/Delete/5
 
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            Employee employee = db.Employees.Find(id);
            db.Employees.Remove(employee);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

 あー、なんだか長くなっちゃいましたが、何となくはデータベースに対するアクセスの仕方が見えたのではないでしょうか?データベース→データ取得→ビューに渡すって単純な流れですね。

 一応、ビュー側も載せておきます。Razorは慣れないと違和感ありまくりですね。

Index.cshtml
@model IEnumerable<MVCTestProject.Models.Db.Employee>
 
@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
 
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.AssignId)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.SortNumber)
        </th>
        <th></th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.EmployeeName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.AssignId)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.SortNumber)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.EmployeeId }) |
            @Html.ActionLink("Details", "Details", new { id=item.EmployeeId }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.EmployeeId })
        </td>
    </tr>
}
 
</table>

 それでは今回はこの辺で…。次回は課題である結合したデータをビューに渡したり、それを利用して更新したりする予定です。






ビックカメラ.com

2012/09/19

【ASP.Net】MVC4で何か作ってみる その1【MVC】

 さて、開発使用していた案件がMVCで作れるんじゃないかと思い始めた今日この頃。というかもうかなり本気でそっちで作ってやろうかと思い始めてるわけで…。
 とりあえずMVCって2の頃からまともに構ってないので試しに4で何か作ってみようかと思ったわけです。んな訳で条件をば…。

前提条件
  • テンプレートは使わず空のプロジェクトから始める。
  • CodeFirstとの連携。ただしビューに表示するのはテーブルを結合したデータを使う。更新、削除はそのデータを利用し行う。要は直接的にテーブルを編集しないってこと。
  • jqueryかjavascriptを利用してテーブルにフィルターとソート機能を設ける。
  • jqueryを利用してリストをドラックアンドドロップで並べ替え、それをデータベースへ反映する。

 なんか半分jqueryじゃんって感じもするけど、まぁこんな感じでやってきます。じゃぁ次はデータベース使うんでその定義。

テーブル定義
社員テーブル
フィールド名 主キー 外部キー
EmployeeId int
EmployeeName nvarchar(50)
AssingId int
SortNumber decimal

配属先テーブル
フィールド名 主キー 外部キー
AssignId int
AssignName nvarchar(50)

 社員テーブルには100行、配属先テーブルに3行くらいデータを準備する予定です。
 では早速プロジェクトを作成します。今回はせっかくですのでVisualStudioExpress2012forWebも試してみます。Expressって言ってますが機能比較を見てみるとWebに関してはこれで十分な気がする…。(Visual Studio と Visual Studio Express 2012 for Web の比較

新しいプロジェクトの作成
2012091901

空のMVCテンプレートを選択
2012091902

 これでマスターページ等の用意されてないMVC開発がスタートです。実際始める前にNuGetでjqueryやらEntityFrameworkの更新やら入れておきます。

NuGet
2012091903

 それでは早速コントローラーとビューを作ってみます。まぁ初めてなんで「Hello World」しときます。一応ですがコントローラーはソリューションエクスプローラーから、ビューはコントローラーのコード上から右クリックで追加できます。

コントローラーの追加
2012091904
2012091905

ビューの追加
2012091906
2012091907

 これを実際に実行してみると以下のようになります。Homeって初期設定で呼び出されるみたいですね。

実行結果
2012091908

 長くなってきたので今回はこれまで。次回はCodeFristをかませていく予定。


訂正…テーブルの各Idフィールドがlong型になってましたがint型に修正してます。きっとこないだまでAccessの記事書いてたせいです。きっとそうです。




B's 動画レコーダー 2

2012/09/18

【雑記】本日の箇条書き

1.MVC4のチャートリアル(Intro to ASP.NET MVC 4 : Official Microsoft Site)をやってみる。今のところ英語オンリーの模様。APIはたぶん手を出さない、と言うかプロジェクト的に使わんと思われる。

2.IFTTTってウェブサービスが便利だと知る。多数のサイト(BloggerやTwitterなど)と連動することができ、いろいろなトリガーに対してといろいろな動作を自分で簡単に設定することができる。自分は「ブログに新しい投稿を行ったら自動で報告ツイートする」のと、「新しいフォロワーに自動でお礼のメッセージを送信する」って作ってみた。

3.他部署の子の結婚式の掲示用ポスターを半日かけて作成。もっと手の込んだのを作るのでデザイン料をください。





ホームページ V3 (468x60)

2012/09/15

【雑記】OSを再インストールした話

 今使ってるPCのOS(Windows7)を再インストールしたったってだけなんですけどね。理由は何となく開発環境周りを整理したかったんです。最初はいろいろ試しに入れてたSDKとか消しちゃいたいなぁと思ってプログラムの削除を地味にやってたんですけど、いまいちVisualStudioとかに元から入ってたものとの区別がつかなくなってきちゃって…。

 それにしても何か年々神経質になってきちゃって…。別に全然問題ないのに最初のドライバ入れる順番を自分の思ってた順番と間違えちゃって最初からやり直したり、SP出てんの忘れてて0からアップデートしてたのが気に入らなくて最初からやり直したり…。こりゃ終わらないはずです。まぁ、自分の使うものはこだわってセットアップしたいよねってことで。

 それにしても再インストールして初めて気がついたんですが、GoogleChromeって色んな個人の情報をどうやらクラウドに保存してるみたいなんですよね。再インストールしてGoogleにログインしただけでお気に入りやら色んなサイトのログイン名やパスワードまで元通りに…。パ、パスワード…キャッシュじゃなくてクラウドに保存されてるとは…。もうChrome使ってるときにパスワード記憶させるのは止めとこうと思った今日この頃でした。


HP Directplus オンラインストア

2012/09/13

【雑記】本日の箇条書き

1.VisualStudio2012が気になる

 新しいもの好きなので出来るだけ気にしないようにしてたんですが…。
 たまたま現在開発中のものがWinForm+WCFじゃなくてAsp.NetMVCで実現できるんじゃないかって思い始めてMVCって今3.0が最新だよねって思ってたらいつの間にか4.0が出てて…って感じで辿っていくうちにVS2012が正式にリリースされてるのに気がついてしまったわけで。
 それにしてもこれアップグレード版は無しですか?
 (http://www.microsoft.com/visualstudio/jpn/)


2.本屋が遠い

 ちょっと気になってる参考書があるんですが、どうやら片道2時間の本屋にあるらしい。チェーン店なんで近場に取り寄せが出来なくもないんだけど取り寄せちゃったら中身がハズレでも買わなきゃいけませんよね?というかそれならAmazonでポチるし…。
 行くべきか、行かざるべきか…ってもう2日くらい悩み中。


3.翻訳された書籍の値段

 なんで翻訳された書籍って元のより1000円近く高かったりするの?っていう愚痴。ケチって原本買って自分の英語力の無さに絶望した今日この頃。 

2012/09/12

【Access】VBA無しでどこまでできるの?最終回

 前回(その4)の続きです。

 最近、長くなりがちなのでさくさくやっちゃうぞーってことでレポート作成です。クエリはその3の時にほとんど出来てるんでちょこっと抽出条件をフォームを参照するように修正してやればOKです。月別の抽出条件「Between [抽出開始] And [抽出終了]」の部分はメニューフォームに入力された年と月を条件にするため、DateSerial関数を用いて抽出開始と抽出終了を割り出します。式は「Between DateSerial([Forms]![MenuForm]![txtReportYear],[Forms]![MenuForm]![txtReportMonth],1) And DateSerial([Forms]![MenuForm]![txtReportYear],[Forms]![MenuForm]![txtReportMonth]+1,1)-1」となります。「txtReportYear」と「txtReportMonth」はメニューフォームに設置した年入力テキストと月入力テキストの名前です。

 さて、ウィザードで一発じゃぁって感じで行こうと思ってましたが実際やってみると何かごちゃっとしてて気にくわなかったので手作業でシンプルに作ります。


日別レポート(商品ベース)

デザインビュー

2012091201

印刷プレビュー

2012091202


日別レポート(メーカーベース)

デザインビュー

2012091203

印刷プレビュー

2012091204


月別レポート(商品ベース)

デザインビュー

2012091205

印刷プレビュー

2012091206


月別レポート(メーカーベース)

デザインビュー

2012091207

印刷プレビュー

2012091208


 特に手をかけてないんで一気に出しちゃいました。メーカーベースの方は若干寂しい気もしますがまぁ良しとしましょう。どうでも良いことですがレポートの罫線ズレが気になる人は手で上位置、左位置を入力したらちょっとはましになりますよ。

 もうマクロもやっちゃいますか。

 まずはおなじみ(?)AutoExecです。マクロ名に「AutoExec」とつけると起動時に自動実行されます。これにメニューフォームの起動を設定します。あとは各フォームの起動、各レポートの起動、在庫計算のクエリの実行を作成すればOKです。「マクロは簡単なやつしか駄目!」ってお約束なのでどれもオブジェクト名と表示形式を指定しただけにしています(なので画像等は載せません)。あとは作成したマクロをメニューフォームの各ボタンに割り当てるだけです。

割り当て中の図

2012091209


 これでついに完成です。…終わり方があっさりしすぎですか?

 結局5回も費やして初心者に優しくない初心者講座みたいになってしまったような…。まぁ、VBAは何とか使わずに乗り切りました。在庫計算とか若干ひどい気もしますが、まぁこんなもんでいいでょ?◯◯くん。



All About スタイルストア
pagetop