WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

MVC5 アプリに 2 要素認証を実装

by WebSurfer 27. October 2021 12:34

Visual Studio 2019 のテンプレートを使って作成した ASP.NET MVC5 アプリには、SMS と Email を利用した 2 要素認証を使用するためのコードが組み込まれています。今更ながらですが、その機能を検証してみました。その際に備忘録として残しておくのが良さそうと思った点を以下に書いておきます。

SQL Server のユーザー情報

まず最初に、ASP.NET MVC5 が何をベースに 2 要素認証を行っているかを書きます。これを覚えておくと、この先の話が分かりやすいかと思いますので。

上の画像を見てください。これは ASP.NET Identity が使う SQL Server データベースの AspNetUsers テーブルで、この例ではアプリを実行して登録した 2 人のユーザー情報が含まれています。

2 要素認証はユーザーごとに有効 / 無効の設定ができます。有効に設定すると当該ユーザーの TwoFactorEnabled 列が True になります。

有効にしただけでは 2 要素認証は働きません。さらに条件があって、メールアドレスと SMS 用の電話番号がそれぞれ Email 列 と PhoneNumber 列に登録済みで(片方のみでも可)、その有効性が確認されて EmailConfirmed 列と PhoneNumberConfirmed 列が True になっている必要があります。

上の画像の2 番目のユーザー (Id が 7c...) がその条件を満たしています。このユーザーはログイン画面で id とパスワードを入力するだけではログインできず、その後 2 要素認証用トークンの確認画面にリダイレクトされます。別途 SMS または Email でトークンが送信されてきますので、それを確認画面に入力するとログインできます。

上の画像の 1 番目のユーザー (Id が 30...) も Manage/Index 画面で 2 要素認証を有効に(TwoTactorEnabled 列を True に)設定することはできます。しかし、EmailConfirmed 列と PhoneNumberConfirmed 列が False となっている場合は 2 要素認証は働かず、ログイン画面で id とパスワードを入力するだけでログインできてしまいます。

Email による 2 要素認証を可能にするためには AspNetUsers テーブルの Email 列に有効なメールアドレスを登録し、EmailConfirmed 列を True にする必要があります。通常それは、先の記事「Email Confirmation の実装 (MVC5)」で書いた機能で、ユーザー登録の際に自動的に設定されます。

ASP.NET Identity 2.0 ではデフォルトで id にメールアドレスを使っていますので、ユーザー登録するだけで AspNetUsers テーブルの Email 列にはメールアドレスが登録されますが、EmailConfirmed 列は False のままです。Email Confirmation 機能によりメールを受け取って、その本文にあるリンクをクリックすることにより、EmailConfirmed 列が True に設定されます。

SMS による 2 要素認証を可能にするには、PhoneNumber 列への SMS 送信先の電話番号の登録が必要になります。その操作はユーザー登録後に Manage/Index 画面にて行います。電話番号を登録すると SMS に確認用トークンが送信されてくるので、それを入力して確認することにより PhoneNumberConfirmed 列が Ture に設定されます。(詳細後述)


2 要素認証の実装方法は Microsoft のチュートリアル「SMS や電子メールで 2 要素認証する ASP.NET MVC 5 アプリ」に書かれています。

ただし、チュートリアルの手順では実際に SMS と Email の送信機能を実装しないと検証ができないのが難点です。Email Confirmation 用に SendGrid にユーザー登録して ApiKey を取得し、メールを送信できるようにするのは相当面倒でした。SMS の方は試してないのでどのぐらい面倒かは分かりませんが、チュートリアルにある Twilio のリンクをクリックしただけでその先に進んでユーザー登録しようという気力が失われました。(汗)

実際に SMS と Email でトークンを受け取ることができるのを確認しないと意味がないと言われるかもしれませんが、SMS と Email の送信機能を実装するところで挫折して先に進めないかもしれません。とりあえず基本を把握しておいて、後で実際に運用に使うプロバイダが決まってから Email や SMS の送信機能を実装して検証する方が良さそうだと思いました。

