ASP.NET Core MVC 記載まとめ　【初学者編】1


##    はじめに
ASP.NET Core MVCのプロジェクトを作成し、
データベース（MySQL）へ接続する機能を備えたRazor Pagesアプリケーションを構築します。

------------------------------------
[前回の記事はこちら](https://qiita.com/nano357/items/d8564fe79c600127eb45)


--------------------------------------------------------------


前提条件
------------------------------------
XAMPP のインストール：以下のサイト様等をご参考ください。
[XAMPP のインストール方法](https://jitera.com/ja/insights/5555)

------------------------------------
MySQL の設定：以下のサイト様等をご参考ください。
[SQL のインストールと設定](https://qiita.com/tatsukikane/items/43c88428b9e1d6641c20)

------------------------------------
データベースの作成：以下のサイト様等をご参考ください。

[データベース作成方法](https://qiita.com/okayu__11/items/9f4785b1b54305cce133)


------------------------------------

※標準設定にてXAMPPにて、MySQLをインストールしてる場合

コマンドプロンプト（cmd）での操作
````
Windowsのスタートメニューを開き、「cmd」と入力してEnterを押します。
````

1.mysqlのあるディレクトリ内へ移動
````
cd C:\xampp\mysql\bin
````

2.MySQLに接続
````
mysql -u root -p
````

3.データベースの作成
````
CREATE DATABASE productdb;
````

--------------------------------------------------------------
## <font color="green">① プロジェクトの作成</font>

1.1･･･ Visual Studio インストールとワークロードの選択
```markdown
 1. Visual Studio Installerを起動し、以下のワークロードを選択します。
   ●  ASP.NET と Web 開発
      : Webアプリケーション開発用
        ASP.NET Core などのフレームワークを使用して
        Webアプリケーションを開発します。

   ●  .NET デスクトップ開発
      : デスクトップアプリケーション開発を補助する機能
        Entity Frameworkなどのデスクトップアプリケーションの
        機能を利用するために追加します。

````
```markdown
 2. 個別のコンポーネント選択
   ●「.NET SDK」
      : プロジェクトが .NET Core をターゲットにしています。

   ●「.NET 8.0 ランタイム（長期サポート）」
      : 最新の .NET バージョンをサポートするために選びます。

````
````
 3. プロジェクトの種類を選択
   ● Visual Studio の「新規プロジェクト」ウィンドウで、「ASP.NET Core Web アプリ」を選択します。
   
   ● フレームワークのバージョン: .NET 8 を選択
   
   ● 「認証なし」を選択します。これによりユーザー認証機能が自動的に含まれません。

   ● 「MVC」を選択し、Model-View-Controllerを使用したWebアプリケーションを作成します。

````
```markdown
 4. プロジェクト名の設定
   ● プロジェクト名を「ProductsApp」に設定し、「場所」を「C:\temp\ASP.NET」に指定します。
````
```markdown
 ※ 認証なしの特徴
   ●  ユーザー認証機能がない
      - ログインやユーザー登録のページ が作成されません。
      - ASP.NET Identity（ASP.NETのユーザー認証と管理システム）も含まれません。

   ●  アクセス制限がない
     - [Authorize] 属性などによるアクセス制御が設定されません。誰でもページにアクセスできます。

   ●  外部認証機能なし
     - GoogleやFacebookなどのOAuthやフォーム認証は設定されていません。必要に応じて後で手動で追加する必要があります。

````

------------------------------------
1.2･･･フォルダ構成
````
/ProductsApp
├── Pages
│   ├── Products    //フォルダを新規作成
│       ├── Index.cshtml   //全ての製品の一覧を表示します。
│       ├── Create.cshtml  //新しい製品の登録を行います。
│       ├── Edit.cshtml    //既存の製品情報の編集を行います。
│       ├── Delete.cshtml  //製品の削除を行います。
│       ├── Details.cshtml //特定の製品の詳細を表示します。
│       ├── Index.cshtml.cs     // 製品一覧ページのモデル
│       ├── Create.cshtml.cs    // 新しい製品作成ページのモデル
│       ├── Edit.cshtml.cs      // 製品編集ページのモデル
│       ├── Delete.cshtml.cs    // 製品削除ページのモデル
│       ├── Details.cshtml.cs   // 製品詳細ページのモデル
│
├── Models    //フォルダを新規作成
│   ├── Product.cs                 //エンティティクラス
│   ├── ApplicationDbContext.cs    //DbContextクラス
│
````
--------------------------------------------------------------
## <font color="green">② データベース接続設定</font>

2.1･･･ データベース接続設定 (appsettings.json)

appsettings.json にMySQLを使用するための接続文字列を設定
```markdown
接続文字列の例:
   ●  server: データベースサーバーのホスト名 (通常は localhost)
   ●  port: MySQLが使用するポート番号 (デフォルトは 3306)
   ●  database: 使用するデータベース名 (productdb など)
   ●  userid: MySQLで使用するユーザー名 (root など)
   ●  password: MySQLユーザーのパスワード
````

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "ProductContext": "Server=127.0.0.1;Port=3306;Database=productdb;Uid=root;Pwd=;"
  }
}



{
  "ConnectionStrings": {
    "ProductContext": "server=127.0.0.1;port=3306;database=productdb;userid=root;password="
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

````

-----------------------------------
2.3･･･ モデル (Models/Product.cs)

製品情報をデータベースにマッピングします。

```csharp
using System.ComponentModel.DataAnnotations;

namespace ProductsApp.Models
{
    // Productクラスは製品のデータ構造を表します
    public class Product
    {
        // ID プロパティ（プライマリキー）
        public int Id { get; set; }

        // 製品名は必須で最大100文字
        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        // 製品の価格、必須
        [Required]
        public decimal Price { get; set; }

        // 製品の説明、必須ではない
        public string Description { get; set; }
    }
}
````

------------------------------------
2.2･･･ DbContext クラスの作成 (Models/ApplicationDbContext.cs)

````
ソリューションエクスプローラーでプロジェクト名を右クリックし、
「追加」>「新しいフォルダー」を選択します。
フォルダー名を「Models」にします。

DbContext を継承し、データベースの接続と操作を管理するクラスを作成します。

````

```csharp
using Microsoft.EntityFrameworkCore;

namespace ProductsApp.Models
{
    // ProductContextは、製品データベースとのやり取りを管理するDbContextクラスです。
    public class ProductContext : DbContext
    {
        // コンストラクタでDbContextOptionsを受け取り、親クラスに渡す
        public ProductContext(DbContextOptions<ProductContext> options) : base(options)
        {
        }

        // Productsテーブルを表すDbSet。製品データが格納される場所。
        public DbSet<Product> Products { get; set; }
    }
}
````

------------------------------------
2.2･･･ パッケージがインストール

2.2.1. Visual Studio の「ソリューション エクスプローラー」でプロジェクトを右クリックし、「NuGet パッケージの管理」を選択します。

2.2.2. 「参照」タブで、以下のパッケージを検索してインストールします。
````
   ●  Pomelo.EntityFrameworkCore.MySql
   ●  Microsoft.EntityFrameworkCore.Tools
````

------------------------------------
2.3･･･ 接続処理の実装手順 (Program.cs)

データベース接続の設定を追加し、アプリケーション起動時にMySQLへ接続できるように、コンテキストを追加します。

```csharp
using Microsoft.EntityFrameworkCore;
using ProductsApp.Models;  // データベースコンテキストの名前空間

var builder = WebApplication.CreateBuilder(args);

// MySQL用のDbContextを登録
builder.Services.AddDbContext<ProductContext>(options =>
    options.UseMySql(
        builder.Configuration.GetConnectionString("ProductContext"),
        // MariaDB 10.4.32バージョンを指定
        new MySqlServerVersion(new Version(10, 4, 32))
    )
);

// MVC を使うためのサービスを登録
builder.Services.AddControllersWithViews();

// Razor Pagesのサービスを追加
builder.Services.AddRazorPages();  // ここを追加

var app = builder.Build();

// ミドルウェアの設定
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

// HTTPS へのリダイレクトと静的ファイルの提供
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// Razor Pagesのルート設定
app.MapRazorPages();

// ルートアクセス時のリダイレクト
app.MapGet("/", () => Results.Redirect("/Products"));

app.Run();
````
--------------------------------------------------------------
## <font color="green">③ CRUD機能の自動生成</font>

4.1 マイグレーションの実行
パッケージマネージャーコンソールを開き、次のコマンドを実行します。

4.1.1. マイグレーションの有効化
````
Add-Migration InitialCreate
````

4.1.2. データベースの更新
````
Update-Database
````
 - マイグレーションを実行すると、テーブルがデータベースに作成され、データの操作が可能になります



--------------------------------------------------------------
## <font color="green">⑤ Razorページモデルの構成</font>

````
「Pagesフォルダー」を右クリックし、
「追加」>「新しいフォルダー」を選択します。

フォルダー名を「Products」にします。

それぞれのRazorページモデルのビューと
ビハインドファイル（モデル）を作成します。

````
------------------------------------

5.1.1. Index.cshtml.cs
```csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ProductsApp.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ProductsApp.Pages.Products
{
    // IndexModelはすべての製品データをデータベースから取得し、表示するページモデル
    public class IndexModel : PageModel
    {
        private readonly ProductContext _context;

        // 製品リストを格納するためのプロパティ
        public IList<Product> Products { get; set; }

        // コンストラクタでデータベースコンテキストを注入
        public IndexModel(ProductContext context)
        {
            _context = context;
        }

        // 非同期メソッドでデータベースから製品情報を取得
        public async Task OnGetAsync()
        {
            Products = await _context.Products.ToListAsync();
        }
    }
}
````
----------------
5.1.2. Index.cshtml
```html
@page
@model ProductsApp.Pages.Products.IndexModel

<!-- 製品のリストを表示するための見出し -->
<h1>製品一覧</h1>

<!-- 製品が存在する場合、リストを表示 -->
@if (Model.Products.Count > 0)
{
    <table class="table">
        <thead>
            <tr>
                <th>名前</th>
                <th>価格</th>
                <th>アクション</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in Model.Products)
            {
                <tr>
                    <td>@product.Name</td>
                    <td>@product.Price.ToString("C")</td>
                    <td>
                        <!-- 編集ページへのリンク -->
                        <a asp-page="Edit" asp-route-id="@product.Id">編集</a> |
                        <!-- 削除ページへのリンク -->
                        <a asp-page="Delete" asp-route-id="@product.Id">削除</a> |
                        <!-- 詳細ページへのリンク -->
                        <a asp-page="Details" asp-route-id="@product.Id">詳細</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
else
{
    <p>製品が見つかりませんでした。</p>
}

<!-- 新規作成ページへのリンク -->
<a asp-page="Create">新しい製品を作成</a>
````

------------------------------------
5.2.1. Create.cshtml.cs
```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using ProductsApp.Models;
using System.Threading.Tasks;

namespace ProductsApp.Pages.Products
{
    // CreateModelは新しい製品を作成するためのページモデル
    public class CreateModel : PageModel
    {
        private readonly ProductContext _context;

        // 製品の作成フォームをバインドするためのプロパティ
        [BindProperty]
        public Product Product { get; set; }

        // コンストラクタでデータベースコンテキストを注入
        public CreateModel(ProductContext context)
        {
            _context = context;
        }

        // Postメソッドで新しい製品をデータベースに追加
        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Products.Add(Product);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}
````
----------------
5.2.2. Create.cshtml
```html
@page
@model ProductsApp.Pages.Products.CreateModel

<h1>製品の作成</h1>

<!-- 製品作成フォーム -->
<form method="post">
    <div>
        <label asp-for="Product.Name"></label>
        <input asp-for="Product.Name" />
        <span asp-validation-for="Product.Name"></span>
    </div>
    <div>
        <label asp-for="Product.Price"></label>
        <input asp-for="Product.Price" />
        <span asp-validation-for="Product.Price"></span>
    </div>
    <button type="submit">作成</button>
    <a asp-page="Index">戻る</a>
</form>

<!-- フォームのバリデーション用スクリプト -->
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

````
------------------------------------
5.3.1. Edit.cshtml.cs
```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ProductsApp.Models;
using System.Threading.Tasks;

namespace ProductsApp.Pages.Products
{
    // EditModelは既存の製品を編集するためのページモデル
    public class EditModel : PageModel
    {
        private readonly ProductContext _context;

        // 編集対象の製品をバインドするためのプロパティ
        [BindProperty]
        public Product Product { get; set; }

        // コンストラクタでデータベースコンテキストを注入
        public EditModel(ProductContext context)
        {
            _context = context;
        }

        // 指定されたIDに基づいて製品を取得
        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Product = await _context.Products.FirstOrDefaultAsync(m => m.Id == id);

            if (Product == null)
            {
                return NotFound();
            }
            return Page();
        }

        // Postメソッドで変更をデータベースに保存
        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Attach(Product).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(Product.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return RedirectToPage("./Index");
        }

        // 製品が存在するかを確認するメソッド
        private bool ProductExists(int id)
        {
            return _context.Products.Any(e => e.Id == id);
        }
    }
}
````
----------------
5.3.2. Edit.cshtml
```html
@page
@model ProductsApp.Pages.Products.EditModel

<h1>製品の編集</h1>

<form method="post">
    <input type="hidden" asp-for="Product.Id" /> <!-- IDを隠しフィールドとして追加 -->

    <div>
        <label asp-for="Product.Name"></label>
        <input asp-for="Product.Name" />
        <span asp-validation-for="Product.Name"></span>
    </div>
    <div>
        <label asp-for="Product.Price"></label>
        <input asp-for="Product.Price" />
        <span asp-validation-for="Product.Price"></span>
    </div>
    <div>
        <label asp-for="Product.Description"></label>
        <input asp-for="Product.Description" />
        <span asp-validation-for="Product.Description"></span>
    </div>
    <button type="submit">保存</button>
    <a asp-page="Index">戻る</a>
</form>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
````
------------------------------------

5.4.1. Delete.cshtml.cs
```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ProductsApp.Models;
using System.Threading.Tasks;

namespace ProductsApp.Pages.Products
{
    // DeleteModelは既存の製品を削除するためのページモデル
    public class DeleteModel : PageModel
    {
        private readonly ProductContext _context;

        // 削除対象の製品をバインドするためのプロパティ
        [BindProperty]
        public Product Product { get; set; }

        // コンストラクタでデータベースコンテキストを注入
        public DeleteModel(ProductContext context)
        {
            _context = context;
        }

        // 指定されたIDに基づいて製品を取得
        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Product = await _context.Products.FirstOrDefaultAsync(m => m.Id == id);

            if (Product == null)
            {
                return NotFound();
            }
            return Page();
        }

        // Postメソッドで製品を削除
        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Product = await _context.Products.FindAsync(id);

            if (Product != null)
            {
                _context.Products.Remove(Product);
                await _context.SaveChangesAsync();
            }

            return RedirectToPage("./Index");
        }
    }
}
````
----------------
5.4.2. Delete.cshtml
```html
@page
@model ProductsApp.Pages.Products.DeleteModel

<h1>製品の削除</h1>

<!-- 削除確認メッセージ -->
<h3>製品を削除しますか？</h3>
<div>
    <h4>製品名: @Model.Product.Name</h4>
    <p>価格: @Model.Product.Price.ToString("C")</p>
</div>

<!-- 削除確認フォーム -->
<form method="post">
    <button type="submit">削除</button>
    <a asp-page="Index">キャンセル</a>
</form>
````
------------------------------------
5.5.1. Details.cshtml.cs
```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ProductsApp.Models;
using System.Threading.Tasks;

namespace ProductsApp.Pages.Products
{
    // DetailsModelは特定の製品の詳細を表示するためのページモデル
    public class DetailsModel : PageModel
    {
        private readonly ProductContext _context;

        // 表示する製品をバインドするためのプロパティ
        public Product Product { get; set; }

        // コンストラクタでデータベースコンテキストを注入
        public DetailsModel(ProductContext context)
        {
            _context = context;
        }

        // 指定されたIDに基づいて製品を取得
        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Product = await _context.Products.FirstOrDefaultAsync(m => m.Id == id);

            if (Product == null)
            {
                return NotFound();
            }
            return Page();
        }
    }
}
````

----------------
5.4.2. Details.cshtml
```html
@page
@model ProductsApp.Pages.Products.DetailsModel

<h1>製品の詳細</h1>

<!-- 製品情報の表示 -->
<div>
    <h4>製品名: @Model.Product.Name</h4>
    <p>価格: @Model.Product.Price.ToString("C")</p>
</div>

<!-- 戻るボタン -->
<a asp-page="Index">戻る</a>
````