SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

.NET最新版でASP.NET Core

.NET 6でASP.NET CoreのRazor PagesによるMVVMパターンを理解する

.NET最新版でASP.NET Core 第4回


データベースコンテキストなど、主要な要素を理解する

データベースコンテキスト - Data/RazorPagesFootmarkContext.csファイル

 モデルクラスとデータベースを結び付けるのは、データベースコンテキストです。DAOパターンにおけるDTOとDAOの関係のようなものです。データベースコンテキストのクラスファイルは、プロジェクトのルートフォルダ直下のDataフォルダに作成されます。

リスト1:Data/RazorPagesFootmarkContext.cs
…略…
public class RazorPagesFootmarkContext : DbContext      (1)
{
    public RazorPagesFootmarkContext (DbContextOptions<RazorPagesFootmarkContext> options)      (2)
        : base(options)
    {
    }

    public DbSet<RazorPagesSample.Models.Footmark> Footmark { get; set; }       (3)
}

 (1)で、データベースコンテキストクラスであるRazorPagesFootmarkContextをDbContextクラスを継承して宣言しています。この名前は、dotnet-aspnet-codegeneratorコマンドで-Dcオプションで指定したものです。クラスには、コンストラクタ(2)とプロパティ(3)があります。このうちコンストラクタはアプリケーションの生成時にProgram.csから呼び出されますが、重要なのはプロパティです。プロパティであるFootmarkは、モデルであるFootmarkクラスによるジェネリクスのDbSet型です。DbSet型なので、データセットを持つのだということがわかります。つまり、データベースコンテキストオブジェクトのFootmarkプロパティを介してデータの取得などが行えるわけです。

 なお、RazorPagesFootmarkContextクラス自体の定義は非常にシンプルですが、それは、基本的な実装は親クラスであるDbContextクラスで行われているためです。

アプリケーションのエントリポイント - Program.csファイル

 Program.csは、第2回で紹介したように、アプリケーションの開始時に実行されるエントリポイントです。Scaffoldingによって、このファイルにもコードが追加されます。ここでは、追加されたコード部分を見てみます。

リスト2:Program.cs
…略…
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesFootmarkContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("RazorPagesFootmarkContext")));         (1)
var app = builder.Build();
…略…

 (1)が追加されたコードです。意味は以下の通りです。

  • 接続文字列として"RazorPagesFootmarkContext"を使う
  • データベースプロバイダにSQLiteを使う(UseSqlite)
  • RazorPagesFootmarkContext型のデータベースコンテキストを追加する

 これで、Footmarkモデルを使うためのデータベースコンテキストが作成されて、ページモデルから利用可能になります。

アプリケーションの設定 - appsettings.jsonファイル

 Program.csファイルでは接続文字列が登場しましたが、これはappsettings.jsonファイルに記述されています。これも、Scaffoldingで自動的に追加されます。

リスト3:appsettings.json
…略…
"AllowedHosts": "*",
"ConnectionStrings": {  (1)
  "RazorPagesFootmarkContext": "Data Source=RazorPagesFootmarkContext-7671a142-….db"   (2)
}
…略…

 (1)は接続文字列のためのブロックで、(2)が接続文字列本体です。接続文字列は(1)の中に複数記述でき、「接続文字列名: 接続文字列」の形で記述します。この場合はSQLiteを使うので、データソースにデータベースファイル名(これも自動的に決められる)が指定されているのみです。

 なお、Development環境ではappsettings.Development.json、Staging環境ではappsettings.Staging.json、Production環境ではappsettings.Production.jsonというように、設定ファイルを用意することで、環境ごとにデータベースを切り替えることができます。これらの設定ファイル内の項目は、appsettings.jsonに優先して適用されます。

 また、Program.csファイル中でapp.Environment.IsDevelopment()メソッド、app.Environment.IsStaging()メソッド、app.Environment.IsProduction()メソッドを用いた切り替えを行うことも可能です。以下は、Development環境ならSQLiteを、それ以外ならMySQLを使用する例です(MySQLを使う場合の接続文字列も別途必要ですが、ここでは割愛しています)。