という訳で、Visual Studio 2019 のテンプレートを使って作成したプロジェクトをベースに SMS と Email の送信機能は実装しないでやってみました。

まずプロジェクトの作成ですが、チュートリアルと同様に .NET Framework 版の ASP.NET MVC5 アプリのプロジェクトを、認証に「個別のユーザーアカウント」 を選択して作成します。Core 版と間違えないよう注意してください。(下の画像は Visual Studio 2019 のもので、チュートリアルのものとは異なります)

プロパティの作成

プロジェクト作成の後、チュートリアルには書いてありませんが、ASP.NET Identity が使うユーザー情報のストアとしての SQL Server データベースの生成と、ユーザー登録が必要です。アプリを実行してブラウザ上でユーザー登録を行えば、EF Code First の機能を使ってのデータベースの作成と、データベースへのユーザー情報の登録が自動的に行われます。

ユーザー登録に成功してログイン状態になると、画面の右上に「ようこそ・・・」と表示されます。それをクリックすると以下のような Manage/Index 画面に遷移します。

Manage/Index 画面

上の画像では「2 要素認証: 2 要素認証プロバイダーが構成されていません・・・」と表示されています。それを変更して 2 要素認証の設定ができるようにします。そのためには、チュートリアルの「7.Views\Manage\Index.cshtml Razor ビューを更新します」にあるように Views\Manage\Index.cshtml に手を加えます。(手順 1 ~ 6 は SMS 送信機能の実装ですので今回はスキップ)

Visual Studio 2019 のテンプレートで作成した場合コードは生成済みでコメントアウトされていますので、それを以下のようにすれば OK です。だたし、テンプレートで生成されたコードには &nbsp;&nbsp;]</TEXT> が欠落していて、そのままではエラーになるので、下の画像の赤枠の部分のコードを追加してください。

Index.cshtml Razor ビューを更新

上の Views\Manage\Index.cshtml の更新後、Manage/Index 画面を表示すると以下のようになります。

Razor ビュー更新後の Manage/Index 画面

上の画像の「電話番号」というのは SMS を利用した 2 要素認証を行う場合の SMS 送信先です。登録してない場合は Add と言うリンクが表示されますので、それをクリックすると Manage/AddPhoneMumber に遷移します。

SMS 用の電話番号の登録

そこで SMS 送信先の電話番号を入力して[送信]ボタンをクリックすると、テンプレートで実装された Manage/AddPhoneNumber アクションメソッドのコードがトークンを生成して SmsService クラスの SendAsync メソッドを実行し、SMS を使って登録した電話番号にトークンを送信します。

この記事では SMS 送信用のコードは実装してませんので何も起こりませんが、以下のようにデバッガを使えばトークンが message の Body に含まれているのが確認できます。

SMS でトークン送信

SMS 送信後、確認画面 Manage/VerifyPhoneNumber にリダイレクトされます。

確認画面でトークンの入力

SMS で送られてきたトークンを上の確認画面の[コード]欄に入力して[送信]ボタンをクリックすると Manage/Index にリダイレクトされ、以下のように登録された SMS 用の電話番号が表示されます。

登録された SMS 用の電話番号

同時に、入力した電話番号は SQL Server データベースの AspNetUsers テーブルの当該ユーザーのレコードの PhoneNumber に登録され、PhoneNumberConfirmed が true になります。

この記事のように SMS の送信機能を実装しない場合、上の画像のようにデバッガでトークンを確認してから確認画面に入力するのは面倒ですよね。そこを何とかしたいという場合は、トークンの生成のコードは確認画面用の Manage/VerifyPhoneNumber アクションメソッドにもありますので、便宜的に(あくまで便宜的にです)以下のようにして ViewBag を使って確認画面に表示することができます。

// GET: /Manage/VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
    var code = await UserManager
                     .GenerateChangePhoneNumberTokenAsync(
                         User.Identity.GetUserId(), phoneNumber);

    // SMS は使えないので ViewBag を使って View に表示されるようにした
    ViewBag.Token = code;

    // 電話番号を確認するために SMS プロバイダー経由で SMS を送信します。
    return phoneNumber == null ? View("Error") : 
        View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}

以上は SMS 送信先の電話番号を登録するだけの設定です。以下に、2 要素認証を行うために、登録した SMS 用の電話番号または Email アドレス宛にトークンを送るための設定を書きます。

・・・と言っても、そのためのコードは Visual Studio 2019 のテンプレートで作成したプロジェクトにほぼ実装済みで、自力でコードを書いて実装しなければならないのは Email, SMS が送信できるようにするための機能だけです。

それらの機能は IdentityConfig.cs の EmailService クラスと SmsService クラスの中の SendAsync メソッドに実装しますが、上に書きましたようにこの記事ではとりあえずそこはスキップします。

2 要素認証プロバイダーは、SMS と Email 用については自動生成された IdentityConfig.cs の ApplicationUserManager クラスの Create メソッドで manager.RegisterTwoFactorProvider メソッドを使って以下のように登録済みです。EmailService クラスと SmsService クラスもインスタンス化されて ApplicationUserManager に設定されています。

manager.RegisterTwoFactorProvider("電話コード", 
    new PhoneNumberTokenProvider<ApplicationUser>
{
    MessageFormat = "あなたのセキュリティ コードは {0} です。"
});

manager.RegisterTwoFactorProvider("電子メール コード", 
    new EmailTokenProvider<ApplicationUser>
{
    Subject = "セキュリティ コード",
    BodyFormat = "あなたのセキュリティ コードは {0} です。"
});

manager.EmailService = new EmailService();
manager.SmsService = new SmsService();

2 要素認証の有効 / 無効はユーザーごとに設定します。Manage/Index 画面で[2 要素認証:]の[有効化]をクリックすると、SQL Server データベースの AspNetUsers テーブルの当該ユーザーの TwoFactorEnabled 列が True に変わります。

この設定により、ログインの際に SignInManager.PasswordSignInAsync メソッドの戻り値が RequiresVerification になって Account/SendCode にリダイレクトされるようになります。

この先に進んで Email による 2 要素認証が動くようにするには、この記事の一番上の画像にあるように当該ユーザーのレコードの EmailConfirmed 列が True になっていなければなりません。先の記事「Email Confirmation の実装 (MVC5)」で書きました機能が実装されていてユーザー登録の際 Email Confirmation が行われていれば True になっているはずです。Email Confirmation 機能が実装されてなければ、Visual Studio の「SQL Server オブジェクトエクスプローラー」で AspNetUsers テーブルを開いて手動で EmailConfirmed 列を True に設定してください。

それで以下の画像のように Account/SendCode にリダイレクトされた時にドロップダウンリストに[電話コード]と[電子メール コード]という選択肢が表示されるようになります。 (EmailConfirmed 列が False のままですとドロップダウンリストには[電話コード]しか表示されません)

Account/SendCode

ドロップダウンリストの[電話コード]が SMS 用で[電子メール コード]が Email 用です。分かり難い日本語ですが、それらの文字列は上のコードのとおり IdentityConfig.cs の ApplicationUserManager クラスで登録したときのもので、任意の文字列に変更可能です。

ドロップダウンリストの[電話コード]を選択して[送信]ボタンをクリックすると SMS で、[電子メール コード]を選択すると Email でトークンが送信されます。その操作は POST 側の Account/SendCode アクションメソッドの SignInManager.SendTwoFactorCodeAsync メソッドで行われます。SMS を使うか Email を使うかの切り替えは、ドロップダウンリストの選択に応じて、メソッド内部で自動的に行われます。

トークンの送信後、下の画像のように Account/VerifyCode にリダイレクトされます。赤枠で囲った部分は検証中に便宜を図るため自分が追加したもので、元のコードには含まれていません。

Account/VerifyCode

Account/VerifyCode 画面の[コード]テキストボックスに SMS または Email で送信されてきたトークンを入力して[送信]ボタンをクリックするとログインできます。


