Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8899

ASP.NET Core 3.0 Razor Pages 事始め(7)

$
0
0

ASP.NET Core 3.0 Razor Pages 事始め(6)の続きです。

今回は公式チュートリアルのASP.NET Core アプリで生成済みページを更新するに沿って進めていこうと思います。

ページの見た目を変更する

まずは、Indexページの見た目の変更です。

チュートリアルでは、表のヘッダーの表示と、価格の表示を変更していますが、せっかくなので日本語で表示させようと思います。

まずは、Models/Movie.csを開いて、各プロパティに属性を追加します。

publicclassMovie{publicintID{get;set;}[Display(Name="タイトル")]publicstringTitle{get;set;}[DataType(DataType.Date)][Display(Name="リリース日")]publicDateTimeReleaseDate{get;set;}[Display(Name="ジャンル")]publicstringGenre{get;set;}[Display(Name="価格")][DisplayFormat(DataFormatString="{0:#,0}")]publicdecimalPrice{get;set;}}

[Display]属性は、項目の名前として表示する文字列を指定します。Indexページでは、表のヘッダー部にこの文字列が表示されます。

[DisplayFormat]属性は、フォーマットの書式を指定します。チュートリアルでは、

[Column(TypeName="decimal(18, 2)")]publicdecimalPrice{get;set;}

のように属性を追加する例が載っていますが、ここでは価格が円であると考え、この属性は取りました。
それと、初期データも、小数点を取って、適当な値に直しておきました。

そういう意味では、decimalではなく、intに変更したほうが良いのかもしれませんが、またマイグレーションやるのも面倒なので、このままにします。

ついでに、Edit | Details | DeleteCreate Newの部分も日本語に変更します。

では、テーブルのデータは全部消してから、再度アプリを起動し直します。

スクリーンショット 2019-11-10 21.28.41.png

スクリーンショット 2019-11-10 21.31.25.png

OKのようです。

Indexページだけではなく、Editページなどほかのページも項目のラベルが日本語表記に変わりました。

変更したIndex.cshtmlもいちおう以下に示しておきます。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1><p><aasp-page="Create">新規追加</a></p><tableclass="table"><thead><tr><th>
                @Html.DisplayNameFor(model => model.Movies[0].Title)
            </th><th>
                @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
            </th><th>
                @Html.DisplayNameFor(model => model.Movies[0].Genre)
            </th><th>
                @Html.DisplayNameFor(model => model.Movies[0].Price)
            </th><th></th></tr></thead><tbody>
@foreach (var item in Model.Movies) {
        <tr><td>
                @Html.DisplayFor(modelItem => item.Title)
            </td><td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td><td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td><td>
                @Html.DisplayFor(modelItem => item.Price)
            </td><td><aasp-page="./Edit"asp-route-id="@item.ID">編集</a> |
                <aasp-page="./Details"asp-route-id="@item.ID">詳細</a> |
                <aasp-page="./Delete"asp-route-id="@item.ID">削除</a></td></tr>
}
    </tbody></table>

それと、Chromeで動かすと、「このページを翻訳しますか」と聞いてくるのがうっとおしいので、_Layout.cshtml を以下のように変更しました。

<!DOCTYPE html><htmllang="ja"><head>

アンカー タグヘルパー

前回も書いたと思うけど、再度、Index.cshtmlで使われているアンカー タグヘルパーを見てみます。

<aasp-page="./Edit"asp-route-id="@item.ID">編集</a> |
<aasp-page="./Details"asp-route-id="@item.ID">詳細</a> |
<aasp-page="./Delete"asp-route-id="@item.ID">削除</a>

このタグヘルパーは、以下のようなHTMLに変換されます。

<ahref="/Movies/Edit?id=3">編集</a> |
<ahref="/Movies/Details?id=3">詳細</a> |
<ahref="/Movies/Delete?id=3">削除</a>

href属性が asp-pageasp-route-idから生成されているのが分かります。

