はじめに
ようやく ASP.NET Core MVC の Model の話です。
Empty で ASP.NET Core のプロジェクトを作り、 Controller クラスだけを作った状態で、下記チュートリアルを参考に Model を追加してみることにします。
スキャフォールド
今回は ASP.NET Core のプロジェクトを Empty のテンプレートで生成し、そこに追加することにしたいと思います。
チュートリアルにのっとり、 Models > Movie というクラスを作ってみます。
Movies.cs
using System; namespace WebApplication1.Models { public class Movie { public int ID { get; set; } public string Title { get; set; } public DateTime ReleaseDate { get; set; } public string Genre { get; set; } public decimal Price { get; set; } } }
DbContext クラスを追加します。
DB の設定などを行うためのクラスですが、ここではほぼ空の状態で作成しておきます。
using Microsoft.EntityFrameworkCore; namespace EfCoreSample.Models { public class EfCoreSampleContext : DbContext { public EfCoreSampleContext (DbContextOptionsoptions) : base(options) { } public DbSet Movie { get; set; } } }
このチュートリアルでは、既存の DB を使うのではなくコードから生成します。
その名前は appsettings.json で指定しています。
appsettings.json
{ ~省略~ "ConnectionStrings": { "MovieContext": "Data Source=MvcMovie.db", } }
必要なパッケージを NuGet でインストールします(どれもバージョンは 2.2.2 にしました)。
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SQLite
- Microsoft.EntityFrameworkCore.Design
- Microsoft.VisualStudio.Web.CodeGeneration.Design
Startup クラスで先ほど作成した DBContext を追加します。
Startup.cs
using EfCoreSample.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace EfCoreSample { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.Configure< CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddDbContext< EfCoreSampleContext>(options => options.UseSqlite(Configuration.GetConnectionString("MovieContext"))); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); } } }
そしてスキャフォールド、…といきたいところですが、 Rider では GUI ではスキャフォールドできず、 EntityFramework のコマンドを実行する必要があるようです。
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc EfCoreSampleContext --relativeFo lderPath Controllers --useDefaultLayout --referenceScriptLibraries
エラー
スキャフォールドに成功すると、 View、Controllerにファイルが追加されます。
が、 Views/Movies/Create.cshtml と Views/Movies/Edit.cshtml でエラーが。。。
Create.cshtml
@model EfCoreSample.Models.Movie @{ ViewData["Title"] = "Create"; } < h1>Create< /h1> < h4>Movie< /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="ReleaseDate" class="control-label">< /label> < input asp-for="ReleaseDate" class="form-control" /> < span asp-validation-for="ReleaseDate" class="text-danger">< /span> < /div> < div class="form-group"> < label asp-for="Genre" class="control-label">< /label> < input asp-for="Genre" class="form-control" /> < span asp-validation-for="Genre" class="text-danger">< /span> < /div> < div class="form-group"> < label asp-for="Price" class="control-label">< /label> < input asp-for="Price" class="form-control" /> < span asp-validation-for="Price" class="text-danger">< /span> < /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 < /div> @{ // ここでエラー } @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
「Cannot resolve section 'Scripts'」と出ます。
また、エラーの個所をコメントアウトして実行しても、ボタンが動作しません orz
どうしてこうなった/(^o^)\
エラーを何とかする
とりあえずエラーを直します。
やるべきことは、「@section Scripts{}」の削除です。
・・・まじかよ。。。
Create.cshtml
~省略~ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
これで問題なく動作します。
また、そもそも上記が不要なら削除してしまっても OK です。
ボタンなどが動くようにする
ボタンなどが動作しない原因は、 TagHelper が無いためです。
ということで、先頭に addTagHelper を追加します。
Create.cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers ~省略~
TagHelper については他の画面も同様であるため、同じように追加します。
Visual Studio でスキャフォールドしたときも同じような状態になるのでしょうか。
それともみんな使ってなry
ルーティング
あ、あとここまでの設定だと生成された MovieController のルーティングが効かず、真っ白な画面が表示されてしまうため、この辺で試したように 設定しておきます。
MovieController.cs
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using EfCoreSample.Models; namespace EfCoreSample.Controllers { public class MoviesController : Controller { private readonly EfCoreSampleContext _context; public MoviesController(EfCoreSampleContext context) { _context = context; } [Route("/")] [Route("/Movies")] // GET: Movies public async TaskIndex() { return View(await _context.Movie.ToListAsync()); } [Route("/Movies/Details/{*id}")] public async Task Details(int? id) { if (id == null) { return NotFound(); } var movie = await _context.Movie .FirstOrDefaultAsync(m => m.MovieId == id); if (movie == null) { return NotFound(); } return View(movie); } [Route("/Movies/Create")] public IActionResult Create() { return View("/Views/Movies/Create.cshtml"); } [HttpPost] [Route("/Movies/Create")] [ValidateAntiForgeryToken] public async Task Create([Bind("MovieId,Title,ReleaseDate,Genre,Price")] Movie movie) { if (ModelState.IsValid) { _context.Add(movie); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } return View(movie); } [Route("/Movies/Edit/{*id}")] public async Task Edit(int? id) { if (id == null) { return NotFound(); } var movie = await _context.Movie.FindAsync(id); if (movie == null) { return NotFound(); } return View(movie); } [HttpPost] [Route("/Movies/Edit/{*id}")] [ValidateAntiForgeryToken] public async Task Edit(int id, [Bind("MovieId,Title,ReleaseDate,Genre,Price")] Movie movie) { if (id != movie.MovieId) { return NotFound(); } if (ModelState.IsValid) { try { _context.Update(movie); await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!MovieExists(movie.MovieId)) { return NotFound(); } else { throw; } } return RedirectToAction(nameof(Index)); } return View(movie); } [Route("/Movies/Delete/{*id}")] public async Task Delete(int? id) { if (id == null) { return NotFound(); } var movie = await _context.Movie .FirstOrDefaultAsync(m => m.MovieId == id); if (movie == null) { return NotFound(); } return View(movie); } [HttpPost, ActionName("Delete")] [Route("/Movies/Delete/{*id}")] [ValidateAntiForgeryToken] public async Task DeleteConfirmed(int id) { var movie = await _context.Movie.FindAsync(id); _context.Movie.Remove(movie); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } private bool MovieExists(int id) { return _context.Movie.Any(e => e.MovieId == id); } } }
DB のマイグレーション
CSS などが無いため非常に簡素ではあるものの、 CRUD ができそうなページが出来上がりました。
が、そのまま実行すると DB は生成されますが、テーブルが見つからないとエラーになります。
DB にテーブルを追加するには、マイグレーションを実行します。
dotnet ef migrations add AddMovie dotnet ef database update
CUI は GUI より最初とっつきにくい印象があるものの覚えた後は楽なので(個人の感想です)、どちらで慣れるのが良いのかは決めづらいどころですが。
これでとりあえず CRUD ができるようになりました。
次回はもう少し生成されたものの中身を見てみたいと思います。