WebSurfer's Home

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

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

by WebSurfer 2021年10月3日 14:15

データベースに MySQL を利用して、Micorsoft の ASP.NET Core MVC のチュートリアル「ASP.NET Core MVC の概要」および「パート 4、ASP.NET Core MVC アプリにモデルを追加する」に従ってアプリを作る方法を書きます。

Movie アプリ

チュートリアルは、モデル(エンティティ)クラスの定義だけをプロジェクトに追加し、その後 CRUD 操作に必要な Controller、View などのコードやデータベースを Visual Studio 2019 のデザイナを利用して自動生成するというものです。初心者でも簡単に作成できるアプリながら、データベースを利用する ASP.NET Core MVC の基本的かつ重要な部分が含まれていると思います。

チュートリアルは SQL Server を使用する前提で書かれていますが、それを MySQL に変える場合はどのようにするかを以下に書きます。MySQL 本体は先の記事「MySQL をインストールしました(その 3)」に書きました既存の 8.0.19 を使います。

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

チュートリアル「ASP.NET Core MVC の概要」の通り、Visual Studio 2019 のテンプレートを利用して対象のフレームワークは .NET 5.0認証「なし」の ASP.NET Core MVC アプリを作成します。

対象のフレームワークは .NET Core 3.1 でも良いですが、そうした場合は NuGet パッケージのバージョンの選び方に注意してください。間違うとスキャフォールディングでエラーになります。

また、認証を「個別のアカウント」にすると SQL Server を利用した Entity Framework 関係のパッケージがインストールされ話がややこしくなりますので、まずは認証は「なし」でやってみることをお勧めします。

(2) モデルの定義とスキャフォールディングの実行

チュートリアル「パート 4、ASP.NET Core MVC アプリにモデルを追加する」に従って、モデル(エンティティ)クラスの定義をプロジェクトに既存の Models フォルダに追加します。

using System;
using System.ComponentModel.DataAnnotations;

namespace MySqlMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

次に、Microsoft.EntityFrameworkCore.Design を NuGet からインストールします。この記事では、この記事を書いた時点での最新版 5.0.10 を使いました。対象のフレームワークを .NET Core 3.1 にした場合は、Microsoft.EntityFrameworkCore.Design のバージョンは 3.1.18 にするのが良さそうです。5.0.10 を使うとスキャフォールディングでエラーになると思います。

チュートリアルの通り Visual Studio でスキャフォールディングを実行すると以下の操作が自動的に行われます。

  1. 他に必要な NuGet パッケージ(Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools, Microsoft.VisualStudio.Web.CodeGeneration.Design)の追加
  2. Data フォルダにコンテキストクラスの作成
  3. Startup.cs ファイルの ConfigureServices メソッドにコンテキストの登録
  4. appsettings.json ファイルへの接続文字列の追加
  5. CRUD 操作に必要な Controller / View 一式の生成

SQL Server の場合は上記でプロジェクトは完成ですが、MySQL を利用する場合は上の 1, 3, 4 に以下の変更を行う必要があります。

(3) NuGet パッケージの変更

スキャフォールディングで自動的に追加された NuGet パッケージ Microsoft.EntityFrameworkCore.SqlServer を削除します。 代わりに MySql.Data.EntityFrameworkCore をインストール・・・しようとしたら非推奨とのこと。 代替えパッケージが MySql.EntityFrameworkCore とのことなのでその最新版 5.0.5 をインストールしました。その結果が以下の画像です。

NuGet パッケージ

(スキャフォールディングで自動的に追加される Microsoft.EntiryFrameworkCore.Tools のバージョンは 5.0.9 ですが、Migration 操作の際ランタイムのバージョン 5.0.10 より古いという警告が出るので、上の画像では 5.0.10 に更新しています)

(4) Startup.cs ファイルの修正

スキャフォールディング操作で Startup.cs ファイルの ConfigureServices メソッドに自動的にコンテキストが登録されますが、それは SQL Server 用なので、以下のように UseSqlServer を UseMySQL に変更します。

