第28章 Web 社員管理アプリ:一覧表示
この章の目的
Section titled “この章の目的”この章から、本研修のもう一つの中心題材 ── Web 版 社員管理アプリ を作り始めます。 第 23〜25 章で作った Windows フォーム版と 同じ業務(CRUD)を、今度は Web で実装 していきます。
本章のゴールは、employees テーブルの全件を ブラウザ上の表(HTML テーブル) として一覧表示することです。
部署名は departments テーブルと JOIN して一緒に取得します。第 23 章でやったのと同じ SQL です。
第 23 章(Windows フォーム) 第 28 章(Web)
Form1_Load /Employees アクセス ↓ ↓ EmployeeRepository.GetAll() EmployeeRepository.GetAll() ↓ ↓ SqlConnection (Windows 統合認証) SqlConnection (SQL 認証) ↓ ↓ JOIN で社員 + 部署名 JOIN で社員 + 部署名 ↓ ↓ DataGridView.DataSource に渡す List<Employee> を View に渡す ↓ ↓ フォームに表が表示される ブラウザに HTML 表が表示されるRepository の中身はほとんど同じ で、変わるのは「呼び出し元」と「結果の渡し先」です。 第 23 章を手元に置きながら読み比べると、共通点と違いがはっきり見えます。
| 章 | テーマ | 主な機能 |
|---|---|---|
| 第 28 章(本章) | 一覧表示 | employees 全件を JOIN で取得し HTML 表で表示 |
| 第 29 章 | 検索 | 名前・部署で絞り込み(WHERE + パラメータ化) |
| 第 30 章 | 編集・更新 | 行の編集、UPDATE。新規登録・削除も実装 |
本章では「一覧表示」までを完成させ、新規登録・削除のボタンは画面に枠だけ置く スタイル(第 23 章と同じ)にします。
この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- 第 27 章のプロジェクトをひな型として、
MvcEmployeeAppを作成できる Employeeクラス(DTO)に姓名分離・部署名込みのプロパティを持たせられるEmployeeRepository.GetAll()でemployeesをdepartmentsと JOIN して取得できる- 未実装メソッドを
NotImplementedExceptionで枠だけ用意する設計の意図を説明できる EmployeesController.Index()で Repository を呼び、List<Employee>を View に渡せる- View で
@foreachを使い、HTML の<table>で一覧表示を組み立てられる decimalの通貨表示や、DateTimeのフォーマットを Razor で書ける- 「新規登録」「削除」ボタンを disabled 状態のプレースホルダー として置ける
- Windows フォーム版(第 23 章)と Web 版の構造の違いを比較できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | ASP.NET Core Web アプリ(Model-View-Controller) |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | KadaiWebApp(第 28 章で作成・29/30 でも使い続ける) |
| プロジェクト名 | MvcEmployeeApp(第 28 章で作成 → 29=検索 → 30=CRUD を追加) |
| ベースとなる章 | 第 27 章(Ch27_MvcDepartmentList の構造をコピーして起点にする) |
| データベース | SQLServer 2022(TrainingDB) |
| 認証方式(DB) | SQL 認証(app_user、第 27 章で作成済み) |
| NuGet パッケージ | Microsoft.Data.SqlClient |
csproj の Nullable は disable に変更してください(第 1 章「1-1」参照)
第 28〜30 章は 1 つのプロジェクトを育てていきます
Web 社員管理アプリは、第 28 章で作る
MvcEmployeeAppプロジェクト 1 つ を、第 29 章(検索)・第 30 章(編集・更新)でも 開いて機能を足していく 形で進めます。章ごとに作り直しません(第 23〜25 章の Windows 版と同じ進め方です)。各章の区切りでコミットして提出します。
作業前チェック
Section titled “作業前チェック”- 第 27 章を Git に提出済みである
-
app_userでTrainingDBに接続できる(第 27 章 27-2) -
TrainingDBのemployeesテーブルに 10 件のサンプルデータが入っている(第 16 章) - 第 27 章
Ch27_MvcDepartmentListの/Departmentsがブラウザで表示できる - 第 23 章
EmployeeRepository.GetAll()のコードが手元にある(参考用)
28-1 この章で作る画面
Section titled “28-1 この章で作る画面”完成形は次のとおりです。
社員一覧
10 件の社員が登録されています。
| 社員ID | 氏名 | 部署名 | メール | 入社日 | 給与 ||--------|--------------|----------------|-------------------------|------------|---------------|| 1001 | 山田 二郎 | 総務 | yamada.jiro@example.com | 2001/04/01 | ¥500,000 || 1002 | 佐藤 昭夫 | 営業 | sato.akio@example.com | 2002/04/01 | ¥500,000 || ... |
[新規登録] [削除] ← 枠のみ(無効)機能の範囲:
| 機能 | 本章での扱い |
|---|---|
一覧表示(employees 全件) | ✅ 実装する |
部署名表示(departments JOIN) | ✅ 実装する |
| 検索 | 第 29 章 |
| 編集・更新 | 第 30 章 |
| 新規登録 | 第 30 章(本章ではボタンの枠だけ) |
| 削除 | 第 30 章(本章ではボタンの枠だけ) |
28-2 プロジェクトを作成・準備する
Section titled “28-2 プロジェクトを作成・準備する”第 27 章で作った Ch27_MvcDepartmentList は、Web + DB の基本構造(SQL 認証・Repository・一覧表示)ができている状態です。これを ひな型 にして、第 28〜30 章で育てる本体プロジェクト MvcEmployeeApp(ソリューション KadaiWebApp)を作ります。第 27 章の Department 一式をコピーして起点にし、その上に社員一覧を乗せていきます。
手順 1:プロジェクトを新規作成する
Section titled “手順 1:プロジェクトを新規作成する”第 27 章 27-3 と同じ手順で、新しい ASP.NET Core MVC プロジェクトを作ります。
| 項目 | 値 |
|---|---|
| テンプレート | ASP.NET Core Web アプリ(Model-View-Controller) |
| ソリューション名 | KadaiWebApp |
| プロジェクト名 | MvcEmployeeApp |
| フレームワーク | .NET 8.0 |
| 認証の種類 | なし |
| HTTPS 用の構成 | 既定のまま |
作成したら、MvcEmployeeApp.csproj を <Nullable>disable</Nullable> に変更します。
手順 2:NuGet で Microsoft.Data.SqlClient を追加する
Section titled “手順 2:NuGet で Microsoft.Data.SqlClient を追加する”第 27 章と同じ手順です(プロジェクト右クリック → NuGet パッケージの管理 → 検索 → インストール)。
手順 3:第 27 章の成果物をコピーする
Section titled “手順 3:第 27 章の成果物をコピーする”以下のファイルを第 27 章のプロジェクトから コピーまたは再作成 します。
| ファイル | コピー元(ch 27) | 修正点 |
|---|---|---|
Models/Department.cs | Ch27_MvcDepartmentList/Models/Department.cs | namespace を MvcEmployeeApp.Models に変更 |
Data/DepartmentRepository.cs | Ch27_MvcDepartmentList/Data/DepartmentRepository.cs | namespace を MvcEmployeeApp.Data に変更 |
Controllers/DepartmentsController.cs | 同上 | namespace を MvcEmployeeApp.Controllers に変更 |
Views/Departments/Index.cshtml | 同上 | @model の型名を MvcEmployeeApp.Models.Department に変更 |
「Models」「Data」フォルダがまだ無い場合
プロジェクトを右クリック → 追加 → 新しいフォルダー で
Models(あれば不要)、Dataを作ってからコピーしてください。
手順 4:動作確認
Section titled “手順 4:動作確認”ここで一度 F5 を押し、/Departments が前章と同じように表示されることを確認してください。
第 27 章のひな型が動く状態 になっていれば、これから社員一覧を上に乗せていけます。
最終的な構成(本章を終えたとき)
Section titled “最終的な構成(本章を終えたとき)”MvcEmployeeApp├─ Controllers/│ ├─ HomeController.cs│ ├─ DepartmentsController.cs (ch 27 から)│ └─ EmployeesController.cs (この章で作る)├─ Data/│ ├─ DepartmentRepository.cs (ch 27 から)│ └─ EmployeeRepository.cs (この章で作る)├─ Models/│ ├─ Department.cs (ch 27 から)│ └─ Employee.cs (この章で作る)├─ Views/│ ├─ Home/│ ├─ Shared/│ ├─ Departments/Index.cshtml (ch 27 から)│ └─ Employees/Index.cshtml (この章で作る)└─ Program.cs28-3 Employee モデルクラスを作る
Section titled “28-3 Employee モデルクラスを作る”Models フォルダに Employee.cs を追加します。
namespace MvcEmployeeApp.Models;
public class Employee{ public int EmployeeId { get; set; } public string LastName { get; set; } = ""; public string FirstName { get; set; } = ""; public string Email { get; set; } = ""; public DateTime HireDate { get; set; } public decimal Salary { get; set; } public int DepartmentId { get; set; } public string DepartmentName { get; set; } = "";
public string FullName => $"{LastName} {FirstName}";}第 23 章 23-5 の Employee クラスと ほぼ同じ です。
| プロパティ | 役割 |
|---|---|
EmployeeId | 主キー |
LastName / FirstName | 姓名分離(章間整合ルール 1-2) |
Email | メールアドレス(UNIQUE) |
HireDate | 入社日 |
Salary | 給与(decimal、章間整合ルール 2-2) |
DepartmentId | 所属部署 ID |
DepartmentName | JOIN で取得する部署名(テーブルには無い) |
FullName | 姓 + 名 を結合した読み取り専用プロパティ |
FullNameは計算プロパティ
=>でメソッドのように書いていますが、これは 読み取り専用プロパティ の省略構文です(第 9 章)。LastNameやFirstNameが変わると、自動でFullNameも変わったように見えます。
28-4 EmployeeRepository を作る
Section titled “28-4 EmployeeRepository を作る”Data フォルダに EmployeeRepository.cs を追加します。
第 27 章の DepartmentRepository とほぼ同じ構造で、SQL だけ社員 + JOIN に差し替えます。
using Microsoft.Data.SqlClient;using MvcEmployeeApp.Models;
namespace MvcEmployeeApp.Data;
public class EmployeeRepository{ private const string ConnectionString = "Server=localhost;Database=TrainingDB;User Id=app_user;Password=AppUserPass1!;TrustServerCertificate=true;";
public List<Employee> GetAll() { List<Employee> list = new List<Employee>();
const string sql = @" SELECT e.employee_id, e.last_name, e.first_name, e.email, e.hire_date, e.salary, e.department_id, d.department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id ORDER BY e.employee_id";
using SqlConnection conn = new SqlConnection(ConnectionString); conn.Open();
using SqlCommand cmd = new SqlCommand(sql, conn); using SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) { Employee employee = new Employee(); employee.EmployeeId = reader.GetInt32(reader.GetOrdinal("employee_id")); employee.LastName = reader.GetString(reader.GetOrdinal("last_name")); employee.FirstName = reader.GetString(reader.GetOrdinal("first_name")); employee.Email = reader.IsDBNull(reader.GetOrdinal("email")) ? "" : reader.GetString(reader.GetOrdinal("email")); employee.HireDate = reader.GetDateTime(reader.GetOrdinal("hire_date")); employee.Salary = reader.IsDBNull(reader.GetOrdinal("salary")) ? 0m : reader.GetDecimal(reader.GetOrdinal("salary")); employee.DepartmentId = reader.IsDBNull(reader.GetOrdinal("department_id")) ? 0 : reader.GetInt32(reader.GetOrdinal("department_id")); employee.DepartmentName = reader.IsDBNull(reader.GetOrdinal("department_name")) ? "" : reader.GetString(reader.GetOrdinal("department_name")); list.Add(employee); }
return list; }
public int Insert(Employee employee) { // 第 30 章で実装します throw new NotImplementedException("Insert は第 30 章で実装します。"); }
public int Delete(int employeeId) { // 第 30 章で実装します throw new NotImplementedException("Delete は第 30 章で実装します。"); }}コードのポイント
Section titled “コードのポイント”| ポイント | 説明 |
|---|---|
| 接続文字列 | 第 27 章と同じ app_user を使用(private const string) |
LEFT JOIN departments | 部署が未割り当ての社員も結果に含める。INNER JOIN だと部署 NULL の社員が消える |
reader.GetOrdinal("列名") | 列名から列番号を取る。SELECT の列順を変えても壊れにくい |
IsDBNull チェック | email / salary / department_id / department_name は NULL があり得るので毎回確認 |
Insert / Delete の枠 | 本章では NotImplementedException を投げるだけ。第 30 章で実装する |
第 23 章とコードが似ているのは意図的
Repository は「DB アクセスをまとめる層」なので、Windows でも Web でも書き方が変わりません。 上の層(フォーム or Controller)から見ると、
GetAll()を呼べばList<Employee>が返ってくる ── ただそれだけです。 第 23 章のコードと差分を取って読み比べると、本章は 接続文字列だけが違う ことが分かります。
なぜ Insert / Delete の枠を最初から置くのか
Section titled “なぜ Insert / Delete の枠を最初から置くのか”完成形(第 30 章)で必要になる メソッドの「形」だけ を先に決めておくと、
- 本章で
EmployeeRepositoryを使う側のコードに、後から インターフェース変更が無い - 「いま実装していないが、ここに入る予定」が コード上で明示 される
- 第 30 章で
NotImplementedExceptionを本物の SQL に置き換えるだけで完成する
という利点があります。Windows フォーム版(第 23 章)と同じやり方です。
ここで一度ビルド確認(まだ実行はできません)
Model と Repository まで書けました。画面(View)も Controller もまだ無いのでブラウザでは動かせませんが、
Ctrl + Shift + Bで ビルドが通ること だけ確認しておきましょう。次の Controller・View をそろえてから 28-7 で実行します。
28-5 EmployeesController を作る
Section titled “28-5 EmployeesController を作る”Controllers フォルダに EmployeesController.cs を追加します。
using Microsoft.AspNetCore.Mvc;using MvcEmployeeApp.Data;using MvcEmployeeApp.Models;
namespace MvcEmployeeApp.Controllers;
public class EmployeesController : Controller{ public IActionResult Index() { EmployeeRepository repository = new EmployeeRepository(); List<Employee> employees = repository.GetAll(); return View(employees); }}第 27 章の DepartmentsController と 構造はまったく同じ です。
| 行 | やっていること |
|---|---|
new EmployeeRepository() | Repository を作る |
repository.GetAll() | DB から全件取得 |
return View(employees) | List を View に渡してレンダリング |
URL との対応
Section titled “URL との対応”| URL | Controller | Action | View |
|---|---|---|---|
/Employees | EmployeesController | Index() | Views/Employees/Index.cshtml |
/Employees/Index | 同上 | 同上 | 同上 |
第 26 章 26-6 で学んだルール「/Xxx/Yyy → XxxController.Yyy()」がそのまま当てはまります。
28-6 View を作る:社員一覧画面
Section titled “28-6 View を作る:社員一覧画面”まず Views フォルダの中に Employees フォルダ を作り、その中に Razor ビュー - 空 で Index.cshtml を追加します。
@model List<MvcEmployeeApp.Models.Employee>
@{ ViewData["Title"] = "社員一覧";}
<h1>社員一覧</h1>
<p>@Model.Count 件の社員が登録されています。</p>
<table class="table"> <thead> <tr> <th>社員ID</th> <th>氏名</th> <th>部署名</th> <th>メール</th> <th>入社日</th> <th>給与</th> </tr> </thead> <tbody> @foreach (MvcEmployeeApp.Models.Employee emp in Model) { <tr> <td>@emp.EmployeeId</td> <td>@emp.FullName</td> <td>@emp.DepartmentName</td> <td>@emp.Email</td> <td>@emp.HireDate.ToString("yyyy/MM/dd")</td> <td>@emp.Salary.ToString("C0")</td> </tr> } </tbody></table>
<div class="mt-3"> <button type="button" class="btn btn-primary" disabled>新規登録</button> <button type="button" class="btn btn-danger" disabled>削除</button></div>
<p class="text-muted small mt-2">※ 新規登録・削除は第 30 章で実装します。</p>各部分のポイント:
@model List<...>
Section titled “@model List<...>”第 27 章と同じく、View に 複数件の Model を渡します。型を List<Employee> に変えただけです。
@emp.FullName
Section titled “@emp.FullName”Employee クラスで定義した読み取り専用プロパティが、Razor からそのまま呼べます。
@emp.LastName と @emp.FirstName を別々に書くより、一箇所にまとまっていて読みやすくなります。
@emp.HireDate.ToString("yyyy/MM/dd")
Section titled “@emp.HireDate.ToString("yyyy/MM/dd")”DateTime をそのまま表示すると 2001/04/01 0:00:00 のように時刻まで出てしまいます。
ToString("yyyy/MM/dd") で日付部分だけにフォーマットしています。
@emp.Salary.ToString("C0")
Section titled “@emp.Salary.ToString("C0")”decimal の通貨表示。C は通貨フォーマット、0 は小数桁数 0 を意味します。
日本のロケールでは ¥500,000 のように表示されます。
| フォーマット | 例 |
|---|---|
"C0" | ¥500,000 |
"C2" | ¥500,000.00 |
"N0" | 500,000 |
<button ... disabled>
Section titled “<button ... disabled>”disabled 属性を付けたボタンは、見た目だけ表示されて クリックできない 状態になります。
第 23 章 23-8 と同じ「枠だけ置く」設計です。第 30 章で disabled を外して、フォーム送信のリンクに置き換えます。
@model・@foreach・class="..."の役割は早見表にこれらの「決まり書き(おまじない)」の役割は 第 26 章 26-12「MVC のおまじない早見表」 にまとまっています。迷ったら参照してください。
class="table"/btn btn-primary/mt-3などは Bootstrap の見た目クラスで、付けると表やボタンが整って見えるだけです(中身は気にしなくて OK)。
28-7 動かしてみる
Section titled “28-7 動かしてみる”Controller(28-5)と View(28-6)までそろったので、実行します(先ほどのビルド確認に続く 2 回目=動作確認です)。 F5 で実行し、ブラウザのアドレスバーに次の URL を入れて開いてください。
https://localhost:xxxx/Employees10 件の社員(章間整合ルール 1-2 のサンプルデータ)が次のように表示されれば成功です。
社員一覧
10 件の社員が登録されています。
| 社員ID | 氏名 | 部署名 | メール | 入社日 | 給与 ||--------|--------------|----------------|-------------------------|------------|----------|| 1001 | 山田 二郎 | 総務 | yamada.jiro@example.com | 2001/04/01 | ¥500,000 || 1002 | 佐藤 昭夫 | 営業 | sato.akio@example.com | ... | ¥500,000 || ... | | | | | || 1010 | 斎藤 京子 | 開発 | saito.kyoko@example.com | ... | ¥400,000 |
[新規登録] [削除]※ 新規登録・削除は第 30 章で実装します。おすすめ:第 21 章のデバッグ操作で観察
EmployeesController.Index()とEmployeeRepository.GetAll()のwhile (reader.Read())の中にブレークポイントを置いて F5 で実行すると、Controller → Repository → reader.Read のループ → View レンダリングの流れが追えます。
第 27 章で作った
/Departmentsも動いていることを確認アドレスバーに
/Departmentsを入れて、部署一覧が変わらず表示されることも確認してください。 同じプロジェクトの中に 複数の Controller(Departments / Employees) が共存し、それぞれが別の URL で動いている状態です。
28-8 新規・削除ボタンの「枠」について
Section titled “28-8 新規・削除ボタンの「枠」について”第 23 章 23-8 と同じ考え方ですが、Web では HTML の disabled 属性で「枠だけ」を表現します。
disabled ボタンの意味
Section titled “disabled ボタンの意味”<button type="button" class="btn btn-primary" disabled>新規登録</button>| 属性 | 効果 |
|---|---|
disabled | クリック・フォーカスを受け付けない。グレーアウトで表示される |
| (なし) | 通常のボタン。クリックでフォーム送信などが起こる |
ブラウザの開発者ツール(F12)でこのボタンを見ると、disabled 属性が付いていて反応しないことが分かります。
なぜ枠を置くのか
Section titled “なぜ枠を置くのか”完成形(第 30 章)では、これらのボタンが実際に動くようになります。本章で「枠」を置いておくと:
- 完成形の 画面レイアウトが想像しやすい
- 第 30 章で
disabledを外し、<a asp-action="Create">などに置き換えるだけで動く - 学習者にとって「この機能は次章で実装されるんだな」というシグナルになる
Repository 側との対応
Section titled “Repository 側との対応”| 画面のボタン | Repository のメソッド | 本章での状態 |
|---|---|---|
| 新規登録ボタン(disabled) | Insert(Employee)(NotImplementedException) | 押せない / 呼ばれない |
| 削除ボタン(disabled) | Delete(int)(NotImplementedException) | 同上 |
「画面に枠」と「Repository に枠」が 対になって います。第 30 章で両方を実装に差し替えます。
28-9 Windows フォーム編との比較
Section titled “28-9 Windows フォーム編との比較”第 23 章(Windows 版社員一覧)と本章を並べてみます。
| 観点 | Windows フォーム(第 23 章) | Web MVC(本章) |
|---|---|---|
| Repository | EmployeeRepository.GetAll() | EmployeeRepository.GetAll()(中身ほぼ同じ) |
| 接続文字列 | Integrated Security=true | User Id=app_user;Password=...; |
| 呼び出し場所 | Form1_Load イベント | EmployeesController.Index() |
| 結果の渡し先 | DataGridView.DataSource | return View(list) |
| 画面の組み立て | フォームデザイナで DataGridView を配置 | .cshtml で <table> + @foreach |
| DataTime の表示 | DataGridView の列フォーマット | @emp.HireDate.ToString("yyyy/MM/dd") |
| 通貨表示 | DataGridView の列フォーマット | @emp.Salary.ToString("C0") |
| 新規/削除ボタン | Button を配置、Enabled = false | <button disabled> を配置 |
| 未実装メソッド | NotImplementedException を投げる | 同左 |
Repository(DB アクセス層)は ほぼコピーで動く、Controller / View は MVC らしい書き方に変える ── これが Windows → Web の発展パターンです。
よくあるつまずき
Section titled “よくあるつまずき”| つまずき | 原因 | 対応 |
|---|---|---|
Login failed for user 'app_user' | パスワード違い、SQL 認証モード無効 | 第 27 章 27-2 を再確認 |
/Employees で 404 | コントローラー名 / View のフォルダ名違い | EmployeesController + Views/Employees/Index.cshtml を確認 |
| 部署名列が空欄 | JOIN が効いていない / DepartmentName を Employee に詰めていない | SQL に d.department_name が含まれているか、Repository で IsDBNull 後に代入できているかを確認 |
給与が 500000 のまま(¥ が付かない) | ToString("C0") を書き忘れ | View のセルを @emp.Salary.ToString("C0") に変更 |
入社日に時刻が出る(2001/04/01 0:00:00) | ToString フォーマット未指定 | @emp.HireDate.ToString("yyyy/MM/dd") |
ビルドエラー「Employee が見つからない」 | namespace の不一致 | View 先頭の @model List<MvcEmployeeApp.Models.Employee> と Employee.cs の namespace を合わせる |
IsDBNull を入れたのに例外が出る | 別の列で NULL に遭遇 | 例外の列名を確認し、reader.GetOrdinal(...) の前に IsDBNull をチェックする |
| disabled ボタンが反応しない | 仕様 | 本章ではこれが正しい挙動。第 30 章で disabled を外す |
学んだことチェック
Section titled “学んだことチェック”- 第 27 章のプロジェクトをベースに、
MvcEmployeeAppを作成できる -
Employeeクラスに姓名分離 + 部署名込みのプロパティを定義できる -
EmployeeRepository.GetAll()で JOIN を含む SELECT を実装できる -
IsDBNullで NULL を判定し、必要なプロパティに既定値を入れられる -
Insert/DeleteをNotImplementedExceptionで枠だけ用意できる -
EmployeesController.Indexで Repository を呼び、List<Employee>を View に渡せる - View で
<table>+@foreachの一覧表示を書ける -
DateTime.ToString("yyyy/MM/dd")で日付フォーマットを指定できる -
decimal.ToString("C0")で通貨フォーマットを指定できる -
<button disabled>で「枠だけのボタン」を表示できる - Windows フォーム版(第 23 章)と Web 版の構造的な違いを説明できる
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- 第 23 章(Windows)と本章(Web)で、
EmployeeRepositoryの中身はどこが同じで、どこが違いますか。 LEFT JOINを使う理由は何ですか(INNER JOINではなく)。reader.GetOrdinal("列名")を使うとどんな利点がありますか。EmployeeクラスのFullNameはどんな仕組みで動いていますか(第 9 章のプロパティ)。@emp.HireDate.ToString("yyyy/MM/dd")のyyyy/MM/ddを何に書き換えると、2001-04-01形式になりますか。- 「新規登録」「削除」ボタンを
disabledで置く理由は何ですか。 Insert/DeleteをNotImplementedExceptionで先に書く設計の利点を 1 つ挙げてください。
この章の演習課題に取り組みます(タイマーはありません。動かして観察し、自分の言葉で説明できることを重視します。チームで進める場合は声を掛け合いながら自分のペースで進めてください)。
必須課題は、本文で育てている MvcEmployeeApp プロジェクト にそのまま書き込みます(「なぜ」コメントも同じプロジェクト)。発展課題だけは、同じ KadaiWebApp ソリューション内に 別プロジェクト として作ります。
| 課題 | プロジェクト | 内容 |
|---|---|---|
| 課題 28-1(必須) | MvcEmployeeApp(本文の続き) | 社員一覧アプリ本体 + 「なぜ」コメント |
| 課題 28-2(発展) | Ext_EmployeeListExtra(新規) | 一覧をカスタマイズ |
| 課題 28-3(発展) | (コードなし・レポート) | 第 23 章とコード比較 |
課題 28-1 ソースを読み解いて「なぜ」コメントを付ける
Section titled “課題 28-1 ソースを読み解いて「なぜ」コメントを付ける”本文 28-2〜28-7 で組み上げた社員一覧アプリ(MvcEmployeeApp)の Employee.cs・EmployeeRepository.cs・EmployeesController.cs を読み返し、第 23 章の課題 23-1 と同じ要領 で、次の 2 種類のコメントを書き込んでください。
- (A) メソッドの役割:各メソッドの 上の行 に、何をするメソッドかを 1 行(
//)で書く - (B) 難所の「なぜ」:下の表の各箇所に、「なぜそう書くのか」「何のためか」 を 前の行 に自分の言葉で書く(言い換えコメントは NG。→ 第 23 章「課題 23-1」・第 7 章「コラム:コメントの書き方」)
View(.cshtml)の @model・@foreach・asp-*・class は MVC の「おまじない」 なので、「なぜ」ではなく 「何をする決まりか」を一言 で十分です(→ 26-12 早見表)。
(B) 「なぜ」コメントを付ける箇所
| ファイル | 箇所 | 説明する観点(= ここに「なぜ」を書く) |
|---|---|---|
EmployeeRepository.cs | using SqlConnection conn = ... | なぜ using を付けるのか |
EmployeeRepository.cs | LEFT JOIN departments | なぜ INNER JOIN ではなく LEFT JOIN か |
EmployeeRepository.cs | reader.GetOrdinal("列名") | なぜ列名から番号を取るのか(列番号直書きとの違い) |
EmployeeRepository.cs | IsDBNull(...) ? ... : ... の三項演算子 | なぜ NULL チェックが必要なのか |
EmployeeRepository.cs | Insert / Delete の NotImplementedException | なぜ中身を書かず「枠」にしておくのか |
EmployeesController.cs | new EmployeeRepository() → GetAll() → return View(...) | なぜ Controller はこれだけで済むのか(SQL が無い理由) |
Employee.cs | FullName => $"{LastName} {FirstName}" | なぜメソッドではなく読み取り専用プロパティにするのか |
Employee.cs | DepartmentName(テーブルに無い列) | なぜモデルに持たせるのか(JOIN との関係) |
確認すること
- (A) 各メソッドの上に「役割」を 1 行コメントした
- (B) 表のすべての箇所に「なぜ/何のため」のコメントを前行で書いた
- View のおまじない(
@model/@foreach/asp-*)は「何をする決まりか」を一言で書いた - 言い換えコメントになっていない/本文の解説の丸写しになっていない
-
/Employeesで 10 件表示・/Departmentsも動く(動作は提供どおり)
課題 28-2 一覧をカスタマイズする
Section titled “課題 28-2 一覧をカスタマイズする”KadaiWebApp ソリューションに新しいプロジェクト Ext_EmployeeListExtra を作成し、MvcEmployeeApp のコードをコピーした上で、下の A〜D から 1 つ以上 を選んで改造してください(本体 MvcEmployeeApp は壊さず、実験はこちらで)。
| 選択肢 | 内容 | ヒント |
|---|---|---|
| A:給与の高い順に並べる | ORDER BY e.salary DESC | Repository の SQL を修正 |
| B:平均給与を画面に追加表示 | 「平均給与: ¥XXX,XXX」を上部に出す | Controller / View どちらでも可。List<T>.Average(x => x.Salary)(LINQ、第 12 章) |
| C:給与を 50 万以上 / 未満で色分け | <tr> の class を切り替えて目立たせる | @(emp.Salary >= 500000m ? "table-warning" : "") |
| D:最終取得時刻を表示 | 「最終取得時刻: 2026/06/07 14:30:00」を上部に。再読み込みで更新 | Controller で ViewBag.LoadedAt = DateTime.Now;、View で @ViewBag.LoadedAt(MVC 的に Controller でデータを用意) |
確認すること
- 選んだ改造が正しく動く
- 改造を入れた場所(Controller / Repository / View のどれか)を自分で指せる
- 改造によって動作がどう変わったかを説明できる
課題 28-3 第 23 章のコードと比較する
Section titled “課題 28-3 第 23 章のコードと比較する”第 23 章のフォルダから EmployeeRepository.cs と、本章の EmployeeRepository.cs を 見比べて、下の表を埋めてください(紙のノート、Markdown、ペア確認の口頭発表 ── 形式は自由)。
| 観点 | 第 23 章(Windows) | 本章(Web) |
|---|---|---|
| 接続文字列の値 | ||
| 接続文字列の置き場所(Repository の中 / 外) | ||
GetAll() の中の SQL | ||
using SqlConnection ... の書き方 | ||
IsDBNull の有無 | ||
| 戻り値の型 |
そのうえで、次の問いに答えてください。
- 「Repository は Windows / Web で 書き換えずに共有できる か?」 ── あなたの結論と、その理由
- もし共有するなら、接続文字列をどこに置くか は誰が決めるべきか?(Repository の中 / フォーム / Controller / 設定ファイル)
この課題はコードを書きません。「動かしたコードを読めて、比べて、説明できる」状態にすること が目的です。
提出前チェックリスト
Section titled “提出前チェックリスト”- ソリューション名が
KadaiWebApp、本体プロジェクト名がMvcEmployeeApp - csproj が
<Nullable>disable</Nullable> -
Microsoft.Data.SqlClientが NuGet で追加されている -
Models/Employee.csが作成されている(姓名分離 +FullName+DepartmentName) -
Data/EmployeeRepository.csのGetAll()が JOIN で部署名を取得する -
Insert/DeleteがNotImplementedExceptionで枠だけある -
Controllers/EmployeesController.csのIndexで Repository を呼んでいる -
Views/Employees/Index.cshtmlが<table>+@foreachで一覧表示する - 新規登録 / 削除ボタンが
disabled状態で表示される -
/Employeesで 10 件の社員が表示される -
/Departmentsも引き続き表示される(第 27 章の機能を残せている) - 必須課題 28-1:各メソッドの上に「役割」を 1 行、難所の表すべてに「なぜ」を前行コメントした
- View のおまじない(
@model/@foreach/asp-*)は「何をする決まりか」を一言で書いた - (発展 28-2 を実施した場合)
Ext_EmployeeListExtraで改造が動く -
bin・obj・.vsフォルダが Git 管理に入っていない
Git への提出
Section titled “Git への提出”git statusgit add .git commit -m "Chapter28: 社員一覧完成+なぜコメント / <一番なるほどと思った点>"git push origin mainGit の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。
この章のまとめ
Section titled “この章のまとめ”- 第 27 章のプロジェクトを ひな型 にして、本格的な業務 Web アプリの足場を作った
- Repository パターンを Windows と Web で共通 に保つことで、DB アクセスのコードを再利用できる
LEFT JOINで関連テーブル(departments)の情報も同時に取得できるInsert/Deleteの 枠を最初から用意 すると、本実装(第 30 章)で差し替えるだけで済む- View の
@emp.HireDate.ToString("yyyy/MM/dd")/@emp.Salary.ToString("C0")で 業務的に読みやすいフォーマット に整える <button disabled>で「未実装ボタンの枠」を視覚化できる- Windows 版(第 23 章)と並べて読むと、Repository は同じ・上の層は変わる という Web 化のパターンが見える
次章では、社員一覧に 検索機能 を追加します。
入力フォームに名前の一部や部署を指定して、WHERE 句で絞り込めるようにします。
第 24 章(Windows 版の検索)と同じく、SqlParameter でパラメータ化クエリ を書き、SQL インジェクション対策も解説します。
Web では検索条件を クエリ文字列(?name=...&departmentId=...) で Controller に渡すパターンと、フォームの POST で渡すパターンの両方が出てきます。
第 23 章 → 第 24 章の流れをそのまま Web に置き換えていく、と思えば構えずに進められます。