ASP.NET Core フレームワークを用いて文献管理システムを作る 【第7回 View遷移とデータベース検索】

1. 前回のあらすじ

前回は文献管理用データベース・スキャフォールディングを作成し、MVCモデルを用いて管理ページを作成しました。

2. 今回の目的

このままではURLからダイレクトに接続する必要があるため、文献一覧へのリンクフォームを作成します。また、文献を追加・編集・削除が誰でも出来てしまうのは問題なので、操作しようとしたときにログインページへ飛ばされるような制御を行います。さらに、データベースの検索機能も作ってみたいと思います。

3. 文献管理ページへのリンク作成

前回、URLにて /Books を入力しました。これが文献管理のページURLなのですが、ソリューションエクスプローラーではどこに位置しているかを確認します。Books という名前は Model と View にありますが、どちらかわかるでしょうか?
ページURL は VIew に相当するので、View ディレクトリの中の Books ディレクトリが正解です。Books へリンクすると、index ファイルが読みだされます。 f:id:takunology:20190622180444p:plain

つまり、リンク先はここを指定すればいいわけです。
Webページの上部に黒いメニューバーに "文献一覧" を追記したいのですが、この View はどこに存在しているのでしょうか?
f:id:takunology:20190622181108p:plain

View と言ってしまっているのでほぼ答えですが、View > Shared > _Layout.cshtml がメニューバーを示しています。 f:id:takunology:20190622181501j:plain

ソースコードをよく見てみると、Home, About, Contact などが書いてありますね。Aboutの次の行に以下のようなコードを追記します。

<li><a asp-area="" asp-controller="Books" asp-action="Index">文献一覧</a></li>

これで実行してみましょう。"文献一覧" がメニューに追加されています。 f:id:takunology:20190622181936p:plain

ここをクリックして文献データベースのページへリンクしているかを確かめてください。うまく遷移されれば成功です。この遷移のことをURLルーティングというらしいです。
少し解説すると、asp-area="" はControllerやView などのファイルがアプリケーションのセクションごとに入っているディレクトリを指定します。今回はカレントなので特に指定する必要はないです。asp-controller="Books" は View の中のどのディレクトリを使用するかを指定します。文献に関する View は Books ディレクトリに入っているのでこのディレクトリを指定しています。asp-action="Index" は その View の中のどのページを読み出すかを指定します。Books の中でも Index.cshtml を読み込みたいので Index と指定しました。このように Area-Controller-Action でURLルーティングを設定することができます。

4. ログインページへの遷移

ログインしていない状態では登録された文献の閲覧、編集したいときにはログインページに遷移させるような機能を実装していきます。文献管理ページを制御しているのは Controller ディレクトリの BooksController.cs ファイルです。ログインへ遷移させる条件としてデータベースを操作するメソッドの先頭に [Authorize] アノテーションを追記します。
その際にUsingディレクティブを追記する必要があります。 f:id:takunology:20190622190224p:plain

データベースを操作するメソッドは Create( ), Edit( ), Delete( ) の3つです。これら以外 Index( ) と Detail( ) はそれぞれ閲覧と概要なので必要ないです。操作するもののメソッドの上に全てつけてください。URLからダイレクトに遷移されないようにもなります。 例として Edit( ) にアノテーションを付けた例を示します。

[Authorize] //各メソッドの上につける
public async Task<IActionResult> Edit(int? id)
{
      //処理
}

これを参考に他のメソッドに対しても追記しておいてください。
できたら実行して確認してください。ログインせずに一覧を表示し、新しく文献追加してみてください。正しく動作していればログインページへ遷移されます。 f:id:takunology:20190622192021p:plain

ログインせずに文献を追加しようとするとログインページへ遷移されます。URLをよく見ると、/Identity/Account/Login? になっており、正しく動作しています。その続きに ReturnUrl=Books/Create となっているため、ログインが完了すれば返り値として /Books/Create の文献登録ページに遷移されるような動きになります。

5. 文献のタイトル検索

せっかくなので、文献一覧から目的のものを探し出す機能を実装してみます。検索のフィルタとして "タイトル" と "分野" で検索できるようにしたいと思います。BooksController.cs の中の Index( ) メソッドを探し、編集します。
まずは検索するために、検索条件にひっかかった行を選択するためのLINQクエリを定義します。次に、View にて検索する文字が入力されたとき、その文字をロジック側の引数 (Keywords) へ値を渡すようにします。そして、return View の部分は今までだとすべて表示になっていましたが、検索にかかったものだけを表示したいので、books 変数を指定してあげます。await は非同期処理のおまじないです。

public async Task<IActionResult> Index(string Keywods)
{
     //LINQクエリの定義
     var books = from b in _context.Books select b;
     //検索フォームに入力された文字を keywords 変数に渡す
     if (!String.IsNullOrEmpty(Keywords))
     {
         // Where で データベース内を keywords で検索
         books = books.Where(s => s.タイトル.Contains(Keywords));
     }
     //既にある処理
     return View(await books.ToListAsync());
}

