メインコンテンツまでスキップ

第7章:MVCのデータ流れを接続する(Model→Controller→View)🔁✨

この章はね、**「MVCが“回る”瞬間」**を体で覚える回だよ〜🌀💖 (.NET 10 / C# 14 世代で作る想定だよ🧁) (Microsoft for Developers)


今日のゴール🎯✨

  • 操作 → 更新 → 再表示 のループを作れる🔁✅
  • 「データの流れ」と「依存の向き」が別物だってわかる🧠💡
  • done 2 みたいなコマンドで、更新→再表示まで一気通貫でできる🎉

まず大事:2つの矢印を分けよう🧠➡️➡️

Dependency vs Flow

① データの流れ(実行時)🌊

ユーザー操作が入ってから、画面が更新されるまでの“道”だよ👇

  • ユーザーが入力する⌨️
  • Controllerが受け取る🎮
  • Modelを更新する📦
  • ViewがModelを表示する🎨

② 依存の向き(コードの参照関係)🧱

「どの層が、どの層を知っててOK?」って話だよ👇

  • View → Model を知ってOK(表示に必要)👀
  • Controller → Model / View を知ってOK(交通整理役)🚦
  • Model → View / Controller は基本NG(Modelはピュアでいたい)🚫💎

図にするとこんな感じ〜🧁✨

【依存(参照)】            【データの流れ(実行時)】
View ───▶ Model ユーザー入力 → Controller → Model更新 → View再表示
Controller ─▶ Model
Controller ─▶ View
Model(どこにも依存しない)

今回作る「回るMVC」:CampusTodo(コンソール版)📚✅

コマンド(最低限)🧾✨

  • help(使い方)
  • list(一覧)
  • add 文字(追加)
  • done 番号(完了)
  • exit(終了)

実装:MVCを“接続”する最小セット🛠️💖

フォルダはこんな感じにすると迷子になりにくいよ〜📁✨

CampusTodo/
Models/
TodoItem.cs
TodoList.cs
Views/
ConsoleTodoView.cs
Controllers/
TodoController.cs
Program.cs

1) Model:TodoItem + TodoList(更新はModelが担当)📦🛡️

Models/TodoItem.cs

namespace CampusTodo.Models;

public sealed class TodoItem
{
public string Title { get; private set; }
public bool IsDone { get; private set; }

public TodoItem(string title)
{
Title = ValidateTitle(title);
IsDone = false;
}

public void MarkDone()
{
IsDone = true;
}

private static string ValidateTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("タイトルが空だよ〜🥺", nameof(title));

return title.Trim();
}
}

Models/TodoList.cs

using System.Collections.ObjectModel;

namespace CampusTodo.Models;

public sealed class TodoList
{
private readonly List<TodoItem> _items = new();

public ReadOnlyCollection<TodoItem> Items => _items.AsReadOnly();

public void Add(string title)
{
_items.Add(new TodoItem(title));
}

// 「done 2」みたいに、表示番号(1始まり)で完了にする
public bool TryMarkDoneByNumber(int number)
{
var index = number - 1;
if (index < 0 || index >= _items.Count) return false;

_items[index].MarkDone();
return true;
}
}

ポイント💡 Modelは「完了にする」っていうルールと更新を持ってるよ🛡️✨ Controllerが直接 IsDone をいじり始めると、責務が混ざりやすい⚠️


2) View:表示は表示だけ(入力もここでOK)🎨⌨️

Views/ConsoleTodoView.cs

using CampusTodo.Models;

namespace CampusTodo.Views;

public sealed class ConsoleTodoView
{
public void ShowWelcome()
{
Console.WriteLine("CampusTodoへようこそ〜📚✅ help で使い方が出るよ💡");
}

public void ShowPrompt()
{
Console.Write(">> ");
}

public string ReadCommandLine()
{
return Console.ReadLine() ?? "";
}

public void ShowMessage(string message)
{
Console.WriteLine(message);
}

public void Render(IReadOnlyList<TodoItem> items)
{
Console.WriteLine();
Console.WriteLine("📋 Todo一覧");
if (items.Count == 0)
{
Console.WriteLine("(まだ何もないよ〜🌱)");
Console.WriteLine();
return;
}

for (int i = 0; i < items.Count; i++)
{
var item = items[i];
var check = item.IsDone ? "✅" : "⬜";
Console.WriteLine($"{i + 1}. {check} {item.Title}");
}
Console.WriteLine();
}

public void ShowHelp()
{
Console.WriteLine("🧾 つかいかた");
Console.WriteLine(" help : 使い方");
Console.WriteLine(" list : 一覧表示");
Console.WriteLine(" add タイトル : 追加");
Console.WriteLine(" done 番号 : 完了(例:done 2)");
Console.WriteLine(" exit : 終了");
Console.WriteLine();
}
}

ポイント💡 Viewは「見せ方」係だから、完了にする処理(MarkDone)とかはやらない🙅‍♀️✨ 表示のフォーマット(✅/⬜とか)はViewが持っててOKだよ〜🎨


3) Controller:受け取って振り分け、Model更新して、Viewへ🔁🚦

Controllers/TodoController.cs

using CampusTodo.Models;
using CampusTodo.Views;

namespace CampusTodo.Controllers;