id=33は、Movie.ID の値で、それぞれの行によって異なります。

@pageディレクティブの変更

Edit.cshtml / Delete.cshtml / Details.cshtml の先頭行の

@page

を以下のように変更します。

@page "{id:int}"

すると、Indexページのアンカータグヘルパーの部分が、以下のようなHTMLに変換されるようになります。

<ahref="/Movies/Edit/3">編集</a> |
<ahref="/Movies/Details/3">詳細</a> |
<ahref="/Movies/Delete/3">削除</a>

Index.cshtml側は何も変更していないのに、出力されるHTMLが変わりました。
ちょっと、驚きですね。

でも、これだと、

https://localhost:5001/Movies/details

のように、idの値を省略すると、 OnGetAsyncが呼び出される前に、404がブラウザに返ってしまいます。

idを省略可能にするには、3つの cshtmlを

@page "{id:int?}"

のように変更します。

こうすることで、 `OnGetAsync`が呼び出されます。
もちろん、

```c#
public async Task<IActionResult> OnPostAsync(int? id)

と指定していますから、id には、nullが渡ってきます。

これで、プログラムコード側で、省略された場合の動作を指定できるようになります。まあ、このアプリの場合はNullableにする必要性はないですが、あくまでも実験ということで。

楽観的同時実行制御

チュートリアルでは、「コンカレンシーの例外処理」という用語を使っていますね。

Edit.cshtml.cs を確認してみます。データ更新のメソッド OnPostAsyncを抜きだしてみます。

publicasyncTask<IActionResult>OnPostAsync(){if(!ModelState.IsValid){returnPage();}_context.Attach(Movie).State=EntityState.Modified;try{await_context.SaveChangesAsync();}catch(DbUpdateConcurrencyException){if(!MovieExists(Movie.ID)){returnNotFound();}else{throw;}}returnRedirectToPage("./Index");}

DbUpdateConcurrencyException例外をキャッチするコードがあり、これが、コンカレンシーの例外処理です。

試しに、ブラウザを2つ立ち上げて、同じMovieに対して、片方では編集ページ、もう片方では削除ページを開き、2つめのページで削除してから、編集ページでデータを更新すると、DbUpdateConcurrencyException例外が発生します。

デバッグでブレークポイントを設定すると、それを確かめることができます。

スクリーンショット 2019-11-04 13.12.38.png

ちなみに、2つのページで編集を開き、別の値で更新した時には、DbUpdateConcurrencyException例外が発生しませんでした。ASP.NET MVCと同様、rowversionカラムが必要みたいです。SQLiteはサポートしているのかな?

モデルバインディング

OnGetAsyncメソッド

もう一度、Edit.cshtml.cs を見てみます。

OnGetAsyncメソッドでは、最後に

returnPage();

とすることで、Pages/Movies/Edit.cshtml Razor ページをレンダリングします。 Edit.cshtml ファイルでは

@model RazorPagesMovie.Pages.Movies.EditModel

の行があるので、EditModelオブジェクトが使用できるようになります。あとは、Razor構文を使って、モデルの値をHTMLとバインドしていきます。

OnPostAsyncメソッド

EditModelクラスには、

[BindProperty]publicMovieMovie{get;set;}

というプロパティがあります。これが、ビューとバインドするデータになります。
この[BindProperty]属性を付けることで、クライアントから送信されてきたデータが、Movieプロパティにバインドされるようになります。

OnPostAsyncメソッドの中に入ってきたときには、既に、Movieプロパティには値が設定されているので、このサンプルでは、これをそのまま

_context.Attach(Movie).State=EntityState.Modified;

とすることで、DbContextに、Movieオブジェクトをアタッチして、状態を変更済みにしています。

この後で、

await_context.SaveChangesAsync();

とすれば、DBが更新されます。

なお、モデルにサーバー側で検知された検証エラーがあれば、ModelState.IsValidの値は、falseになっています。


Viewing all articles
Browse latest Browse all 8899

Trending Articles