情報応用演習Ⅰ(2024)

【T9b】簡易ブログソフトウェアの作成 Part.Ⅱ ~ 記事関連機能の実装(7/11)

プロジェクトタイプ(注意: 本文参照)
プロジェクト名T9b
ソリューション名PIT9
注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備作業を参照せよ.

9b-7. 記事の作成のためのアクションの修正

次に記事の作成のためのアクションであるCreateアクションとそのビューを見直そう.自動生成したコントローラー/アクションでは 一対多の項目の新規作成・編集時の動作は ドロップダウンリスト(select要素)で選択するという形式になっている . つまり今回のArticlesコントローラーでいえば 記事の所有者(作成者)たるユーザーを選択する という動作になっているため, CreateやEditアクションには ユーザーIdのリストをSelectListオブジェクトのリストとしてViewDataに渡す,という余計な処理が含まれている が これは今回は望ましい挙動ではない.

このことを確認しておこう.デフォルトのCreateアクションは,たとえばGET用のアクションであれば_の ようになっているはずである.

ArticlesコントローラーのデフォルトのCreateアクション(GET用)
1
2
3
4
5
6
// GET: Articles/Create
public IActionResult Create()
{
    ViewData["BlogUserId"] = new SelectList(_context.Users, "Id", "Id");
    return View();
}

ViewData["BlogUserId"]に既存の全ユーザーの情報をSelectListクラスのインスタンスとして格納している. このアクションに対応するビューも見てみよう.自動生成されたままのデフォルトの Views/Articles/Create.cshtml も見てみよう.

デフォルトのViews/Articles/Create.cshtmlの内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@model T9b.Models.Article

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Article</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Body" class="control-label"></label>
                <input asp-for="Body" class="form-control" />
                <span asp-validation-for="Body" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Created" class="control-label"></label>
                <input asp-for="Created" class="form-control" />
                <span asp-validation-for="Created" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Modified" class="control-label"></label>
                <input asp-for="Modified" class="form-control" />
                <span asp-validation-for="Modified" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PublicationState" class="control-label"></label>
                <select asp-for="PublicationState" class="form-control"></select>
                <span asp-validation-for="PublicationState" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="BlogUserId" class="control-label"></label>
                <select asp-for="BlogUserId" class ="form-control" asp-items="ViewBag.BlogUserId"></select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

42行目を見ると select 要素で先ほど ViewBag.BlogUserId(=ViewData["BlogUserId"]) に設定したものを asp-items タグヘルパーに設定してドロップダウンリストで選択させるようになっている.これは実際に実行すると _のようになる.

一対多の項目の作成・編集時のデフォルトの挙動

これは先ほど説明した通り,自動生成されたコントローラー/ビューにおける一対多の項目の作成・編集時のデフォルトの挙動である. しかしこれが自然な動作ではないことは明らかだろう .通常ユーザーとしてログインしておきながら,別のユーザーになりすまして 記事を作成できるようにするのはブログというウェブアプリの挙動としては明らかに望ましいものではない. ログイン中はログイン中のユーザーの所有物としての記事しか作成できないようにするのが自然であるため,この部分は修正する必要がある. GET用およびPOST用のCreateアクションを各々__に 示すように修正しよう.

Articlesコントローラーの修正内容(Createアクション(GET用))
1
2
3
4
5
6
7
// GET: Articles/Create
[Authorize]
public IActionResult Create()
{
    // ViewData["BlogUserId"] = new SelectList(_context.Users, "Id", "Id"); // ←削除
    return View(new Article()); // Articleクラスのインスタンスを作成して渡している
}
Articlesコントローラーの修正内容(Createアクション(POST用))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// POST: Articles/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]                // Created,Modifiedを削除↓              ↓BlogUserIdを削除
public async Task<IActionResult> Create([Bind("ArticleId,Title,Body,PublicationState")] Article article) 
{
    var currentUser = await _userManager.GetUserAsync(User); // ユーザー情報を取得する.

    // 認証済みのはずなのでユーザー情報を取得できないことはあり得ないがいちおうチェックしておく.
    if (currentUser == null) throw new InvalidOperationException(nameof(currentUser));

    if (ModelState.IsValid)
    {
        article.BlogUserId = currentUser.Id; // 記事の所有者のIDを現在ログイン中のユーザーに設定する.

        article.Created = article.Modified = DateTime.Now; // 記事の作成日時と更新日時を設定する.

        _context.Add(article);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Details), new { id = article.ArticleId }); // 記事の作成が成功したら Details に遷移する
    }
    // ViewData["BlogUserId"] = new SelectList(_context.Users, "Id", "Id", article.BlogUserId); // ←削除
    return View(article);
}

Createアクションにはログイン済みのユーザーにしかアクセスできないようにするため,GET/POSTの両方とも [Authorize]属性を付加している._のPOST用のCreateアクションでは 投稿された記事情報の所有者のユーザーIDを,現在ログイン中のユーザーのIDに設定している(14行目).

このCreateアクションのためのビューも変更しておこう. Views/Articles/Create.cshtml を_に示すように変更しよう.

Views/Articles/Create.cshtmlの内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@model T9b.Models.Article

@{
    ViewData["Title"] = "新規記事";

    if (Model is null) throw new ArgumentNullException(nameof(Model)); // ad-hoc! 非null保証のための回避策
}

<form asp-action="Create">
  @* タイトル(Title)のための入力欄*@
  <label asp-for="@Model.Title"></label>  <input asp-for="@Model.Title" />
  <br />

  @* 本文(Body)のための入力欄*@
  <label asp-for="@Model.Body"></label><br />
  <textarea asp-for="@Model.Body" rows="10" cols="70"></textarea>
  <br />

  @* 公開状態(PublicationState)のための入力欄*@
  <label asp-for="@Model.PublicationState"></label>  <select asp-for="@Model.PublicationState" asp-items="Html.GetEnumSelectList<PublicationStateType>()"></select>
  <br />
  
  <input type="submit" value="投稿" />
  <div asp-validation-summary="All"></div>
</form>

<a asp-action="Index">一覧に戻る</a>

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

ここでは特別なことはしていない.単純に記事情報のうち「タイトル」「本文」「公開状態」の入力のための 入力欄を設けているだけである.なお「公開状態」はPublicationStateType列挙型で設定するので, チュートリアル【T5b】でやったように select 要素と asp-items タグヘルパーを使用している.

ここまで書けたら実行してみよう.プロジェクトを起動して「ログイン」のリンクをクリックし適当な通常ユーザー (admin以外のユーザー)としてログインしよう.ログイン後に「全記事一覧」 →「新規作成」とリンクをたどり,新規記事画面(Create)を表示させよう(__). タイトルと本文に適当な内容を入力し公開状態を「公開済み」にして「投稿」ボタンをクリックしよう. 記事詳細画面(Details)に遷移し,記事が作られたことが分かるはずである(__). この記事詳細画面は現時点では自動生成されたデフォルトのままであるが,この後でよりふさわしい表示に変更する予定である. また「Back to List」→「新規作成」のリンクをたどり,公開状態に「下書き」を指定した記事を作成してから「Back to List」のリンクから 記事一覧の画面に戻ってみよう(__).先ほどと同じように下書きの記事も記事一覧で表示されるが,この状態でログアウトすると 「下書き」の記事は一覧に表示されなくなるはずである(__).

実行結果

さらにこの状態から「ログイン」のリンクをクリックして 別の通常ユーザーとして ログインして公開状態に「公開済み」を指定した記事を作成してみよう (別のユーザーアカウントを用意していなければadminとしてログインしてユーザーを作成してからログインしなおす)(__). そして「Back to List」のリンクから記事一覧画面(Index)に戻ると,いま作成した記事と先ほどのユーザーで作成した記事の両方が表示されるはずである. また,自分で作成した記事にのみ「編集」「削除」のリンクが表示されるはずである(_).

実行結果

ここまでを確認したら次に進もう.

Last updated on 2024-06-19
Published on 2024-06-19

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.