UseSqlServer を UseMySQL に変更

コンテキストクラスの登録はコントローラーへの DI に必要です。登録してあれば、フレームワークがクライアントからの要求を受けてコントローラーを初期化する際、コンテキストクラスを初期化してコンストラクタ経由で渡してくれます。

(5) 接続文字列を MySQL 用に変更

スキャフォールディング操作で appsettings.json ファイルに接続文字列が自動生成されますが、それは SQL Server 用なので MySQL 用に変更します。

接続文字列の変更

データベース名は任意です。上の画像のように database=MySqlMovie というようにデータベース名を指定すると、Entity Framework Code First の機能を使って MySqlMovie という名前のデータベースを新たに生成し、そこに必要なテーブルを生成してくれます。

(6) Add-Migration の実行

Visual Studio のパッケージマネージャーコンソールで Add-Migration InitialCreate コマンドを実行します。結果、以下の画像の通り InitialCreate クラスが Migrations フォルダに自動生成されます。

Add-Migration の実行結果

ちなみに、InitialCreate という名前は任意に指定できます。指定した名前で xxxxx_InitialCreate.cs (xxxxx は作成日時) という名前のファイルが作成され、それに InitialCreate という名前のクラスが定義されます。

(7) Update-Database の実行

Visual Studio のパッケージマネージャーコンソールで Update-Database コマンド を実行します。これにより以下の画像の通り MySQL にデータベースが生成されます。

Update-Database の実行結果

接続文字列で database=MySqlMovie とした通り MySqlMovie という名前でデータベースが生成され、InitialCreate クラスの name: "Movie" で指定された名のテーブルが生成されています。

(8) プロジェクトの実行

上記 (7) まででアプリは完成です。プロジェクトを実行して Create 画面を表示して 2 つレコードを追加し、Index 画面でその一覧を表示したのがこの記事の一番上の画像です。

MySQL の Movie テーブルにも追加結果が反映されています。

Movie テーブルの内容

Tags: , , , ,

CORE

アクションメソッドと構造不定の JSON (CORE)

by WebSurfer 2021年8月23日 18:54

クライアントから ASP.NET Core MVC や Web API のアクションメソッドに送信されてくる JSON 文字列の構造が不定の場合、どのように受け取って処理できるかという話を書きます。(.NET Core 3.x 以降の話です。.NET Framework および .NET Core 2.x 以前は未検証・未確認です)

JSON 文字列から指定した name の value を取得

(注: 以下は MVC のアクションメソッドを例に取って書いていますが、Web API のアクションメソッドでもモデルバインディングの関係は全く同じです)

クライアントから送信されてくる JSON 文字列の構造が常に同じなら、先の記事「JSON 文字列から C# のクラス定義生成」に書いたような手段で C# のクラス定義を生成し、それをアクションメソッドの引数に設定してやれば、フレームワーク組み込みのモデルバインダが JSON 文字列を C# のオブジェクトにデシリアライズしてバインドしてくれます。

しかし、JSON 文字列の構造が不定の場合は C# のクラス定義ができません。それでも ASP.NET のフレームワークがモデルバインディングしてくれるようにするにはどのようにしたらいいでしょう?

構造が不定とは言っても必要な情報のある JSON 文字列 {"name" : "value"} の name は事前に分かっているのであれば、それに該当する value はJsonElement オブジェクトとして取得できます(詳しくは先の記事「JSON 文字列から指定した name の value を取得」を見てください)。それで目的が果たせるのであればその方向で進めるのが良さそうです。

クライアントから送信されてきた構造不定の JSON 文字列を、アクションメソッドでどのように受け取って JsonElement オブジェクトにデシリアライズするかですが、それはフレームワーク組み込みのモデルバインダが自動的に行ってくれます。