リスト4:Program.cs
if (app.Environment.IsDevelopment())
{
    // Development環境ならSQLiteを使う
    builder.Services.AddDbContext<RazorPagesFootmarkContext>(options =>
        options.UseSqlite(builder.Configuration.GetConnectionString("RazorPagesFootmarkContext")));
}
else
{
    // それ以外の環境ならMySQLを使う
    builder.Services.AddDbContext<RazorPagesFootmarkContext>(options =>
        options.UseMySql(builder.Configuration.GetConnectionString("RazorPagesFootmarkContextMySQL"))); // 接続文字列の定義は省略
}

ページモデル - Pages/Footmarks/Create.cshtml.csファイル

 ページモデルの例として、作成(C)を司るPages/Footmarks/Create.cshtml.csファイルがどうなっているか見てみましょう。

リスト5:Pages/Footmarks/Create.cshtml.cs
…略…
namespace RazorPagesSample.Pages.Footmarks
{
    public class CreateModel : PageModel
    {
        // データベースコンテキストを保持するフィールド
        private readonly RazorPagesFootmarkContext _context;    (1)

        // データベースコンテキストを受け取るコンストラクタ
        public CreateModel(RazorPagesFootmarkContext context)   (2)
        {
            _context = context;
        }

        // GETメソッドによるリクエストの処理(元のページを表示)
        public IActionResult OnGet()    (3)
        {
            return Page();
        }

        // データバインドするプロパティFootmarkの宣言
        [BindProperty]  (4)
        public Footmark Footmark { get; set; }

        // POSTメソッドによるリクエストの処理
        public async Task<IActionResult> OnPostAsync()  (5)
        {
            // 検証に問題があれば元のページを表示
            if (!ModelState.IsValid)    (6)
            {
                return Page();
            }
            // データベースコンテキストにモデルデータを追加
            _context.Footmark.Add(Footmark);    (7)
            await _context.SaveChangesAsync();  (8)
            // 一覧ページにリダイレクト
            return RedirectToPage("./Index");   (9)
        }
    }
}

 (1)はデータベースコンテキストの保持のためのフィールド、そして(2)はコンストラクタで引数にデータベースコンテキストを受け取って(1)にセットしています。ページモデルのコンストラクタはページ生成時に呼び出されますが、その際にあらかじめ作成されているデータベースコンテキストが渡されます(以下のNOTEも参照)。

 (3)では、ページがHTTP GETメソッドで呼び出された場合に、Page()メソッドの戻り値を返してページを表示します。Page()メソッドは、ページオブジェクトを返すメソッドで、この場合はCreate.cshtmlそのものになります。つまり、HTTP GETでは具体的な処理は何もせずに、作成ページをそのまま再表示するわけです。

 (4)からの2行は、データバインドの指定です。第2回で、データバインドとはビューとページモデル(ビューモデル)を結び付けるものとしましたが、ここではFootmarkをデータバインドするプロパティとしています。これにより、ビューでこのプロパティを参照し、かつビューで変更されたこのプロパティをページモデルで受け取ることが可能になります。

 (5)は、ビューでフォームが送信されたとき、すなわちHTTP POSTメソッドで呼び出された場合の処理を行うメソッドです。async演算子が付いているので、このメソッドは非同期実行となります。このメソッドのように、データベースの更新など比較的時間のかかる処理を含むときは、非同期実行を指定しておきます。

 (6)は、入力データの検証に問題があった場合の処理をModelState.IsValidで判定しています。問題ない場合は、(7)でビューから受け取ったFootmarkプロパティをもとにデータベースコンテキストにデータを保存し、(8)で変更を反映し、(9)で一覧ページにリダイレクトしています。このとき、(8)ではawait演算子を付けて、変更処理が終了するまでいったんOnPostAsync()メソッドを待機状態にすることを指定しています。