ついでに、本題とは関係ない話ですが、検証の際の便宜を図るために (いちいちデバッガで SendAsync メソッドの引数に含まれるトークンを調べなくても済むようにするため)、上の画像の赤枠で囲ったようにトークンを Account/VerifyCode 画面に表示する方法を書いておきます。トークンは以下のように Account/SendCode アクションメソッドで取得します。

// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // トークンを生成して送信します。
    if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
    {
        return View("Error");
    }

    // SMS, Email は使えないのでここでトークンを取得して
    // Account/VerifyCode に渡すコードを追加
    var id = await SignInManager.GetVerifiedUserIdAsync();
    var provider = model.SelectedProvider;
    var code = await UserManager.GenerateTwoFactorTokenAsync(id, provider);

    // パラメータ token という名前で Account/VerifyCode
    // にトークンを渡す
    return RedirectToAction("VerifyCode", 
        new { 
            Provider = model.SelectedProvider, 
            ReturnUrl = model.ReturnUrl, 
            RememberMe = model.RememberMe,
            Token = code        // これを追加
        });
}

上のコードの最後で Accout/VerifyCode にリダイレクトしていますが、そこで取得したトークンを Token という名前のパラメータとして設定して渡します。Accout/VerifyCode アクションメソッドの引数でパラメータ(クエリ文字列)Token に設定されたトークンを取得し、ViewBag を使って View に渡して表示しています。

Tags: , , ,

MVC

MySQL で Contoso University チュートリアル (CORE)

by WebSurfer 19. October 2021 12:22

データベースに MySQL を利用して、ASP.NET Core MVC アプリを Micorsoft のチュートリアル「ASP.NET Core MVC と EF Core - チュートリアル シリーズ」に従って作成してみました。その際に気になったこと、憶えておいた方が良さそうなことを備忘録として残しておきます。

Contoso University アプリ

チュートリアルはデータベースに SQL Server (LocalDB) を使うことを前提に書かれています。それを MySQL に代えて、チュートリアルの「1. 開始するには」から「8. コンカレンシーの競合の処理」までの手順に従って実装しました。

この記事で使った MySQL サーバーは Windows 10 にインストールしたバージョン 8.0.19 です。詳しくは先の記事「MySQL をインストールしました(その 3)」を見てください。

以下に「1. 開始するには」から「8. コンカレンシーの競合の処理」の各手順において、気になったこと、憶えておいた方が良さそうなことを書いておきます。

一番気になっていたのは「8. コンカレンシーの競合の処理」の手順の楽観的同時実行制御の実装でしたが、モデルのプロパティの定義だけ変更すれば MySQL でも可能でした。その結果が上の画像です。

(1) 開始するには

この記事では、チュートリアルに書いてあるようなプロジェクトの新規作成はせず、先の記事「MySQL で Movie チュートリアル (CORE)」で作成した既存のプロジェクトをベースに使いました。

まず Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore を NuGet からインストールします。その他の必要な NuGet パッケージはベースに使った既存のプロジェクトにインストール済です。結果は以下のようになります。

NuGet パッケージ

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore の役割は "ASP.NET Core middleware for Entity Framework Core error pages. Use this middleware to detect and diagnose errors with Entity Framework Core migrations." だそうです。チュートリアルの「データベース例外フィルターを追加する」と関係があるようです (未確認ですが)。

Strtup.cs ファイルにある Startup クラスの ConfigureServices メソッドに SchoolContext を登録するコードを追加します。以下の通りです。

SchoolContext を登録

チュートリアルの手順「SchoolContext を登録する」に書いてあるコードは SQL Server 用で、これを MySQL 用に変更する必要がありますので注意してください。

次に appsettings.json ファイルに接続文字列を設定します。チュートリアルの例は LocalDB 用ですので MySQL 用に変更します。以下のような感じです (あくまで「感じ」です。MySQL の設定により異なります)。

接続文字列を設定