具体例は下のサンプルコードの Full アクションメソッドを見てください。JsonElement 型の引数を設定するだけで、自動的にクライアントから送信されてきた JSON 文字列をデシリアライズしてバインドしてくれます。

using Microsoft.AspNetCore.Mvc;
using MvcCore5App4.Models;
using System.Text.Json;

namespace MvcCore5App4.Controllers
{
    public class JsonController : Controller
    {
        [HttpPost]
        public IActionResult Partial([FromBody] Rootobject postedObject)
        {
            JsonElement element = postedObject.Response.Result.ObjectArray;
            return Content(element.ToString());
        }

        [HttpPost]
        public IActionResult Full([FromBody] JsonElement postedObject)
        {
            JsonElement? element = FindElementByName(postedObject, "ObjectArray");

            string returnValue = (element != null) ?
                element.Value.ToString() : "ObjectArray は取得できません";

            return Content(returnValue);
        }


        private JsonElement? FindElementByName(JsonElement jelem, string name)
        {
            if (jelem.ValueKind == JsonValueKind.Object)
            {
                foreach (JsonProperty jprop in jelem.EnumerateObject())
                {
                    if (jprop.Name == name)
                    {
                        return jprop.Value;
                    }
                    else
                    {
                        JsonElement? retVal = FindElementByName(jprop.Value, name);
                        if (retVal != null)
                        {
                            return retVal;
                        }
                    }
                }
            }
            else if (jelem.ValueKind == JsonValueKind.Array)
            {
                foreach (JsonElement jelemInArray in jelem.EnumerateArray())
                {
                    JsonElement? retVal = FindElementByName(jelemInArray, name);
                    if (retVal != null)
                    {
                        return retVal;
                    }
                }
            }
            else
            {
                return null;
            }
            return null;
        }
    }
}

送信した JSON 文字列のサンプルは以下の通りです。先の記事「JSON 文字列から指定した name の value を取得」に書いたものと同じです。

{
  "Title": "This is my title",
  "Response": {
    "Version": 1,
    "StatusCode": "OK",
    "Result": {
      "Profile": {
        "UserName": "SampleUser",  
        "IsMobileNumberVerified": false,
        "MobilePhoneNumber": null
      },
      "ObjectArray" : [
          {"Code": 2000,"Description": "Fail"},
          {"Code": 3000,"Description": "Success"}
      ],
      "lstEnrollment": "2021-2-5"
    },
    "Message": {
      "Code": 1000,      
      "Description": "OK"
    }
  },
  "StringArray" : ["abc", "def", "ghi"]
}

JSON 文字列まるまる全部の構造が不定なわけではなく、特定の項目だけが不定の場合は別の方法があります。例えば、上の JSON 文字列の中で "ObjectArray" の value のみが不定だとします。

その場合は、上の JSON 文字列から Visual Studio の機能を利用して生成したクラス定義の中の ObjectArray プロパティの型を JsonElement に書き換えてやります。具体的には以下の通りです。

using System.Text.Json;

namespace MvcCore5App4.Models
{
    // Visual Studio を利用して JSON 文字列から生成したクラス定義
    public class Rootobject
    {
        public string Title { get; set; }
        public Response Response { get; set; }
        public string[] StringArray { get; set; }
    }

    public class Response
    {
        public int Version { get; set; }
        public string StatusCode { get; set; }
        public Result Result { get; set; }
        public Message Message { get; set; }
    }

    public class Result
    {
        public Profile Profile { get; set; }

        // ObjectArray の value の構造が不定ということで書き換え
        //public Objectarray[] ObjectArray { get; set; }
        public JsonElement ObjectArray { get; set; }
        
        public string lstEnrollment { get; set; }
    }

    public class Profile
    {
        public string UserName { get; set; }
        public bool IsMobileNumberVerified { get; set; }
        public object MobilePhoneNumber { get; set; }
    }

    // ObjectArray クラスの定義は不要なのでコメントアウト
    //public class Objectarray
    //{
    //    public int Code { get; set; }
    //    public string Description { get; set; }
    //}