[NOTE]依存性注入(DI)

 CreateModelでは、コンストラクタにデータベースコンテキストが渡されています。このように、依存性のあるオブジェクトなどを動的に渡す仕組みは、依存性注入(DI; Dependency Injection)と呼ばれます。ページモデルに、最初から特定のデータベースにアクセスする仕組みを入れてしまうと、そのデータベースに対して高い依存性を持つことになり、汎用性に欠けてしまいます。この場合は、どのデータベースを使うか? ということはデータベースコンテキストに任せて、ページモデルはデータベースコンテクストだけを受け取ることで、データベースに対する依存性を下げているのです。

ビュー - Pages/Footmarks/Create.cshtmlファイル

 最後にビューについて、同じく作成(C)のPages/Footmarks/Create.cshtmlファイルがどうなっているか見てみましょう。

リスト6:Pages/Footmarks/Create.cshtml
<!-- POSTメソッドで送信するフォーム -->
<form method="post">
  <!-- 検証メッセージの概要を表示するブロック -->
  <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  <!-- フォームの各要素はform-groupクラスでまとめられる -->
  <div class="form-group">
    <!-- ラベルに展開 -->
    <label asp-for="Footmark.YourName" class="control-label"></label>   (1)
    <!-- 入力要素に展開 -->
    <input asp-for="Footmark.YourName" class="form-control" />  (2)
    <!-- 個々の入力要素の検証メッセージをここに表示する -->
    <span asp-validation-for="Footmark.YourName" class="text-danger"></span>    (3)
  </div>
…略…
  <!-- 送信ボタン -->
  <div class="form-group">
    <input type="submit" value="Create" class="btn btn-primary" />
  </div>
</form>

 作成ページのフォーム部分だけの抜粋です。(1)と(2)は、それぞれフォームにおけるラベルと入力要素ですが、asp-for属性に注目してください。asp-forはタグヘルパーと呼ばれ、要素に応じた属性などの自動生成をサポートします。ここでの属性値は、ページモデルにおけるプロパティ(ここではFootmark)です。ビューでは、このようにプロパティを参照できるのは第2回で紹介したとおりです。<label>要素では、ラベルとして表示されるリテラル、そしてfor属性を生成し、<input>要素ではname属性とvalue属性を生成します。(3)は検証メッセージの表示を行う<span>要素です。

 (2)では、name属性とvalue属性がasp-for属性から生成されることで、プロパティの値をvalue属性として初期表示し、name属性によってvalue属性とともに送信するという動きになります。これが、ページモデルにおけるデータバインドとして機能しているわけです。

図2:asp-forタグヘルパーとデータバインディング

 これらのマークアップは、具体的には以下のようにレンダリングされます。<label>要素のfor属性とリテラル、<input>要素のname属性とvalue属性がどのようになっているか確認してください(value属性は、プロパティの初期値が空なので、同じく空になります)。

リスト7:Pages/Footmarks/Create.cshtml(レンダリング後)
<div class="form-group">
  <!-- 検証に問題なければメッセージはレンダリングされない -->
  <!-- <label>要素に展開 -->
  <label class="control-label" for="Footmark_YourName">YourName</label>
  <!-- <input>要素に展開 -->
  <input class="form-control" type="text" data-val="true" data-val-required="The YourName field is required." id="Footmark_YourName" name="Footmark.YourName" value="" />
  <!-- <span>要素に展開されるが初期状態では空 -->
  <span class="text-danger field-validation-valid" data-valmsg-for="Footmark.YourName" data-valmsg-replace="true"></span>
</div>

次のページ
第3回で拡張した機能について特徴的な構文を理解する

この記事は参考になりましたか?

.NET最新版でASP.NET Core連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/15940 2023/05/26 17:17

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー