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/Index | MoviesController.Index | MoviesControllerがDBから取得してViewへ渡す | 「ここで表示できるからHomeでも表示できるはず」 |
| Home/Index | HomeController.Index | HomeControllerが改めて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.Index | return View(...)の引数 | ViewModelを渡していない | return View(vm);にする |
| HomeController.Index | vm.Movies.Count | DB取得が0件(接続先違い、条件で除外など) | 接続文字列、フィルタ条件、Seed/Migrationを確認 |
| Home/Index.cshtml | @model宣言 | 型不一致でプロパティ参照がズレる | Controllerが返す型に合わせる |
| Home/Index.cshtml | Model.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側」「サムネイルだけ出ない」の両方を最短で解消できます。ホーム画面はアプリの顔なので、表示件数の調整や部分ビュー化まで行うと、見た目と保守性が一段上がります。

コメント