    public class Message
    {
        public int Code { get; set; }
        public string Description { get; set; }
    }
}

上の Rootobject クラスをアクションメソッドの引数に設定してやれば、ObjectArray プロパティには JsonElement 型、それ以外は上の定義に指定した通りの型にデシリアライズしてくれます。

そのアクションメソッドの具体例は上のコントローラのサンプルコードの Partial アクションメソッドを見てください。アクションメソッドの実行結果が上の画像です。

Tags: , , , , ,

CORE

Link Tag Helper と Script Tag Helper (CORE)

by WebSurfer 2021年8月9日 11:22

ASP.NET Core アプリで Content Delivery Network (CDN) から css や JavaScript のリソースを取得する際、CDN からの取得に失敗した場合にフォールバック(代替えリソース)を取得するのに便利な Link Tag Helper, Script Tag Helper があります。

その概要は Mocrosoft のドキュメント「ASP.NET Core のリンク タグ ヘルパー」と「ASP.NET Core のスクリプト タグ ヘルパー」に書いてあるのを見つけました・・・が、それを読んだだけでは理解できませんでした。(汗)

なので、実際にコードを書いて動かしてどういう動きになるのかを調べて、その結果分かったことを以下に備忘録として残しておきます。

まず、上に紹介したドキュメントのサンプルコードにある integrity 属性と crossorigin 属性とは何かを書きます。それらは ASP.NET Core の Tag Helper の機能ではなく、html に備わっている改ざん防止機能です。詳しい説明は MDN のドキュメント「サブリソース完全性」と「HTML crossorigin 属性」にあります。

ユーザーがあらかじめ CDN から取得したリソースのハッシュ値を計算してそれを integrity 属性に設定しておくと、ブラウザが CDN に要求をかけて応答として取得したリソースのハッシュ値を計算して比較し、一致しない場合はブラウザはそのリソースをロードしないという動きになるようです。それにより悪意のある第三者による改ざん攻撃のリスクを軽減するものだそうです。

integrity 属性に設定するハッシュ値の取得方法は、MDN の記事に書いてあるように、オンラインで SRI Hash Generator というサービスから取得できます。自分も試してみましたが、期待通りの結果が得られました。

crossorigin 属性の方は、MDN のドキュメントでは自分には意味不明でした。(涙) いろいろ調べてみると、サブリソース完全性に以下のように書いてあり、CORS と関係があるようです。

"サブリソース完全性の検証において、サブリソースが埋め込まれる文書のオリジン以外から提供されたリソースについては、ブラウザーはオリジン間リソース共有 (CORS) を使用してリソースに追加のチェックを行い、オリジンがリソースがリクエストしたオリジンに共有されることを許可しているかどうかを確認します。"

実際に検証してみると、integrity 属性を付与した場合は crossorigin="anonymous" 属性も一緒に付与しないと、CDN から応答が返ってきてもブラウザはそれを取り込むことはないという結果になりました。

crossorigin="anonymous" 属性を付与した場合は要求に origin ヘッダが含まれるようになります。それにより CORS によるチェックが行われ、応答ヘッダの Access-Control-Allow-Origin: * を確認してスクリプトを読み込むという動作になります。

ちなみに、crossorigin="use-credentials" の場合は要求の credential mode が 'include' になり、そのモードでは Access-Control-Allow-Origin にワイルドカード '*' は許されてないので CORS ポリシーによりブロックされスクリプトは読み込まれません。

要するに、integrity 属性にハッ���ュ値を設定をして改ざん防止の効果を期待するなら、同時に crossorigin="anonymous" 属性の付与も必須ということのようです。

さらに、要求に Origin ヘッダが含まる場合はサーバー側で応答ヘッダに Access-Control-Allow-Origin を含めるということも必須になります。CDN がそれに対応してないと「サブリソース完全性」による検証はできないということになるので、使用する CDN が対応しているかどうか調べる必要がありそうです。