チュートリアルの「コントローラーとビューを作成する」の手順でスキャフォールディングを行う際、NuGet パッケージに Microsoft.EntityFrameworkCore.SqlServer が必要になるケースがありますので注意してください。エンティティクラスの定義によると思われます。ここ「(1) 開始するには」の手順では不要でしたが、下の「(6) 関連データの読み取り」では必要でした。

アプリを実行すると EF Code First の機能を使って、接続文字列で database=ContosoUniversity1 と指定した通り ContosoUniversity1 という名前のデータベースと、SchoolContext クラスで指定した Course, Enrollment, Student という名前のテーブルが生成されます。

さらにチュートリアルには初期データをシードするためのコードが含まれており、その通り実装すれば、最初にアプリを実行した時にコードに書かれた通り初期データが Course, Enrollment, Student テーブルに登録されます。

(2) 作成、読み取り、更新、削除の操作

チュートリアルある通りに実装してチュートリアル通りの結果が得られます。

(3) 並べ替え、フィルター、ページング、グループ化

チュートリアルある通りに実装してチュ��トリアル通りの結果が得られます。

(4) 移行

ここは Migration 操作を学ぶことを目的としているようです。

チュートリアルでは dotnet コマンドを使っていますが、Visual Studio を使っているならそのパッケージマネージャーコンソールを使った方が簡単だと思います。

NuGet パッケージ Microsoft.EntityFrameworkCore.Tool が必要ですが、上のステップ (1) の画像の通りインストール済みですので、このチュートリアルの操作にはパッケージマネージャーコンソールを使いました。

Drop-Database, Add-Migration, Update-Database という操作を行いますが、データベースが MySQL だから何か問題が出るということは、少なくともこのチュートリアルの操作ではありませんでした。(この後のセクション「(5) 複合データ モデルの作成」では 2 点問題がありましたが)

ベースに使った既存のプロジェクトには他の既存のコンテキストクラスが含まれています。そういう場合は、Drop-Database, Add-Migration, Update-Database 操作の際に、既存のコンテキストと区別するため、-Context SchoolContext オプションの追加が必要です。

(5) 複合データ モデルの作成

MySQL ではチュートリアルの通りにはできないということがありました。問題があったのは以下の 2 点です。

  1. Column 属性」のセクションで Student クラスの FirstMidName プロパティに付与した [Column("FirstName")] 属性。
  2. Department エンティティを作成する」のセクションで Department クラスの Budget プロパティに付与した [Column(TypeName = "money")] 属性。

いずれも Add-Migration の後の Update-Database コマンドで前者は NotImplementedException、後者は MySqlException がスローされ、MySQL のテーブルの変更・生成に失敗します。

前者は既存の Student テーブルの FirstMidName 列の名前を Migration 操作で FirstName という名前に変更しようというものです。NotImplementedException 例外がスローされるということは MySQL 用の Entity Framework にその実装がされてないということのようです。

後者は MySQL には TypeName = "money" で指定した money 型はないためのエラーです。

上記の操作に失敗で先に進めることができなくなったしまったので、やむを得ずゼロから作り直しました。Drop-Database で ContosoUniversity3 をドロップし、Add-Migration で生成された Migrations フォルダのファイルを削除し、チュートリアルに従ってコードの修正・追加を最後まで終わらせてから(途中で Migration 操作は行わないで)、最後に一気に Add-Migration, Update-Database コマンドでデータベースを生成しました。

問題となった Student クラスの FirstMidName プロパティに付与した [Column("FirstName")] 属性ですが、データベースを生成する最初の時点で付与しておけば指定した通り Student テーブルの当該列の名前は FirstName になります。チュートリアルのように最初に FirstMidName という列名で作って、後で Migration 操作で変更しようとすると、その操作に必要なメソッド等が実装されてないからか、NotImplementedException がスローされます。

Department クラスの Budget プロパティに付与した [Column(TypeName = "money")] 属性は [Column(TypeName = "decimal")] に変更が必要です。