public sealed class TodoController
{
private readonly TodoList _model;
private readonly ConsoleTodoView _view;

public TodoController(TodoList model, ConsoleTodoView view)
{
_model = model;
_view = view;
}

// 戻り値:false ならアプリ終了
public bool Handle(string input)
{
var trimmed = input.Trim();
if (trimmed.Length == 0) return true;

var parts = trimmed.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
var cmd = parts[0].ToLowerInvariant();
var arg = parts.Length >= 2 ? parts[1] : "";

switch (cmd)
{
case "help":
_view.ShowHelp();
return true;

case "list":
_view.Render(_model.Items);
return true;

case "add":
return HandleAdd(arg);

case "done":
return HandleDone(arg);

case "exit":
_view.ShowMessage("またね〜👋✨");
return false;

default:
_view.ShowMessage("そのコマンドわかんないかも🥺 help を見てね💡");
return true;
}
}

private bool HandleAdd(string title)
{
try
{
_model.Add(title);
_view.ShowMessage("追加したよ〜🌱✨");
_view.Render(_model.Items); // 更新したら再表示✅
}
catch (ArgumentException ex)
{
_view.ShowMessage($"追加できなかったよ🥺:{ex.Message}");
}
return true;
}

private bool HandleDone(string numberText)
{
if (!int.TryParse(numberText.Trim(), out var n))
{
_view.ShowMessage("番号が読めないよ〜🥺 例:done 2");
return true;
}

var ok = _model.TryMarkDoneByNumber(n);
if (!ok)
{
_view.ShowMessage("その番号は存在しないよ〜🥺 list で確認してね📋");
return true;
}

_view.ShowMessage("完了にしたよ〜✅🎉");
_view.Render(_model.Items); // 更新したら再表示✅
return true;
}
}

ここが第7章の核心💖 **Controllerが「更新したら、必ず再表示する」**って流れを作ると、MVCが気持ちよく回り始めるよ〜🌀✨


4) Program:3人(Model/View/Controller)を出会わせて、ループ開始🔁🚀

Program.cs

using CampusTodo.Controllers;
using CampusTodo.Models;
using CampusTodo.Views;

var model = new TodoList();
model.Add("レポートの下書きを作る📝");
model.Add("ゼミの資料をまとめる📚");

var view = new ConsoleTodoView();
var controller = new TodoController(model, view);

view.ShowWelcome();
view.Render(model.Items);

while (true)
{
view.ShowPrompt();
var input = view.ReadCommandLine();

var cont = controller.Handle(input);
if (!cont) break;
}

動かし方イメージ🎮✨

>> list
1. ⬜ レポートの下書きを作る📝
2. ⬜ ゼミの資料をまとめる📚

>> done 2
完了にしたよ〜✅🎉
1. ⬜ レポートの下書きを作る📝
2. ✅ ゼミの資料をまとめる📚

>> add 洗濯する🧺
追加したよ〜🌱✨
1. ⬜ レポートの下書きを作る📝
2. ✅ ゼミの資料をまとめる📚
3. ⬜ 洗濯する🧺

ミニ演習(この章の勝ち筋)💪✨

演習A:done の“優しさ”強化🧸💖

  • done 0 / done -1 / done 999 を試して、落ちないようにする🧯
  • メッセージをもっと優しくする🌸

演習B:list を「完了 / 未完了」で見やすく🎨✨

  • 未完了を上に出す(表示順だけ変える)⬜➡️✅ ※ Modelの並び替えにしないで、View側の工夫でできたら最高🎉(次章の伏線💡)

演習C:help を見ずに使える導線づくり🧭✨

  • 起動直後に「help って打つと使い方出るよ」って出す📣

あるある事故まとめ(混ざってきたサイン)⚠️🧠

  • Viewの中で「IsDone = true」し始めた 🙅‍♀️💥
  • Controllerが「表示フォーマット(✅とか揃え方)」を持ち始めた 🙅‍♀️🎨
  • ModelがConsoleを触り出した(Console.WriteLine) 🙅‍♀️🧨

このへん出たら、だいたいMVCがグチャってきてる合図だよ〜🥺🧹✨


AI相棒の使い方🤖💡(Copilot / Codex向け)

そのままコピって投げてOKだよ〜🫶✨

  • 「Console MVCで、Controllerは更新したら必ずView.Renderする構造にしたい。責務が混ざらないようにリファクタ案を出して」🧹
  • 「doneコマンドの入力バリデーションを、初心者向けに読みやすく改善して」🧸
  • 「ModelがViewに依存しないように、依存関係をチェックして指摘して」🔍
  • 「このコードにログ(Controller→Model→Viewの流れが見えるやつ)を最小で入れて」🧾

⚠️コツ:AIの提案は“全部採用”じゃなくて、差分で1個ずつ入れると安全だよ〜🧠✨


ふりかえりチェック✅🧁

  • 「done 2」したとき、Model更新→再表示まで1回で流れてる?🔁
  • ModelはConsoleを触ってない?📦🚫
  • 表示の工夫はViewに寄せられてる?🎨
  • Controllerは「振り分け+指示」に集中できてる?🚦

ちょい最新メモ📝✨

今の主流ラインだと .NET 10(LTS)/ C# 14 が“土台”になってるよ〜(Visual Studio 2026 系でのサポートも案内されてる) (Microsoft for Developers)


次の章(第8章)では、このControllerの if/switch が肥大化する問題を「ルーティング表」でスッキリさせるよ〜🗺️✨