ASP.NET Core MVCでHome/IndexにDBの映画一覧が表示されない原因と解決策(No movies/サムネイル対応)

ASP.NET Core MVCでMovies/IndexはDBの映画一覧が表示できるのに、Home/Indexでは常に「No movies available…」になってカードもサムネイルも出ない――この症状は、HomeController側でMoviesを詰めたViewModelを返していない(または型・プロパティが一致していない)ことがほぼ原因です。原因の切り分けから、ホーム画面に最適な一覧表示・画像表示まで実装例つきで整理します。

目次

現象:Movies/Indexは表示できるのに、Home/Indexだけ空になる

.NETのチュートリアル(MvcMovie系)をベースに開発していると、次のような状況に遭遇しがちです。

  • Movies/Index:DBから取得した映画一覧が正しく表示される(検索・ジャンル絞り込みもOK)
  • Home/Index:同じようにDBの映画一覧(カード+サムネイル)を並べたいが、@if (Model?.Movies != null && Model.Movies.Any()) が常に false になり、else 側の「No movies available in the database.」が出る
  • 画像カラムに値が入っているつもりでも、if (!string.IsNullOrEmpty(movie.Image)) が効かず、プレースホルダー画像になってしまうように見える

ここで大事なのは、Movies/IndexとHome/Indexは別コントローラー/別アクションだという点です。Movies/Indexで表示できていても、Home/Indexが同じモデルを自動で受け取ることはありません。

画面アクションモデルを作る責任よくある誤解
Movies/IndexMoviesController.IndexMoviesControllerがDBから取得してViewへ渡す「ここで表示できるからHomeでも表示できるはず」
Home/IndexHomeController.IndexHomeControllerが改めてDBから取得してViewへ渡す「View側の@ifが悪い/Razorが壊れている」

結論:Home/IndexにMoviesを詰めたViewModelを渡していないのが原因

質問の状況に一番一致する原因はこれです。

  • HomeController.Indexが return View(); のまま
  • またはViewModelを作っているつもりでも、Moviesプロパティを詰めていない
  • あるいはView側の@modelが別型で、プロパティ名が一致していない

つまり、Home/IndexのRazorで Model?.Movies を見ても、そもそもModelがnull、または Moviesがnull/空 なので、Any() が常に false になります。画像判定の前に、一覧のループに入れていないケースがほとんどです。

まず確認したい「間違い例」

Home/Indexが常にelse側になるとき、HomeController側が次のままになっていることがよくあります。

public class HomeController : Controller
{
    public IActionResult Index()
    {
        // これだとHome/Indexにモデルが渡らない(Modelはnullになりやすい)
        return View();
    }
}

解決策:HomeController.IndexでもDBを問い合わせてViewModelを返す

MoviesController.Indexでやっていることを、HomeController.Indexでも「ホーム用に」やります。ポイントは次の2つです。

  • DbContext(例:MvcMovieContext)をDIで受け取る
  • Home/Indexで使うViewModelを作り、Moviesを必ず詰めて返す

HomeControllerにDbContextを注入する(DI)

まずHomeControllerにコンストラクタを追加し、DbContextを受け取ります。

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;

public class HomeController : Controller
{
private readonly MvcMovieContext _context;


public HomeController(MvcMovieContext context)
{
    _context = context;
}

public async Task<IActionResult> Index()
{
    // ↓ここでホーム用にMoviesを取得してViewModelへ詰める
    var vm = new MovieGenreViewModel
    {
        Movies = await _context.Movie
            .OrderByDescending(m => m.ReleaseDate)
            .Take(8) // ホームなので最新8件などに絞ると見栄えが良い
            .AsNoTracking()
            .ToListAsync()
    };

    return View(vm);
}


}

ポイント:

  • ホーム画面は「一覧の全件表示」ではなく、最新数件に絞るとUXも性能も良くなります。
  • AsNoTracking()は読み取り専用表示では有効です(追跡を切って軽量化)。
  • チュートリアル通りにMovieGenreViewModelにGenresやSearchStringがある場合、ホームで不要なら詰めなくてもOKです(ただしViewが参照するなら用意が必要)。

ViewModel側でnullを起こしにくくする(推奨)

ビューで毎回nullチェックを書くより、ViewModelで初期化しておくと事故が減ります。特にホームは「表示するだけ」のことが多いので、以下のようにしておくと堅牢です。

public class MovieGenreViewModel
{
    // nullを避け、Viewの条件分岐がシンプルになる
    public List<Movie> Movies { get; set; } = new();
}

この形なら、Viewは if (Model.Movies.Any()) のように書けます(ただし、ControllerがModel自体を渡していないと意味がない点は同じです)。

Home/Indexの@modelと表示コードをMovies/Indexと整合させる

Controller側でViewModelを返しても、View側が別型を宣言していたり、プロパティ名が違うと表示できません。Home/Indexでは、まず@model宣言がViewModelと一致しているかを確認します。

Home/Index.cshtmlの@model例

@model MvcMovie.Models.MovieGenreViewModel

Welcome

@if (Model?.Movies != null && Model.Movies.Any())
{

@foreach (var movie in Model.Movies)
{




            @* サムネイル *@
            @{
                // 空文字だけでなく空白も弾くと強い
                var hasImage = !string.IsNullOrWhiteSpace(movie.Image);
            }
            @if (hasImage)
            {
                <img class="card-img-top" src="@movie.Image" alt="@movie.Title" />
            }
            else
            {
                <img class="card-img-top" src="~/images/noimage.png" alt="no image" />
            }

            <div class="card-body">
                <h5 class="card-title">@movie.Title</h5>
                <p class="card-text">@movie.Genre</p>
            </div>
        </div>
    </div>
}
</div>


}
else
{
No movies available in the database.
}

ここでの重要ポイントは3つです。

  • Home/IndexはMovies/Indexとは独立:同じように表示したいなら、Home側にも同じモデルを渡す必要があります。
  • @modelの型が一致していないと、Modelがnullだったり、表示に必要なプロパティが見えません。
  • IsNullOrEmptyよりIsNullOrWhiteSpaceの方が、DBに「空白だけ」が入ったケースを弾けるため実運用で強いです。

Model型が合っていない場合に起きること

Viewが期待する型とControllerが渡す型が違うと、本来は実行時に「型が違う」という例外が出ます。一方で、Viewが期待する型が正しくても、ControllerがView()だけ返すとModelはnullのままなので、「例外は出ないが常にelse側」という状況になりやすいのが落とし穴です。

「else側が出る」原因を最短で切り分けるチェックリスト

この手の不具合は、Viewをいじるより先に「HomeController.Indexが何をViewへ渡しているか」を確認した方が早く解決できます。以下の順に見れば、ほぼ迷いません。

チェック箇所見るもの想定される原因対処
HomeController.Indexreturn View(...)の引数ViewModelを渡していないreturn View(vm);にする
HomeController.Indexvm.Movies.CountDB取得が0件(接続先違い、条件で除外など)接続文字列、フィルタ条件、Seed/Migrationを確認
Home/Index.cshtml@model宣言型不一致でプロパティ参照がズレるControllerが返す型に合わせる
Home/Index.cshtmlModel.Moviesの参照箇所プロパティ名違い(Movie vs Movies)ViewModelの定義と揃える
Program.cs / appsettings.json接続文字列・環境開発用DBと本番用DBが違う環境変数・User Secrets・appsettings.*を確認

ブレークポイントは「HomeController.Index」に置く

最も確実な方法は、HomeController.Indexにブレークポイントを置いて次を確認することです。

  • vmは生成されているか(nullではないか)
  • vm.Moviesはnullではないか
  • vm.Movies.Countは0ではないか

ここで0件なら、View側の@ifや画像判定をいくら触っても状況は変わりません。DBから取れていないので、接続先やクエリ条件の問題を疑うべきです。

接続先DBが違う「あるある」

Movies/Indexが表示できているのにHome/Indexが0件、というケースは基本的に起きません。もし起きているなら、HomeControllerだけ別のDbContextを参照している、あるいは環境別設定で接続先が変わっている可能性があります。

  • appsettings.jsonとappsettings.Development.jsonで接続文字列が違う
  • User Secretsで上書きされている
  • DbContextの登録が二重になっている/別名のContextを使っている

HomeControllerとMoviesControllerが同じMvcMovieContextを使っているか、Program.csのDI登録を見直しましょう。

「画像があるのに出ない」問題は、まずMoviesが来ているかを確認する

質問の症状では、画像判定以前にMoviesが空で、表示ロジック自体が回っていない可能性が高いです。先にMoviesが表示される状態を作ってから、画像の切り分けに進むのが最短ルートです。

画像判定が期待通りに動かない代表パターン

  • movie.Imageがnull/空:そもそもDBに値が入っていない、または保存処理が失敗している
  • 空白だけが入っている:IsNullOrEmptyでは弾けないので、IsNullOrWhiteSpaceが必要
  • data URL形式ではない:Base64本体だけを保存していて、data:image/jpeg;base64,のプレフィックスが無い
  • ファイルパスを保存している:~/からの相対パスや絶対URLに変換できていない
  • 静的ファイル配信が無効:app.UseStaticFiles()を入れていない、またはwwwroot配置が違う

プレースホルダー画像が表示できる前提も確認する

~/images/noimage.pngのような相対パスでプレースホルダーを出す場合、wwwroot/images配下にファイルが存在し、かつ静的ファイル配信が有効である必要があります。Program.csに以下が無い場合は追加します。

var app = builder.Build();

app.UseStaticFiles(); // wwwroot配下の静的ファイルを配信するために必要

app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Base64(data URL)をDBに保存している場合の前提と実装例

DBのImageカラムにBase64文字列を直接保存して、Razorで <img src="@movie.Image"> として表示したい場合、保存される文字列は次の形式になっている必要があります。

  • OK例:data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...
  • NG例:/9j/4AAQSkZJRgABAQ...(プレフィックス無し)

プレフィックスが無いと、ブラウザはそれを画像として解釈できず、結果的に「表示されない」状態になります。

Create/EditでIFormFileをdata URLに変換して保存する例

private static async Task<string?> ToDataUrlAsync(IFormFile? file)
{
    if (file == null || file.Length == 0) return null;


using var ms = new MemoryStream();
await file.CopyToAsync(ms);

var base64 = Convert.ToBase64String(ms.ToArray());

// ContentType例:image/jpeg, image/png
var contentType = string.IsNullOrWhiteSpace(file.ContentType)
    ? "image/jpeg"
    : file.ContentType;

return $"data:{contentType};base64,{base64}";


}

そして保存時に:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Movie movie, IFormFile? posterFile)
{
    movie.Image = await ToDataUrlAsync(posterFile);


_context.Add(movie);
await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));


}

これでHome/Indexの <img src="@movie.Image"> はそのまま動きます。

Base64直保存のメリット・デメリット

方式メリットデメリット向いている場面
DBにdata URL(Base64)を保存実装が簡単、参照が1本化DBサイズ増大、転送量増、キャッシュ効きにくい学習・社内ツール・小規模
DBにファイルパス/URLだけ保存DB軽量、CDN/キャッシュが効く保存先(ディスク/Blob)の運用が必要中〜大規模、運用前提
DBにbyte[]保存(BLOB)一体管理できる、改ざん管理しやすい配信最適化が難しい、取り回しが重い要件でDB一体管理が必要

チュートリアル段階ではBase64でも成立しますが、運用を見据えるなら「画像はストレージに置き、DBにはURL/パスのみ」がよく採られます。

ホーム画面に映画カードを出すときの“実務的な”改善ポイント

表示できるようになったら、ホーム向けに少しだけ改善すると完成度が上がります。

表示件数は絞る(Take)

Home/Indexは入口なので、全件を並べるより最新6〜12件に絞る方が読みやすいです。全件はMovies/Indexに任せましょう。

部分ビュー(Partial View)でカードを共通化する

Movies/IndexとHome/Indexでカード表示を共有したい場合、カード部分を_MovieCard.cshtmlのような部分ビューにしておくと、デザインの変更が一箇所で済みます。

@* Views/Shared/_MovieCard.cshtml *@
@model MvcMovie.Models.Movie


@{
var hasImage = !string.IsNullOrWhiteSpace(Model.Image);
}
@if (hasImage)
{

}
else
{

}
@[email protected]

Home/Index側では:

@foreach (var movie in Model.Movies)
{
    <div class="col-md-3 mb-3">
        <partial name="_MovieCard" model="movie" />
    </div>
}

「No movies available…」の表示を“原因がわかる”文言にする

開発中は、単に「0件です」よりも、原因切り分けが進む文言にするのがおすすめです。例えば:

  • DB接続ができていない場合:「DB接続設定を確認してください」が分かりやすい
  • 本当に0件の場合:「映画データがまだ登録されていません」が親切

ただし本番公開時は、内部情報を漏らさない表現に戻しましょう。

よくある落とし穴:Modelは合っているのにMoviesだけ空になるケース

HomeControllerでViewModelを返しているのにMoviesが空のときは、次のような実装ミスが多いです。

落とし穴1:Moviesプロパティに代入していない

ViewModelをnewして返していても、Moviesを詰めていなければ当然0件です。例えば以下は「Genresだけ埋めた」などで起きます。

var vm = new MovieGenreViewModel();
// vm.Movies = ... が無い
return View(vm);

落とし穴2:Selectで列を絞ってImageを落としている

パフォーマンスを気にしてSelectで必要列だけ取得すると、Imageを落としてしまうことがあります。

var movies = await _context.Movie
    .Select(m => new Movie
    {
        Id = m.Id,
        Title = m.Title,
        // Image = m.Image を書いていないと、Imageはnullになる
    })
    .ToListAsync();

この場合は、Imageも含めるか、表示用DTOを作って必要な値を確実に渡しましょう。

落とし穴3:Home/Indexだけ別のフィルタ条件を付けている

「公開中だけ」「評価が一定以上だけ」など、ホーム専用の条件を付けた結果、データが全て除外されて0件になることがあります。まずは条件を外して、一覧が出る状態に戻してから条件を追加すると安全です。

まとめ:Homeは“別画面”なので、別途Moviesを詰めて渡す

Movies/IndexでDB一覧が表示できているのにHome/Indexが空になるときは、Razorの@ifや画像表示ロジック以前に、HomeControllerがViewへMoviesを渡していないのが第一容疑です。

  • HomeController.IndexでもDbContextからMoviesを取得し、ViewModelに詰めてreturn View(vm);する
  • Home/Indexの@modelをViewModelと一致させ、Model.Moviesで回す
  • 画像問題は「Moviesが来ている」ことを確認してから、data URL形式・空白・パス・静的配信などを切り分ける

この順で直していけば、「常にelse側」「サムネイルだけ出ない」の両方を最短で解消できます。ホーム画面はアプリの顔なので、表示件数の調整や部分ビュー化まで行うと、見た目と保守性が一段上がります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次