チュートリアルの「移行を追加する」の手順に、Migrations フォルダに生成された ComplexDataModel クラスのコードに手を加えるように書かれていますが、それは先の操作でデータベースに作成済みの既存のレコードとの整合を取るためのもので、ゼロから一気に作るのであれば不要です。

(6) 関連データの読み取り

スキャフォールディング操作の際 NuGet パッケージに Microsoft.EntityFrameworkCore.SqlServer が必要になります。ちなみに「(1) 開始するには」の手順では不要でした。上の「(5) 複合データ モデルの作成」でいろいろ変更・追加したエンティティクラスの定義によると思われます。

そのため、最初のスキャフォールディング操作には失敗しますが、その際 Microsoft.EntityFrameworkCore.SqlServer が自動的に追加されますので、以下のようになっていることを確認し再度スキャフォールディングすれば成功すると思います。

NuGet パッケージの確認

上の画像の 1 番目から 4 番目のパッケージ間でのバージョンの不一致、ランタイムとのバージョンの不一致があるとスキャフォールディングに失敗することがありますので注意してください。

もう一つ、MySQL とは関係ないチュートリアルのミスですが、Courses Taught by Selected Instructor テーブルを表示する View のコードで selectedRow = "success"; となっていますが、selectedRow = "table-success"; にしないと Select しても背景色が変わらないので修正してください。

(7) 関連データの更新

チュートリアルある通りに実装してチュートリアル通りの結果が得られます。

(8) コンカレンシーの競合の処理

チュートリアルの手順では Department テーブルに rowversion (Transact-SQL) 列を追加し、それを使って楽観的同時実行制御を実装するというものです。

MySQL には rowversion はなく、それに代わるものとして timestamp というものがありますが同じではありません。.NET 側では rowversion は byte[] 型、timestamp は DateTime 型になるのですが、そのような違いがあっても Entity Framework による楽観的同時実行制御に使えるかが懸念したところです。

チュートリアルには Department クラスに byte[] 型の RowVersion プロパティを追加して Migration 操作でデータベースの Department テーブルに RowVersion 列を追加するよう書かれています。まずそこを MySQL ように変更する必要があります。

ググって見つけた記事 entity framework 6 mysql rowversion を参考に以下のプロパティ定義を使いました。

[Timestamp]
[ConcurrencyCheck]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime TimeStamp { get; set; }

timestamp では分解能が不十分という話もあるようですが、11.3.1 DATE、DATETIME、および TIMESTAMP 型によると "DATETIME または TIMESTAMP 値には、マイクロ秒 (6 桁) までの精度で後続の小数秒部分を含めることができます" とのことですので十分ではないかと思います。なので今回は上のプロパティ定義で実装してみました。

マイクロ秒以内で同時実行が行われるケースも考えなければならないとか、どうしても分解能が気になるという場合は Better way to implement a row version with EF Core and MySQL? に紹介されている方法もありそうです。

Add-Migration, Update-Database コマンドで MySQL の Department テーブルには以下のようにそれらしい形で datetime 型の Timestamp 列が追加されます。timestamp 型でないのがちょっと気になりますが。

Department テーブル

特に気になっていたのはスキャフォールディング機能を使って Department テーブルの CRUD 用のコントローラーとビューを生成したとき TimeStamp 列を使って同時実行制御を行う機能が含まれるかということです。

結果は、SQL Server の場合と同様に、同時実行で DbUpdateConcurrencyException 例外がスローされ楽観的同時実行制御ができるようになりました。この記事の一番上の画像がその結果です。

なお、プロパティ名をチュートリアルの RowVersion から TimeStamp に変えましたので、チュートリアルの「Edit メソッドを更新する」の Edit アクションメソッドのコードで RowVersion, rowVersion となっているところは TimeStamp, timeStamp に変更が必要です。型の指定も byte[] となっているところを DateTime に変更する必要があります。

Edit 用の View のコードも同様で、「Edit ビューを更新する」のコードで RowVersion, rowVersion となっているところは TimeStamp, timeStamp に変更が必要です。

Delete については View の <input type="hidden" asp-for="RowVersion" /> の RowVersion を TimeStamp に変更します。


チュートリアルにはこの先に「9. 継承」と「10. 高度なトピック」がありますが、それはまた次の機会にということで・・・

Tags: , , , ,

CORE

MVC の DropDownList での NULL の処置

by WebSurfer 17. October 2021 13:31

ASP.NET MVC アプリでデータベースの CRUD 操作を行う際ドロップダウンリストを利用してユーザー入力に便宜を図ることがあると思います。その際、NULL 可になっている列にドロップダウンリストから NULL を入力できるようにするにはどうするかという話を書きます。

ドロップダウンリスト

この記事は ASP.NET MVC5 および ASP.NET Core MVC の場合です。ASP.NET Web Forms アプリの場合は別の記事「DropDownList での NULL の処置」に書きましたのでそちらを見てください。

先の記事「スキャフォールディング機能」および「スキャフォールディング機能 (CORE)」で作ったアプリのドロップダウンリストから NULL を入力できるようにしてみます。

そのアプリは Northwind サンプルデータベースの Products, Suppliers, Categories テーブルから作ったコンテキストクラス、エンティティクラスをベースに、Visual Studio のスキャフォールディング機能を使って CRUD 操作を行うための Controller / View のコードを一式自動生成したものです。

自動生成されたコードの Create, Edit 画面では CatrgoryID, SupplierID 列にドロップダウンリストを使用して CategoryName, CompanyName (名前) が表示され、ID (数字) ではなく名前を見て選択できるようになっています。上の画像を見てください。

Products テーブルの SupplierID, CategoryID 列は NULL 可なのですが、自動生成されたコードでは NULL を選択することはできません。それを、Create, Edit 操作の際ドロップダウンリストから NULL を選択して、その結果を Products テーブルに反映できるようにします。

必要なコードの追加・変更はわずかで、自動生成されている View のコードの一部に以下の修正を行うだけです。

ASP.NET MVC5

View に自動生成されている DropDownList メソッドの第 3 引数として "NULL" という文字列を追加。

@Html.DropDownList("SupplierID", null, "NULL", 
    htmlAttributes: new { @class = "form-control" })

ASP.NET Core MVC

View に自動生成されている select タグヘルパーに以下のように option 要素を追加。

<select asp-for="SupplierID" class="form-control" 
    asp-items="ViewBag.SupplierID">
    <option value="">NULL</option>
</select>

上記の設定の結果、ブラウザに送信される html ソースは以下のようになります。value="" で NULL という項目が追加されているところに注目してください。

<select class="form-control" id="SupplierId" name="SupplierId">
  <option value="">NULL</option>
  <option value="1">Exotic Liquids</option>
  <option value="2">New Orleans Cajun Delights</option>
  <option value="3">Grandma Kelly's Homestead</option>
  ・・・中略・・・
</select>

これによりブラウザに表示されたドロップダウンリストを開くと NULL という選択肢が含まれるようになります。

NULL の選択

そして NULL を選択して POST すれば、データは SupplierId= という形 (name=value の value が空) でフォームに含まれて送信されます。

送信結果

モデルバインディングに使う Product クラスの定義は以下のようになっています。SupplierId, CategoryId プロパティの型が int? であるところに注目してください。

public partial class Product
{
    [Key]
    [Column("ProductID")]
    public int ProductId { get; set; }
    [Required]
    [StringLength(40)]
    public string ProductName { get; set; }
    [Column("SupplierID")]
    public int? SupplierId { get; set; }
    [Column("CategoryID")]
    public int? CategoryId { get; set; }

    // ・・・中略・・・
}

それをアクションメソッドの引数に使っていますので、POST されてきたデータは以下のようにモデルバインディングされます。SupplierId が null になっているところに注目してください。

モデルバインディング

これにより、自動生成されたアクションメソッドのコードで Products テーブルの当該レコードの SupplierID 列は NULL になります。

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2021  >>
MoTuWeThFrSaSu
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar