WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

ASP.NET MVC の Model

by WebSurfer 2020年5月29日 13:46

ASP.NET MVC で言う Model がどういうものかについて書いておきます。ネットの記事などで目にする一般的な MVC の Model の説明とは少々違っていて理解し難いかもしれませんので (実は自分がそうでした)。

ASP.NET MVC の Model

上の画像はマイクロソフト公式解説書「プログラミング Microsoft ASP.NET MVC」に記載されている図を借用したものです。ASP.NET MVC には「入力モデル」「ビューモデル」「ドメインモデル」という 3 種類の Model があることを示しています。それら各モデルが重なっている点にも注意してください。

重なっているのは、Model 自体は一つのクラスとして定義されており同じものですが、使われ方によって「入力モデル」「ビューモデル」「ドメインモデル」というように役割が変わるということと理解すれば良いと思います。

一方、ネットなどでよく目にする一般的な MVC の Model の説明は、例えば Wikipedia の記事 Model View Controller にあるように「アプリケーションデータ、ビジネスルール、ロジック、関数」という役割を持ち、クライアント側から見て View と Controller の背後にあるという構成で説明されていることが多いと思います。

ASP.NET MVC の 3 種類の Model のうち、「ドメインモデル」が上で言う「アプリケーションデータ、ビジネスルール、ロジック、関数」に近いものです。

「ビューモデル」は Controller から View にデータを渡すために使われます。「入力モデル」はクライアントから送信されてくるデータを Controller のアクションメソッドに渡す時に使われます。

以下に説明のための具体例を書きます。

まず、プロジェクトの Models フォルダに、以下の Student クラスの定義(即ち、Model の定義)を含むクラスファイルがあるとします。

using System;
using System.Collections.Generic;

namespace Mvc5App.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

さらに、データーベースに Student テーブルがあって、それを Entity Framework を利用して編集・更新を行うための Controller と View を作るとします。

編集用の画面を表示するための Controller のアクションメソッドは以下のようになります。

public async Task<ActionResult> Edit(int? id)
{
    Student student = await db.Students.FindAsync(id);

    return View(student);
}

db.Students.FindAsync(id) の db は DbContext クラスを継承するコンテキストクラスで、Entity Framework を利用してデータベースへのクエリの実行などを行うものです。このコードでは、指定された id のレコードをデータベースから取得して Student オブジェクトを作っています。その部分は上の図で「ドメインモデル」に該当します。

return View(student) で View に Student オブジェクトを渡していますが、それが「ビューモデル」に該当します。

アクションメソッドに対応する View (Edit.cshtml) のコードの一行目に @model Mvc5App.Models.Student と書けば、View の中では Model プロパティで Student オブジェクトを取得できます。また、EditorFor などの Html ヘルパーでは、例えばその引数を model => model.LastName とすれば model に Student オブジェクトが渡されます。

View によって html ソースが生成されクライアント (ブラウザ) に送信されます。ユーザーがブラウザに表示されたテキストボックスの内容を編集後、ボタンクリック操作などでデータを送信するとサーバー側ではモデルバインディングという操作が行われます。