なお、ASP.NET Core の Link Tag Helper と Script Tag Helper にとって integrity 属性と crossorigin 属性の設定は必須ではありません。無くてもフォールバック機能は動きます。

次に、ASP.NET Core の Tag Helper 独自の asp-fallback-* という属性の説明をします。名前に fallback とあるように、それらの属性はすべてフォールバックを行うためのものです。

(1) Link Tag Helper の場合

asp-fallback-href: CDN の css ファイルがロードできなかった場合のフォールバック(代替え)css ファイルの URL を指定します。

asp-fallback-test-*: CDN の css ファイルに含まれる特定のクラス名、プロパティ名とその値を指定します。例えば、以下のクラスが含まれる場合、

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

asp-fallback-test-* は以下のように設定します。

<link rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
  asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  asp-fallback-test-class="sr-only" 
  asp-fallback-test-property="position"
  asp-fallback-test-value="absolute"
  crossorigin="anonymous"
  integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" />

上の Link Tag Helper が html にレンダリングされると以下のようになります (一部略)。

<link rel="stylesheet" 
  href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
   crossorigin="anonymous" 
  integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" />

<meta name="x-stylesheet-fallback-test" content="" class="sr-only" />

<script>
  !function (a,b,c,d) {
    var e, f=document,
        g = f.getElementsByTagName("SCRIPT"),
        h = g[g.length - 1].previousElementSibling,
        i = f.defaultView && f.defaultView.getComputedStyle ? 
              f.defaultView.getComputedStyle(h) : h.currentStyle;

    if ( i && i[a] !== b) 
      for (e = 0; e < c.length; e++)
        f.write('<link href="'+c[e]+'" '+d+"/>")}
  ("position","absolute",["/lib/bootstrap/dist/css/bootstrap.min.css"], 
   "rel=\u0022stylesheet\u0022 crossorigin=\u0022anonymous\u0022 ... ");
</script>

meta タグとスクリプトを見てください。それらによって CDN の css が asp-fallback-test-* に設定したクラス名、プロパティ名とその値を含んでいるかがテストされ、テスト結果 NG と判断された場合は asp-fallback-href に指定される URL のフォールバック css ファイルを取得するよう link 要素を document に書き込みます。

なお、integrity 属性を使っての検証 NG の場合は CDN から送られてきた css はロードされませんので、 テスト結果は NG と判断され、フォールバック css ファイルを取得するようになります。

(2) Script Tag Helper の場合

asp-fallback-src: CDN の js ファイルがロードできなかった場合のフォールバック(代替え)js ファイルの URL を指定します。

asp-fallback-test: CDN の js ファイルに含まれる特定の JavaScript オブジェクト名を指定します。例えば、window.jQuery という名前の JavaScript オブジェクトが含まれる場合、asp-fallback-test="window.jQuery" とします。以下の例を見てください。

<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"
  asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  asp-fallback-test="window.jQuery"
  crossorigin="anonymous"
  integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2">
</script>

上の Script Tag Helper が html にレンダリングされると以下のようになります (一部略)。

<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"
  crossorigin="anonymous" 
  integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2">
</script>

<script>
  (window.jQuery || 
   document.write("\u003Cscript src=\u0022/lib/jquery/dist/jquery.min.js\u0022 ..."));
</script>

asp-fallback-test に設定した window.jQuery が定義されていない場合は CDN から送られてきた js がロードできなかったということなので、document.write(...) が実行されて、asp-fallback-src に指定される URL の js ファイルを取得するよう script 要素を document に書き込みます。

Link Tag Helper の場合と同様に、integrity 属性を使っての検証 NG の場合は CDN から送られてきた js はロードされませんので、上のスクリプトで window.jQuery は未定義となり、フォールバック js ファイルを取得するようになります。

Tags: , , ,

CORE

About this blog

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

Calendar

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

View posts in large calendar