WebSurfer's Home

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

スキャフォールディング機能 (CORE)

by WebSurfer 2020年3月16日 14:47

ASP.NET Core 3.1 MVC プロジェクトで、既存のデータベースからコンテキストクラスとテーブルクラスを生成し、それをベースに Visual Studio 2019 のスキャフォールディング機能を使って Create, Read, Update, Delete (CRUD) 操作を行うための Controller と View を自動生成する手順を書きます。

Create

先の記事「スキャフォールディング機能」で .NET Framework MVC5 プロジェクトのケースを紹介しましたが、今回の記事はそれと同等なことを ASP.NET Core 3.1 MVC プロジェクトで行うものです。

上の画像は作成した ASP.NET Core 3.1 MVC アプリの編集 (Create) 画面を Edge から呼び出して表示したものです。(理由不明ですが一部の項目のコードが生成されていません。Create 以外は OK)

先の MVC5 の記事と同様、この記事でも既存のデータベースとして Microsoft のサンプルデータベース Northwind を使用し、その中の Products テーブルの CRUD 操作を行うことにします。

大まかな手順は以下の通りです。

  1. SQL Server にサンプルデータベース Northwind をアタッチして接続できるようにしておく。
  2. Visual Studio Community 2019 のテンプレートを使用して ASP.NET Core 3.1 MVC プロジェクトを生成。
  3. リバースエンジニアリングによって Northwind データベースからコンテキストクラスとテーブルクラスを生成してプロジェクトに追加する。(EF Core では Visual Studio の ADO.NET Entity Data Model のウィザードで EDM を作るということはできません)
  4. 追加したコンテキストクラスとテーブルクラスをベースに、スキャフォールディング機能を使って CRUD 操作用の Controller と View を自動生成する。

以下に、Visual Studio で上の操作を行った際に表示された画像を貼って要点を書いておきます。

(1) リバースエンジニアリング

リバースエンジニアリング

Visual Studio のパッケージマネージャーコンソールで、Microsoft ドキュメント「リバースエンジニアリング」を参考に、既存の SQL Server データベース Northwind からコンテキストクラスとテーブルクラスを生成します。(各パラメータについては、こちらの記事 Scaffold-DbContext の方がまとまっていてわかりやすいかも)

パラメータの内 -Connection と -Provider の設定は必須です。

-Connection に設定する接続文字列ですが、文字列に空白がある場合(普通あるはず)は接続文字列全体をクォーテーション " で囲います。バックスラッシュ \ はエスケープする必要はありません。上の画像を見てください。

-Provider に設定するプロバイダ名は、SQL Server を使っている場合は Microsoft.EntityFrameworkCore.SqlServer とします。

その他のパラメータ設定はオプションです。上の画像の例では -ContextDir でコンテキストクラスを生成するディレクトリ、-OutputDir でテーブルクラスを生成するディレクトリ、-Tables で含めるテーブルの名前(今回は Products, Categories, Suppliers を指定)、-DataAnnotations で各プロパティにデータアノテーション属性を付与するように指定しています。

成功すると Build succeeded. と表示されます。

(2) 生成されたクラス

生成されたクラス

リバースエンジニアリングの結果、上のステップ (1) のパラメータで指定した通り DAL ディレクトリにコンテキストクラス NorthwindContext.cs、Models ディレクトリにテーブルクラス Products.cs, Categories.cs, Suppliers.cs が生成されています。

(3) スキャフォールディング

スキャフォールディング

Visual Studio のソリューションエクスプローラーで Controllers を右クリックして[追加(D)]⇒[コントローラー(T)...]⇒[新規スキャフォールディングアイテムの追加]⇒[Entity Framework を使用したビューがある MVC コントローラー]と進んで、出てきたダイアログでスキャフォールディングの設定を行います。

この記事では、モデルクラス(M) には CRUD 対象とする SQL Server のテーブルに対応する Products クラスを、データコンテキストクラス(D) には NorthwindContext.cs の NorthwindContext クラスを設定しています。

[追加]ボタンをクリックすると SQL Server の Products テーブルの CRUD に必要な Controller / Action Methods と View が一式自動生成されます。

(4) コードの変更・追加

この状態で実行すると "InvalidOperationException: Unable to resolve service for type 'NorthwindContext' while attempting to activate 'ProductsController'" というエラーになるはずです。

なので、Code First で作った時と同様な形になるよう、以下の変更・追加を行います。

  1. 接続文字列を appsettings.json に追加。JSON 形式で名前は "NorthwindConnection" とし、値はステップ (1) で設定したものと同じにします。なお、JSON 文字列なので \ はエスケープして \\ にする必要があることに注意してください。
  2. NorthwindContext.cs の NorthwindContext クラスに自動生成されているコードの中から、引数を取らないコンストラクタ NorthwindContext() をコメントアウト。
  3. ProductsController に NorthwindContext オブジェクトを DI できるよう、Startup.cs の ConfigureServices メソッドに services.AddDbContext<NorthwindContext>( ... ) を追加。

上記 1 ~ 3 の変更・追加の後アプリを実行すれば期待通り動きます。

但し、理由は不明ですが、スキャフォールディン機能で自動生成された Create.cshtml には項目 UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel のコードがありません。そこはだけは自力でコードを書く必要がありました。

上記ステップ (3) のスキャフォールディング前に、ステップ (4) のコードの変更・追加をしないとダメかもと思い、(3), (4) の順序を逆にして試してみましたが結果は同じでした。

その他の View、Index.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml には全項目のコードが生成されていました。

Tags: , , ,

CORE

ASP.NET Core MVC の Unicode Escape Sequence (UES)

by WebSurfer 2020年3月11日 14:40

ASP.NET Core 3.1 MVC の Controller.Json メソッドを使って .NET オブジェクトを JSON 文字列にシリアライズすると日本語の文字は Unicode Escape Sequence (以下 UES と書きます) という形にエスケープされます。

JSON 文字列

UES というのは \uxxxx という形で表される Unicode 文字で、xxxx はその文字の Unicode コードになります。以下に、UES になる理由と、UES ではなく UTF-8 で(即ち、エスケープしないで)JSON 文字列に出力する方法を書きます。

UES となる理由はシリアライザでエスケープ処理が行われているからのようで、日本語の文字に限らず非 ASCII 文字は全てデフォルトで UES になるそうです。そのことは Microsoft のドキュメント How to serialize and deserialize (marshal and unmarshal) JSON in .NET に書いてありました。(注: そのドキュメントに書いてありますが、ASCII 文字でも HTML-sensitive characters はエスケープされます。例えば、< とか > はそれぞれ \u003C および \u003E になります)

ちなみに ASP.NET Core 3.1 Web API では UES にはならず、日本語の文字も UTF-8 で出力されます。何故 MVC と Web API で違う結果になるのかは不明です。(たぶん、MVC では HtmlEncode、JavaScriptEncode、UrlEncode の 3 つのエンコーダーすべてでエスケープされるようになっている、Web API では以下のサンプルコードのように BMP の文字はエスケープ対象から外す設定になっているからではなかろうかと想像しています)

日本語の文字も UES ではなく UTF-8 で出力する、即ちエスケープされないように設定するにはどうするかを以下に書きます。

Controller.Json メソッドには第 2 引数に JsonSerializerOptions クラスを設定するオーバーロードがありますが、その JsonSerializerOptions.Encoder プロパティでエスケープ対象から外す文字の設定が可能なようです。

アクションメソッド単位で JsonSerializerOptions を設定する方法は Microsoft のドキュメント Configure System.Text.Json-based formatters を参照してください。具体的には以下のサンプルコードのように設定します。

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebAPI.Data;
using Microsoft.EntityFrameworkCore;

// エスケープ回避を設定するため追加
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;

namespace WebAPI.Controllers
{
  public class HomeController : Controller
  {
    private readonly BloggingContext _context;

    public HomeController(BloggingContext context)
    {
      _context = context;
    }

    public async Task<IActionResult> Json()
    {
      // Core は遅延ローディングが働かないので注意
      var list = await _context.Blogs.
                       Include(b => b.Posts).ToListAsync();

      // BMP 全てをエスケープしないよう設定
      // (WriteIndented はオマケ)
      return Json(list, new JsonSerializerOptions
      {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
        WriteIndented = true,
      });
    }
  }
}

上記のようなアクションメソッド単位でなく、Startup.cs でプロジェクト全体に設定することもできます。それには、AddControllersWithViews メソッドに以下のように AddJsonOptions メソッドを適用してやります。

services.AddControllersWithViews().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.WriteIndented = true;
    options.JsonSerializerOptions.Encoder = 
        JavaScriptEncoder.Create(UnicodeRanges.All);
});

上のコードでは、JavaScriptEncoder.Create(UnicodeRange[]) メソッドの引数に UnicodeRanges クラスの All プロパティを渡して BMP(Basic Multilingual Plane・・・U+0000 から U+FFFF の範囲)の文字をエスケープ対象から外すように設定しています。(WriteIndented プロパティの設定は UES とは関係ありません。これにより JSON 文字列がインデントされ見やすくなるので追加しました)

結果は以下のようになります。

JSON 文字列

なお、UnicodeRanges クラスの説明では、"現時点では、UnicodeRange クラスでサポートされているのは、基本多言語面 (BMP) の名前付き範囲のみです" とのことですので注意してください。

上のサンプルコードのように BMP 全てをエスケープしないよう設定しても、例えば 𠀋 という文字 (u2000b) は BMP にありませんが、それを JSON にシリアライズすると、 \uD840\uDC0B (サロゲートペアの形) になります。Web API でも同じです。

さらに、BMP の範囲内の文字でも全角空白 (U+3000) はエスケープされます。理由は GitHub の記事 Can't serializ the '\u3000' when using with UnicodeRanges.All に書いてありますが、U+3000 のみならず Space_Separator [Zs] category に属する文字は U+0020 (半角空白) 以外はブロックされる仕様になっているからだそうです。ブロックする理由は "their potential to cause problems or errors in consuming applications." だそうです・・・が、JSON 文字列の一部に過ぎないのにエスケープすると何故 "problems or errors" が避けられるのか納得できませんけど。何か別の使い方を考慮しているのかもしれません。


【2023/12/23 追記】

<. >, & などの HTML-sensitive 文字や + など文字は、上のサンプルコードのように BMP 全てをエスケープしないよう設定しても、やはりエスケープされます。

それらの文字もエスケープされないようにするには JavaScriptEncoder.UnsafeRelaxedJsonEscaping を使います。

ただし、Microsoft のドキュメント「すべての文字のシリアル化」に書いてあるように、セキュリティの問題がありますので注意が必要です。

なお、上に書いた全角空白 (U+3000) は、JavaScriptEncoder.UnsafeRelaxedJsonEscaping を使っても、やはりエスケープされます。

Tags: , , , , ,

CORE

jQuery ajax で部分ビューの呼出・表示 (CORE)

by WebSurfer 2020年3月7日 12:25

jQuery ajax を使って、部分ビューを応答として返すアクションメソッドにフォームデータを POST 送信し、返ってきた部分ビューの html をページ内の指定の場所にレンダリングする方法を書きます。

jQuery ajax で部分ビューの呼出・表示

先の記事「ASP.NET Core MVC と Ajax ライブラリ」では、Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js を利用して、Ajax で部分ビューを呼び出してページ内の指定の場所に表示する方法を書きました。

それと機能的に同じことを、Microsoft の Ajax ライブラリの助けを借りないで、jQuery ajax を利用して実装する方法です。

CSRF 用の隠しフィールドのデータを含めてフォームデータを全て jQuery ajax を使って POST 送信し、部分ビュー用のアクションメソッドに付与した [ValidateAntiForgeryToken] 属性での検証ができるようにしていることに注目してください。

問題はフォームデータをクライアント側でどのように取得するかですが、jQuery の .serialize() メソッドを使うと form データを application/x-www-form-urlencoded 形式の文字列として取得できます。それを jQuery ajax の data パラメータに設定して POST 送信してやればアクションメソッドで受けることができます。

Controller と View のサンプルコードを以下に書いておきます。コードは ASP.NET Core 3.1 MVC のものです。.NET Framework MVC5 の場合はライブラリ jquery.unobtrusive-ajax.js と AjaxHelper を使う方が簡単でよさそうです。

使っているのは Microsoft が提供する Northwind サンプルデータベースの Customers テーブルです。その CompanyName をドロップダウンリストに表示し、ユーザーが選択してボタンをクリックすると部分ビューを呼び出して、選択した Customers のレコードの詳細を指定した場所(下の View のコードで言うと <div id="result"></div> の中)に書き出すものです。

Controller / Action Method

先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものと同じです。

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
  public class AjaxController : Controller
  {
    private readonly NorthwindContext _context;

    public AjaxController(NorthwindContext context)
    {
      _context = context;
    }

    // jQuery ajax で下のアクションメソッド Details
    // を呼んで詳細 Customer データの部分ビューを表示。
    // CSRF 用の隠しフィールドのデータも送信して検証で
    // きるようにする。
    public IActionResult Index2()
    {
      // 全部取得すると長すぎるので Take(10) した
      var list = _context.Customers.Take(10);
      ViewData["customers"] = 
        new SelectList(list, "CustomerId", "CompanyName");
      return View();
    }

    // 部分ビュー用のアクションメソッド
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Details(string customerid)
    {
      if (string.IsNullOrEmpty(customerid))
      {
        return NotFound();
      }

      var customer = _context.Customers.Find(customerid);

      if (customer == null)
      {
        return NotFound();
      }

      return PartialView(customer);
    }
  }
}

View (Index2.cshtml)

ボタンには input type="submit" タイプ(クリックすると form を submit する)と input type="button" タイプ(クリックしても何も起こらない)の 2 つを配置して、両方期待通り動くことを確認してみました。

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

<h1>Index2</h1>

<form id="form1" asp-action="Details" 
      asp-controller="Ajax" method="post">

    <select id="customerid" name="customerid" 
            asp-items="@ViewBag.customers">
    </select>

    <input type="submit" value='詳細表示 (type="submit")' />
    <input id="button1" type="button"
                         value='詳細表示 (type="button")' />

</form>

<div id="result"></div>

@section Scripts {
    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="submit")] ボタンをクリック
        // したときの処理

        // ボタンクリックで form が submit されるという
        // 動きになるので、form 要素の submit イベントに
        // リスナをアタッチして jQuert ajax で処理する
        $("#form1").on("submit", function (event) {
            // submit されては困るのでキャンセル
            event.preventDefault();

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $(this).serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {                    
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>

    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="button")] ボタンをクリック
        // したときの処理

        // ボタン要素の click イベントにリスナをアタッチ
        // して jQuert ajax で処理する
        $("#button1").on("click", function () {

            // input type="button" タイプのボタンはクリ
            // ックしても from が submit されることは
            // ないので event.preventDefault(); は不要

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $("#form1").serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>
}

部分ビューのコードは、先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものとほとんど同じなので省略します。

Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js は ASP.NET Core MVC ではサポートされてないのかもしれません。なので、上記のように jQuery ajax を使うのが正解のような気がします。

Tags: , , ,

CORE

About this blog

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

Calendar

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

View posts in large calendar