これで変数 books に検索されたレコードが保持されます。"タイトル" は日本語ですが、変数名としては正しいので注意してください。データベースへ登録する際、タイトルを日本語で定義したため、このようになっています。
次に View に検索フォームを作ります。表示したいのは文献一覧の Index ページなので、Views > Books > Index.cshtml を開きます。そして、次の内容を追記します。

<form asp-controller="Books" asp-action="Index">
    <p>
        タイトル: <input type="text" name="Keywords">
        <input type="submit" value="検索" />
    </p>
</form>

できたらこれで実行してみます。文献検索機能が正しく動作しているか確かめたいので、文献をいくつか登録しておいてください。 f:id:takunology:20190622215229p:plain

試しに "解析" と入れて検索してみます。 f:id:takunology:20190622215304p:plain

f:id:takunology:20190622215322p:plain

解析学の文献データが表示されました。"解析" だけでも、一致すればその検索結果を得られます。URLのところが Keyword=解析 となっており、Keyword変数に解析という文字列が引数として入っています。タイトルを一部しか覚えていなくても、検索することが可能です。

6. 文献の分野検索

続いて、分野ごとに検索したいと思います。Models に新しいクラス "BooksFieldView.cs" を作成します。名前は自由でいいです。そのクラスの中に次の内容を記述します。Books.csと内容はほぼ同じですが、検索用の変数 (BookField, Keywords) が追加されています。

public class BooksFieldView
{
    public List<Books> Books { get; set; }
    public string タイトル { get; set; }
    public string 著者 { get; set; }
    public string 出版社 { get; set; }

    [DataType(DataType.Date)]
    public DateTime 発行日 { get; set; }

    public SelectList 分野 { get; set; }
    public string リンク { get; set; }
    public string BookField { get; set; }
    public string Keywords { get; set; }
}

Usingディレクティブも追加しておいてください。 f:id:takunology:20190622221520p:plain

次に、BooksController.cs の Index ( ) メソッドを編集します。先ほどはキーワード検索しか行えませんでしたが、分野による検索も加えたいので、次のように編集します。 コメントアウトはメモ代わりなので、消していいです。

public async Task<IActionResult> Index(string BookField, string Keywords)
{
    //LINQクエリの定義
    var books = from b in _context.Books select b;

    //データベースに保存された分野をすべて取得するLINQクエリ
    IQueryable<string> field = from b in _context.Books
                               orderby b.分野
                               select b.分野;

    //検索フォームに入力された文字を keywords 変数に渡す
    if (!String.IsNullOrEmpty(Keywords))
    {
        // Where で データベース内を keywords で検索
        books = books.Where(s => s.タイトル.Contains(Keywords));
    }

    //分野検索にて受け取った文字列を BookField へ渡す
    if (!String.IsNullOrEmpty(BookField))
    {
        // Where で データベース内の分野を BookField で検索
        books = books.Where(f => f.分野 == BookField);
    }

    // Model で定義した BooksFieldView を参照
    // 分野 にLINQクエリで取得した分野データをリスト化して保持
    var bookFieldView = new BooksFieldView
    {
        分野 = new SelectList(await field.Distinct().ToListAsync()),
        Books = await books.ToListAsync()
    };

    // Model の BooksFieldView を表示させる
    return View(bookFieldView);
}

これでロジック部分はできたので、次に View 部分を編集していきます。Views > Books > Index.cshtml を次のように編集します。
まずは先頭の Model を参照する部分を変更します。デフォルトでは Books を参照するようになってますが、ここを先ほど作成した BooksFieldView に変更します。 f:id:takunology:20190622224924p:plain

そして検索フォームの部分に分野検索用フォームを追記します。

<form asp-controller="Books" asp-action="Index" method="get">
    <p>
        分野:
        <select asp-for="BookField" asp-items="Model.分野">
            <option value="">All</option>
        </select>

        タイトル: <input type="text" name="Keywords" />
        <input type="submit" value="検索" />
    </p>
</form>

さらに下の方の部分に

@foreach (var item in Model) {
     //処理
}

というコードがありますが、これを次のように変更します。

@foreach (var item in Model.Books) { 
     //処理
}

これで実行してみましょう。分野を選択して検索してみてください。全体の流れを見るため、動作例を動画にしてみました。


文献管理システム検索欄の実行例

ここまでできればアプリとしては完成です!あとはひたすらに日本語化&いらない機能の削除をします。

7. まとめ

今回は文献一覧へのリンクと文献のタイトル検索・分野検索をできるようにしてみました。ここまでくればシステムとしての機能は完成です。あとは日本語にしていくのと、必要ない機能をそぎ落とすくらいですので、ここら辺は頑張ってください。

次回からは仮想環境のサーバでデプロイ&運用し行きたいと思います。 Windows 環境でのアプリとしては完成ですので、仮想環境を使用しない方はここまでとなります。お疲れさまでした!