本文へスキップ
BecomeCoder

C# MVVMコース · 第5章 Service ― 業務ロジックの層 · レッスン19

Mapper/DTO ― TaskDtoとTaskMapper

ブラウザで完結

導入

TaskItemTitleTaskTitle(ValueObject)、Priorityenumです。WPFの画面(View)にそのまま渡すと、バインディングや表示のたびに.Value.ToString()を書くことになり、Viewが業務クラスの内部構造を知りすぎてしまいます。画面用のデータの形を別に用意しましょう。

図解

flowchart LR
    TI["TaskItem<br/>(Entity・業務データ)"] -->|TaskMapper.ToDto| DTO["TaskDto<br/>(画面用・プレーンな値)"]
    DTO --> VM["ViewModel / View"]
    style DTO fill:#e1f5fe

サンプル

public record TaskDto(Guid Id, string Title, string Priority, bool IsCompleted);

public static class TaskMapper
{
    public static TaskDto ToDto(TaskItem task)
        => new(task.Id, task.Title.Value, task.Priority.ToString(), task.IsCompleted);
}
  • TaskDtorecord(値の入れ物)。TaskTitleではなくstringPriorityではなくstringというプレーンな型だけを持つ
  • TaskMapper.ToDtoが変換を1箇所に集約する。Viewや他のクラスが変換ロジックを個別に持たなくてよい
  • ViewModelはTaskDtoだけを扱えばよく、TaskTitleのバリデーションルールなど業務データの内部構造を知らずに済む

やってみよう

TaskDtoからTaskItemを作る変換(逆方向)はこのコースでは扱いませんが、なぜ普通は「表示専用」の一方向(Entity→Dto)で十分なのか考えてみましょう(更新はTaskServiceのメソッド経由で行うため)。

演習

var task = TaskFactory.Create("牛乳を買う", Priority.High);
task.Complete();

var dto = TaskMapper.ToDto(task);
Assert(dto.Id == task.Id, "Idが一致");
Assert(dto.Title == "牛乳を買う", "Titleが文字列になっている");
Assert(dto.Priority == "High", "Priorityが文字列になっている");
Assert(dto.IsCompleted, "IsCompletedが引き継がれる");
Console.WriteLine("全テスト成功");

public record TaskDto(Guid Id, string Title, string Priority, bool IsCompleted);
public static class TaskMapper
{
    // TODO: TaskItem を TaskDto に変換する ToDto を実装してください
    ___
}

void Assert(bool c, string n) { if (!c) throw new Exception($"FAIL: {n}"); }

// --- 型のコピー ---
public readonly struct TaskTitle : IEquatable<TaskTitle>
{
    public string Value { get; }
    public TaskTitle(string value)
    {
        if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("タイトルは空にできません");
        if (value.Length > 50) throw new ArgumentException("タイトルは50文字以内です");
        Value = value.Trim();
    }
    public bool Equals(TaskTitle other) => Value == other.Value;
    public override string ToString() => Value;
}
public enum Priority { Low, Medium, High }
public class TaskItem
{
    public Guid Id { get; }
    public TaskTitle Title { get; private set; }
    public Priority Priority { get; private set; }
    public bool IsCompleted { get; private set; }
    public TaskItem(Guid id, TaskTitle title, Priority priority)
    { Id = id; Title = title; Priority = priority; }
    public void Complete() => IsCompleted = true;
    public void Rename(TaskTitle newTitle) => Title = newTitle;
}
public static class TaskFactory
{
    public static TaskItem Create(string title, Priority priority = Priority.Medium)
        => new TaskItem(Guid.NewGuid(), new TaskTitle(title), priority);
}
  • 期待される出力: 全テスト成功
ヒント1を見る

public static TaskDto ToDto(TaskItem task) => new(task.Id, task.Title.Value, task.Priority.ToString(), task.IsCompleted);

ヒント2を見る

TaskTitle.Value文字列に、Priority(enum)は.ToString()で文字列に変換します

まとめ

  • TaskDtoは画面用のプレーンなデータの形、変換はTaskMapperに集約する
  • ViewやViewModelはTaskTitleのバリデーションのような業務データの内部構造を知らずに済む
  • Entity(TaskItem)→Repository→Service→Dtoという流れがここまでで揃った

次章: このTaskDtoを状態として持つViewModelを作ります。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。