ブラウザから送信されてきたデータを受け取ってデーターベースを更新する Controller のアクションメソッドは以下のようになっています。モデルバインディングというのは、送信されてきたデータで Student オブジェクトを作成し、それをアクションメソッドの引数 student に渡す操作ですが、その時使うのが上の図の「入力モデル」になります。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(Student student)
{
    if (ModelState.IsValid)
    {
        db.Entry(student).State = EntityState.Modified;
        await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(student);
}

ModelState.IsValid が true の場合(送信データの検証結果が OK の場合)、if 文の中の最初の 2 行で既存のレコードが更新されます。その部分も上の図で「ドメインモデル」に該当します。

ASP,NET MVC アプリを作っているときに目立つのが「ビューモデル」と「入力モデル」と思いますが、それらは上に紹介した Wikipedia の記事にあるような一般的な MVC の Model の説明の機能「アプリケーションデータ、ビジネスルール、ロジック、関数」は持たないので、説明と違うということが混乱を招くところと思います。

Tags: , ,

MVC

Password Recovery (CORE)

by WebSurfer 2020年5月19日 12:59

Password Recovery はユーザーがパスワードを忘れてしまっても復活できる手段を提供するものです。ASP.NET Core 3.1 の Password Recovery がデフォルトの実装ではどのような動きになるかを以下に説明します。

(先の記事「Email Confirmation の実装 (CORE 版)」の「(9) Password Recovery」の補足です)

ページのヘッダ右上に表示されている [Login] をクリックして Login ページを表示すると、その中に [Forgot your password?] というリンクがあります。それをクリックすると下の画像の ForgotPassword ページに遷移します。

ForgotPassword ページ

その Email 欄にメールアドレスを入力して [Submit] をクリックすると、ForgotPassword.cshtml.cs の OnPostAsync メソッドで (a) 送信されたメールアドレスが登録済み、(b)Email Confirmation 済みであることを確認します。

条件 (a), (b) が確認できない場合は単純に ForgotPasswordConfirmation ページにリダイレクトされるだけでそれ以外何も起こりません。

条件 (a), (b) の条件の両方が確認できるとメールが送信され、その後で ForgotPasswordConfirmation ページにリダイレクトされます。以下のような画面になります。ユーザーにメールを見るよう促しているだけです。

ForgotPasswordConfirmation ページ

送信されたメールの本文には ResetPassword ページに確認用のトークンをクエリ文字列に含めた URL が含まれます。

Reset Password メール

上の画像のメールは、ユーザーのメーラーが html の表示を許可していない可能性を考えて URL そのものを送信するようにしています。(自動生成されるコードでは、URL を a 要素の href 属性に設定したハイパーリンク "clicking here" になります)

具体的には、Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs の OnPostAsynce() メソッドの中の SendEmailAsync メソッドのコードを以下のようにしています。

await _emailSender.SendEmailAsync(
    Input.Email,
    "Reset Password",
    "Please confirm your account by: " + 
        HtmlEncoder.Default.Encode(callbackUrl));

メールの URL をクリックすると ResetPassword ページが GET 要求され、クエリ文字列から確認用のトークンが渡されます。その応答としてブラウザに以下のような画面が表示されます。

ResetPassword ページ

上のページに、最初に ForgotPassword ページで申請したのと同じメールアドレスと、新しいパスワードを入力して [Submit] をクリックすると、メールアドレスとトークンが有効であればパスワードが新しいものに変更され、その後 ResetPasswordConfirmation ページにリダイレクトされます。

ResetPasswordConfirmation ページ

そのページのリンク [click here to log in] をクリックすると Login ページに遷移するので、そこでメールアドレスと新しいパスワードを入力すればログインできます。


ユーザー登録の場合と違って、Password Recovery ではメールが受信でき、それに含まれるトークンを ResetPassword ページに送信し、そのページで新しいパスワードを登録できないと何ともならない・・・すなわちメール送信機能の実装は必須のコーディングとなっています。

メール送信機能を実装しなくても Password Recovery を可能にするには、コードを書き換えて、 ForgotPasswordConfirmation ページで上に書いた条件 (a), (b) の条件の両方が確認できたら、ResetPassword ページにリダイレクトするといった修正が必要と思われます。(未検証・未確認です)

Tags: , , ,

CORE

Email Confirmation の実装 (CORE)

by WebSurfer 2020年5月18日 16:12

ASP.NET Core 3.1 MVC で ASP.NET Identity をユーザー認証に使用し、Email Confirmation と Password Recovery を実装する際、自分的に気になったことを備忘録として書いておきます。

(.NET Framework 版については特に気を付ける点はなさそうなので割愛します。手抜きですみません。基本的な枠組みが Visual Studio のテンプレートで生成されるコードに含まれており、Microsoft のチュートリアル「ログイン、電子メール確認、パスワード リセットを使用して安全な ASP.NET MVC 5 Web アプリを作成する (C#)」に従って生成されたコードに手を加えれば特に苦労なく実装できると思います。 2021/3/14 追記: SendGrid は id と password による認証をサポートしなくなりました。なので、.NET Framework 版 MVC5 の場合もこの記事に書いた Api Key による認証とする必要がありますので注意してください

基本的には Microsoft のチュートリアル Account confirmation and password recovery in ASP.NET Core に従って実装しますので、詳しくはそのドキュメントを見てください。そのドキュメントの説明だけではよく分からないと思った点を以下に書きます。

(1) プロジェクトの作成

Visual Studio 2019 のテンプレートを利用して ASP.NET Core 3.1 MVC アプリを作成します。

認証関係のソースコードに手を加えるので、アプリ作成の際に認証は「認証なし」のままとしておき、スキャフォールディング機能を利用して ASP.NET Identity を実装し、ソースコードがプロジェクトに含まれるようにします。

詳しい手順は先の記事「ASP.NET Identity で MySQL 利用 (CORE 版)」のステップ「(1) プロジェクトの作成」とステップ「(2) ASP.NET Core Identity の実装」を見てください。

(2) Email Confirmation 実装前の動き

上のステップ (1) でプロジェクトを作成直後(Email Confirmation を実装する前)のユーザー登録の仕組み説明します。それを理解しておいた方がこの後の説明が分かりやすいと思いますので。仕組みをご存じの方はここはスキップして (3) に進んでください。

デフォルトで options.SignIn.RequireConfirmedAccount が true に設定されており(そのコードは Areas/Identity/IdentityHostingStartup.cs にあります)、ユーザー登録に使用する Register ページも Email Confirmation を使用する前提でコーディングされています。ちなみに false に設定すると登録後直ちにログイン状態になります。

Areas/Identity/Pages/Account フォルダにある Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。登録画面にユーザーが email と password を入力して送信するとそのメソッドでユーザー登録処理が行われます。

ユーザー登録に成功すると RegisterConfirmation ページにリダイレクトされます。(実際はリダイレクト前に _emailSender.SendEmailAsync メソッドが実行されますが、デフォルトではメール送信機能は実装されていないので何も起こりません。何故かエラーも出ません)

RegisterConfirmation ページは下の画像のように表示されます。

RegisterConfirmation ページ

上の画像の "Click here to confirm your account" には ConfirmEmail ページへのリンクが張ってあり、クエリ文字列に UserId とトークンが設定されています。

リンクをクリックすると ConfirmEmail ページが GET 要求され Areas/Identity/Pages/Account フォルダにある ConfirmEmail.cshtml.cs の _userManager.ConfirmEmailAsync(user, code) メソッドで Email Confirmation が実行(AspNetUsers テーブルの EmailConfirmed フィールドが True に更新)されます。

Email Confirmation に成功すると下の画像の ConfirmEmail ページが表示されます。

ConfirmEmail ページ

この後、右上の [Login] をクリックして Login ページを表示し、登録した email と password を入力してログインできるようになります。

Email Confirmation を実装してメールを送信できるようにすると、ConfirmEmail ページへのリンク(UserId とトークンのクエリ文字列を含む)が本文に含まれたメールが自動的に送信され、それをクリックすると ConfirmEmail ページが GET 要求されて Email Confirmation が実行されるようになります。

RegisterConfirmation ページへのリダイレクトは同様に起こりますが、それには ConfirmEmail ページへのリンクは含まれず、単に "Please check your email to confirm your account." と表示されただけものになります。

以下のステップ (3) 以降にメールの自動送信を含む Email Confirmation の実装方法を述べます

(3) API Key の入手

チュートリアルに従って、メールの送信には SendGrid を利用します。ユーザー登録して API Key を入手してください。

API Key の入手

SendGrid は基本的に有償ですが、開発目的限定で送信する量が少ない場合は Free のサービスでよさそうです。Pricing のページを見てください。

(4) SendGrid のインストール

NuGet を利用してプロジェクトに SendGrid をインストールします。

SendGrid をインストール

上の画像ではバージョンが 9.12.6 となっていますが、自分が試したときはそれが最新版だったということで、意図的にそのバージョンを選んだわけではありません。

(5) User Secrets

メールの差出人と SendGrid の API Key をユーザーシークレットに設定します。チュートリアルは dotnet コマンドを使っていますが、Visual Studio のソリューションエクスプローラーから開いて設定できます。

ユーザーシークレットの管理

上の画像のように操作すると secrets.json ファイルが開くので、それに以下のようにメールの差出人と SendGrid の API Key を設定して保存します。

secrets.json

appsettings.json に設定しても取得できますが、チュートリアルでは、開発時に、Password その他の機密データを秘密の場所に保存できることを紹介する意味でユーザーシークレットを使っているようです。

ユーザーシークレットは開発時の開発マシンのでの利用に限られます。secrets.json ファイルは開発マシンの開発者のユーザープロファイルフォルダー内にあるので当たり前ですが・・・ 詳しくは Safe storage of app secrets in development in ASP.NET Core を見てください。

チュートリアルには AuthMessageSenderOptions クラスの追加と Startup.ConfigureServices へのコードの追加を行うように書かれています。

まず、プロジェクトルート直下に Services フォルダを追加し、その中にクラスファイル AuthMesageSenderOptions.cs を追加します。コードはチュートリアルのものをコピペします。

// AuthMessageSenderOptions クラスの追加

namespace MySQLIdentity.Services
{
    public class AuthMessageSenderOptions
    {
        public string SendGridUser { get; set; }
        public string SendGridKey { get; set; }
    }
}

次に、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。

// Startup,cs の ConfigureServices メソッド内に追加

services.Configure<AuthMessageSenderOptions>(Configuration);

これらは、AuthMessageSenderOption クラスを、必要に応じて初期化してそのオブジェクトを DI によって注入するために必要なもののようです。

その設定により、secrets.json ファイルに設定した差出人と API Key 情報は、EmailSender クラスを初期化する際に DI によって注入された AuthMessageSenderOption オブジェクトから取得できます。

なお、secrets.json ファイルを使わなくても、普通に appsettings.json に差出人と API Key 情報を設定しても同様に DI によって取得できます(secrets.json と appsettings.json の両方から探してくるようです)。運用環境にデプロイする際に appsettings.json に移すということではなさそうですが、実際どうすべきかは調べておらず不明です。

(6) EmailSender クラスの実装

メールを送信するため IEmailSender インターフェイスを継承した EmailSender クラスを実装します。

チュートリアルに従ってクラスファイル Services/EmailSender.cs を追加し、それにチュートリアルのコードをそのままコピペしてください。

念のため、以下にコードを貼っておきます。

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Threading.Tasks;

namespace MySQLIdentity.Services
{
    public class EmailSender : IEmailSender
    {
        public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
        {
            Options = optionsAccessor.Value;
        }

        public AuthMessageSenderOptions Options { get; }

        public Task SendEmailAsync(string email, string subject, string message)
        {
            return Execute(Options.SendGridKey, subject, message, email);
        }

        public Task Execute(string apiKey, string subject, string message, string email)
        {
            var client = new SendGridClient(apiKey);
            var msg = new SendGridMessage()
            {
                From = new EmailAddress("xxxxx", Options.SendGridUser),
                Subject = subject,
                PlainTextContent = message,
                HtmlContent = message
            };
            msg.AddTo(new EmailAddress(email));

            // Disable click tracking.
            // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
            msg.SetClickTracking(false, false);

            return client.SendEmailAsync(msg);
        }
    }
}

上のコード From = new EmailAddress("xxxxx", Options.SendGridUser) の xxxxx をメールの差出人のメールアドレスに変更してください。

EmailSender クラスが初期化される際に DI により AuthMessageSenderOptions オブジェクトが注入され、差出人と API Key 情報が取得できるようにコーディングされています。

EmailSender クラスは Register ページと ForgotPassword ページでメールを送信する際に使用されます。その際、DI により EmailSender クラスを初期化してそのオブジェクトを注入するコーディングになっています。それを可能にするため、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。

// Startup.cs の ConfigureServices メソッド内に追加

services.AddTransient<IEmailSender, EmailSender>();

上記の Startup.cs への設定を忘れるとメール送信機能は動きませんので注意してください。

(7) Register ページの修正

Areas/Identity/Pages/Account フォルダの Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。

ユーザー登録に成功すると変数 callbackUrl に UserId とトークン情報がクエリ文字列として設定された /Account/ConfirmEmail ページへの URL 文字列が設定されます。

デフォルトでは html の a 要素の href 属性にその URL が設定されたメール本文がユーザーが email として登録したメールアドレスに送信されます。

ユーザーが使っているメーラーが html を表示できる設定になっていれば、その a 要素で "clicking here" と表示されるリンクをクリックすれば ConfirmEmail ページが GET 要求され、クエリ文字列に設定された UserId とトークンで Email Confirmation が行われ、ブラウザが立ち上がって ConfirmEmail ページが表示されます。

ユーザーのメーラーが html の表示を許可していない可能性を考えて、html の a 要素を組み立てて送るのではなく、URL そのものを送信したい場合は以下のようにします。

await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
    "Please confirm your account by: " + 
    HtmlEncoder.Default.Encode(callbackUrl).Replace("&amp;", "&"));

その際、その際クエリ文字列内の &amp; は & にしないと ConfirmEmail の OnGetAsync(string userId, string code) の code が null になってしまうので注意してください。

上記の結果、送信されたメールは以下の画像のようになります。(メーラーは Windows Live メールです)

送信されたメール

(8) RegisterConfirmation ページの修正

Areas/Identity/Pages/Account フォルダの RegisterConfirmation.cshtml.cs を開いて OnGetAsync メソッドを以下のように修正します。

RegisterConfirmation ページの修正

Email Confirmation を実装したので ConfirmationEmail ページへのリンクを表示する必要はありません。そのリンクを生成するコードをコメントアウトします。

加えて DisplayConfirmAccountLink を falses に設定し、ConfirmationEmail ページへのリンクではなくて単に "Please check your email to confirm your account." と表示されるようにします。

(9) Password Recovery

Password Recovery はユーザーがパスワードを忘れてしまっても復活できる手段を提供するものです。

上のステップ「(6) EmailSender クラスの実装」 まででメールは送信されるようになっていますので、基本的な機能だけなら既存のコードに手を加える必要はありません。

なお、スキャフォールディングで実装されるコードはメール送信機能を実装しないと Password Recovery が使えないコーディングになっていますので注意してください。(メール送信機能なしで Password Recovery を利用するにはコードを書き換える必要があります)

以下に Password Recovery がどのような動きになるかを説明しようと思いましたが、長くなりすぎるので別の記事「Password Recovery (CORE 版)」に書きました。興味があればそちらを見てください。

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar