Skip to content

第28章 Web 社員管理アプリ:一覧表示

この章から、本研修のもう一つの中心題材 ── 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()employeesdepartments と JOIN して取得できる
  • 未実装メソッドを NotImplementedException で枠だけ用意する設計の意図を説明できる
  • EmployeesController.Index() で Repository を呼び、List<Employee> を View に渡せる
  • View で @foreach を使い、HTML の <table> で一覧表示を組み立てられる
  • decimal の通貨表示や、DateTime のフォーマットを Razor で書ける
  • 「新規登録」「削除」ボタンを disabled 状態のプレースホルダー として置ける
  • Windows フォーム版(第 23 章)と Web 版の構造の違いを比較できる

項目内容
開発環境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 版と同じ進め方です)。各章の区切りでコミットして提出します。


  • 第 27 章を Git に提出済みである
  • app_userTrainingDB に接続できる(第 27 章 27-2)
  • TrainingDBemployees テーブルに 10 件のサンプルデータが入っている(第 16 章)
  • 第 27 章 Ch27_MvcDepartmentList/Departments がブラウザで表示できる
  • 第 23 章 EmployeeRepository.GetAll() のコードが手元にある(参考用)

完成形は次のとおりです。

社員一覧
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.csCh27_MvcDepartmentList/Models/Department.csnamespace を MvcEmployeeApp.Models に変更
Data/DepartmentRepository.csCh27_MvcDepartmentList/Data/DepartmentRepository.csnamespace を MvcEmployeeApp.Data に変更
Controllers/DepartmentsController.cs同上namespace を MvcEmployeeApp.Controllers に変更
Views/Departments/Index.cshtml同上@model の型名を MvcEmployeeApp.Models.Department に変更

「Models」「Data」フォルダがまだ無い場合

プロジェクトを右クリック → 追加 → 新しいフォルダー で Models(あれば不要)、Data を作ってからコピーしてください。

ここで一度 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.cs

28-3 Employee モデルクラスを作る

Section titled “28-3 Employee モデルクラスを作る”

Models フォルダに Employee.cs を追加します。

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
DepartmentNameJOIN で取得する部署名(テーブルには無い)
FullName姓 + 名 を結合した読み取り専用プロパティ

FullName は計算プロパティ

=> でメソッドのように書いていますが、これは 読み取り専用プロパティ の省略構文です(第 9 章)。 LastNameFirstName が変わると、自動で FullName も変わったように見えます。


Data フォルダに EmployeeRepository.cs を追加します。 第 27 章の DepartmentRepository とほぼ同じ構造で、SQL だけ社員 + JOIN に差し替えます。

EmployeeRepository.cs
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 章で実装します。");
}
}
ポイント説明
接続文字列第 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 で実行します。


Controllers フォルダに EmployeesController.cs を追加します。

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 に渡してレンダリング
URLControllerActionView
/EmployeesEmployeesControllerIndex()Views/Employees/Index.cshtml
/Employees/Index同上同上同上

第 26 章 26-6 で学んだルール「/Xxx/YyyXxxController.Yyy()」がそのまま当てはまります。


まず Views フォルダの中に Employees フォルダ を作り、その中に Razor ビュー - 空Index.cshtml を追加します。

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>

各部分のポイント:

第 27 章と同じく、View に 複数件の Model を渡します。型を List<Employee> に変えただけです。

Employee クラスで定義した読み取り専用プロパティが、Razor からそのまま呼べます。 @emp.LastName@emp.FirstName を別々に書くより、一箇所にまとまっていて読みやすくなります。

DateTime をそのまま表示すると 2001/04/01 0:00:00 のように時刻まで出てしまいます。 ToString("yyyy/MM/dd") で日付部分だけにフォーマットしています。

decimal の通貨表示。C は通貨フォーマット、0 は小数桁数 0 を意味します。 日本のロケールでは ¥500,000 のように表示されます。

フォーマット
"C0"¥500,000
"C2"¥500,000.00
"N0"500,000

disabled 属性を付けたボタンは、見た目だけ表示されて クリックできない 状態になります。 第 23 章 23-8 と同じ「枠だけ置く」設計です。第 30 章で disabled を外して、フォーム送信のリンクに置き換えます。

@model@foreachclass="..." の役割は早見表に

これらの「決まり書き(おまじない)」の役割は 第 26 章 26-12「MVC のおまじない早見表」 にまとまっています。迷ったら参照してください。class="table" / btn btn-primary / mt-3 などは Bootstrap の見た目クラスで、付けると表やボタンが整って見えるだけです(中身は気にしなくて OK)。


Controller(28-5)と View(28-6)までそろったので、実行します(先ほどのビルド確認に続く 2 回目=動作確認です)。 F5 で実行し、ブラウザのアドレスバーに次の URL を入れて開いてください。

https://localhost:xxxx/Employees

10 件の社員(章間整合ルール 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 属性で「枠だけ」を表現します。

<button type="button" class="btn btn-primary" disabled>新規登録</button>
属性効果
disabledクリック・フォーカスを受け付けない。グレーアウトで表示される
(なし)通常のボタン。クリックでフォーム送信などが起こる

ブラウザの開発者ツール(F12)でこのボタンを見ると、disabled 属性が付いていて反応しないことが分かります。

完成形(第 30 章)では、これらのボタンが実際に動くようになります。本章で「枠」を置いておくと:

  • 完成形の 画面レイアウトが想像しやすい
  • 第 30 章で disabled を外し、<a asp-action="Create"> などに置き換えるだけで動く
  • 学習者にとって「この機能は次章で実装されるんだな」というシグナルになる
画面のボタン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(本章)
RepositoryEmployeeRepository.GetAll()EmployeeRepository.GetAll()(中身ほぼ同じ)
接続文字列Integrated Security=trueUser Id=app_user;Password=...;
呼び出し場所Form1_Load イベントEmployeesController.Index()
結果の渡し先DataGridView.DataSourcereturn 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 の発展パターンです。


つまずき原因対応
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 を外す

  • 第 27 章のプロジェクトをベースに、MvcEmployeeApp を作成できる
  • Employee クラスに姓名分離 + 部署名込みのプロパティを定義できる
  • EmployeeRepository.GetAll() で JOIN を含む SELECT を実装できる
  • IsDBNull で NULL を判定し、必要なプロパティに既定値を入れられる
  • Insert / DeleteNotImplementedException で枠だけ用意できる
  • EmployeesController.Index で Repository を呼び、List<Employee> を View に渡せる
  • View で <table> + @foreach の一覧表示を書ける
  • DateTime.ToString("yyyy/MM/dd") で日付フォーマットを指定できる
  • decimal.ToString("C0") で通貨フォーマットを指定できる
  • <button disabled> で「枠だけのボタン」を表示できる
  • Windows フォーム版(第 23 章)と Web 版の構造的な違いを説明できる

研修の進め方によっては、隣の人またはチーム内で説明確認を行います。

次の内容を、自分の言葉で説明してください。

  1. 第 23 章(Windows)と本章(Web)で、EmployeeRepository の中身はどこが同じで、どこが違いますか。
  2. LEFT JOIN を使う理由は何ですか(INNER JOIN ではなく)。
  3. reader.GetOrdinal("列名") を使うとどんな利点がありますか。
  4. Employee クラスの FullName はどんな仕組みで動いていますか(第 9 章のプロパティ)。
  5. @emp.HireDate.ToString("yyyy/MM/dd")yyyy/MM/dd を何に書き換えると、2001-04-01 形式になりますか。
  6. 「新規登録」「削除」ボタンを disabled で置く理由は何ですか。
  7. Insert / DeleteNotImplementedException で先に書く設計の利点を 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.csEmployeeRepository.csEmployeesController.cs を読み返し、第 23 章の課題 23-1 と同じ要領 で、次の 2 種類のコメントを書き込んでください。

  • (A) メソッドの役割:各メソッドの 上の行 に、何をするメソッドかを 1 行(//)で書く
  • (B) 難所の「なぜ」:下の表の各箇所に、「なぜそう書くのか」「何のためか」前の行 に自分の言葉で書く(言い換えコメントは NG。→ 第 23 章「課題 23-1」・第 7 章「コラム:コメントの書き方」)

View(.cshtml)の @model@foreachasp-*classMVC の「おまじない」 なので、「なぜ」ではなく 「何をする決まりか」を一言 で十分です(→ 26-12 早見表)。

(B) 「なぜ」コメントを付ける箇所

ファイル箇所説明する観点(= ここに「なぜ」を書く)
EmployeeRepository.csusing SqlConnection conn = ...なぜ using を付けるのか
EmployeeRepository.csLEFT JOIN departmentsなぜ INNER JOIN ではなく LEFT JOIN
EmployeeRepository.csreader.GetOrdinal("列名")なぜ列名から番号を取るのか(列番号直書きとの違い)
EmployeeRepository.csIsDBNull(...) ? ... : ... の三項演算子なぜ NULL チェックが必要なのか
EmployeeRepository.csInsert / DeleteNotImplementedExceptionなぜ中身を書かず「枠」にしておくのか
EmployeesController.csnew EmployeeRepository()GetAll()return View(...)なぜ Controller はこれだけで済むのか(SQL が無い理由)
Employee.csFullName => $"{LastName} {FirstName}"なぜメソッドではなく読み取り専用プロパティにするのか
Employee.csDepartmentName(テーブルに無い列)なぜモデルに持たせるのか(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 DESCRepository の 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 の有無
戻り値の型

そのうえで、次の問いに答えてください。

  1. 「Repository は Windows / Web で 書き換えずに共有できる か?」 ── あなたの結論と、その理由
  2. もし共有するなら、接続文字列をどこに置くか は誰が決めるべきか?(Repository の中 / フォーム / Controller / 設定ファイル)

この課題はコードを書きません。「動かしたコードを読めて、比べて、説明できる」状態にすること が目的です。


  • ソリューション名が KadaiWebApp、本体プロジェクト名が MvcEmployeeApp
  • csproj が <Nullable>disable</Nullable>
  • Microsoft.Data.SqlClient が NuGet で追加されている
  • Models/Employee.cs が作成されている(姓名分離 + FullName + DepartmentName)
  • Data/EmployeeRepository.csGetAll() が JOIN で部署名を取得する
  • Insert / DeleteNotImplementedException で枠だけある
  • Controllers/EmployeesController.csIndex で Repository を呼んでいる
  • Views/Employees/Index.cshtml<table> + @foreach で一覧表示する
  • 新規登録 / 削除ボタンが disabled 状態で表示される
  • /Employees で 10 件の社員が表示される
  • /Departments も引き続き表示される(第 27 章の機能を残せている)
  • 必須課題 28-1:各メソッドの上に「役割」を 1 行、難所の表すべてに「なぜ」を前行コメントした
  • View のおまじない(@model/@foreach/asp-*)は「何をする決まりか」を一言で書いた
  • (発展 28-2 を実施した場合)Ext_EmployeeListExtra で改造が動く
  • binobj.vs フォルダが Git 管理に入っていない

Terminal window
git status
git add .
git commit -m "Chapter28: 社員一覧完成+なぜコメント / <一番なるほどと思った点>"
git push origin main

Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。


  • 第 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 に置き換えていく、と思えば構えずに進められます。