ASP.NET Core MVCとデータベースを使ったWebアプリをつくって公開しようとしたところ、ページがうまく表示されないなどいくつかハマった点があったので、今回はそのことについて書きます。
ASP.NET Core MVC は、モデル ビュー コントローラー デザイン パターンを使用して、WebアプリとAPIをビルドするためのフレームワークです。ここではアプリケーションをモデル、ビュー、コントローラーという3つの主要なコンポーネントのグループに分けます。そうすることでコーディング、デバッグ、テストをしやすくします。
基本的にASP.NET Core MVC の概要を参考に進めます。ただSQL Server Express LocalDBではサーバー上に公開できなさそうなので、SQLiteに変更します。
アプリを作成する
文章だけだと説明しにくいので動画もつくりました。動画だと文字が小さくて読みにくいのでこのページとあわせてみていただければわかりやすくなるかもしれません。
最初にデータモデルクラスを追加します。Modelsフォルダーを右クリックし、新しいクラス(Movieクラス)を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.ComponentModel.DataAnnotations; namespace WebApplication1.Models { public class Movie { public int Id { get; set; } public string? Title { get; set; } [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } public string? Genre { get; set; } public decimal Price { get; set; } } } |
そのあと[パッケージ マネージャー コンソール]でMicrosoft.EntityFrameworkCore.SQLiteをインストールします。
1 |
Install-Package Microsoft.EntityFrameworkCore.SQLite |
次にスキャフォールディングを実行します。
Scaffolding(スキャフォールディング)とは、データモデルとなる型を元に、いわゆる追加、読込、変更、削除を行う画面とそのコードを自動で生成する機能です。
コントロールの上で右クリックして新規スキャフォールディングアイテムを選択して、ダイアログで、[Entity Framework を使用したビューがある MVC コントローラー]を選択します。
[モデル クラス] ドロップ ダウンで、 [Movie] を選択します。
[データ コンテキスト クラス] 行で、[+] (プラス) 記号を選択し、生成されたものを選択します。
他はそのままでOKです。
コードが自動生成されます。
その一部を書き換えます。
Program.cs
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 |
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using WebApplication1.Data; var builder = WebApplication.CreateBuilder(args); // この部分を削除して //builder.Services.AddDbContext<WebApplication1Context>(options => // options.UseSqlServer(builder.Configuration.GetConnectionString("WebApplication1Context") ?? throw new InvalidOperationException("Connection string 'WebApplication1Context' not found."))); // 以下に変更する builder.Services.AddDbContext<WebApplication1Context>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); // あとは変更なし // Add services to the container. builder.Services.AddControllersWithViews(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); |
appsettings.json
1 2 3 |
"ConnectionStrings": { "WebApplication1Context": "Server=(localdb)\\mssqllocaldb;Database=WebApplication1.Data;Trusted_Connection=True;MultipleActiveResultSets=true" } |
となっている部分を
1 2 3 |
"ConnectionStrings": { "DefaultConnection": "Filename=./sample.db" } |
と書き換えます。
次にデータベース作成と更新をおこないます。
パッケージマネージャコンソールのウィンドウから以下を一行ずつ実行していきます。
1 2 |
Add-Migration InitialCreate Update-Database |
あとはビルドしてビルドが通れば実際にローカルで動作させてみます。localhost:5000/Moviesにアクセスしてデータの追加、編集、削除ができるか確認します。
サーバーで公開したい
完成したので他の人にも見てもらうために公開したいのですが、ここからハマりどころがたくさんあります。
NET 6.0をエックスサーバーにインストールするにあるようにappsettings.jsonを修正してアップロードするだけではうごきません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "urls": "http://localhost:5000", "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Filename=./sample.db" } } |
エラーで表示されない
エラーの内容はProgram.csを編集すれば表示されるようになります。
Program.cs
1 2 3 4 5 6 7 |
//var builder = WebApplication.CreateBuilder(args); この行を以下のように書き換える var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = Environments.Development // 本当にサーバー上に公開するときには EnvironmentName = Environments.Production に変更する // そうしないとエラーの内容によっては他人にみられると困るものが見えてしまう }); |
すると以下のようなエラーメッセージが表示されます。
DllNotFoundException: Unable to load DLL ‘e_sqlite3’ or one of its dependencies: 指定されたモジュールが見つかりません。 (0x8007007E)
e_sqlite3.dllがないと言っているのですが、一体どこにあるのでしょうか?
bin\Release\net6.0\runtimes\win-x64\nativeのなかにあります。これをサーバーにアップロードするファイルが出力されているフォルダであるbin\Release\net6.0\publishにコピーします。
次にsample.dbというSQLiteのファイルがあるのですが、サイズが0であることに気がつきます。これはソリューションエクスプローラに表示されているsample.dbのプロパティ「出力ディレクトリにコピー:常にコピーする」を選択すれば解決します。
.htaccessを編集する
これであとはサーバー上にアップロードすればいいのかというとそうではありません。
まず新しくサブドメインをつくってそこに公開する場合ですが、トップページにアクセスしたときになにも表示されません。
https://example.lets-csharp.com/ なにも表示されない
https://example.lets-csharp.com/Home/ Welcomeのページが表示される
https://example.lets-csharp.com/Home/Index Welcomeのページが表示される
https://test3.lets-csharp.com/Movies/ 今回作成したアプリケーションが表示される
https://test3.lets-csharp.com/Movies/Index 今回作成したアプリケーションが表示される
これはルーティングのさいに
1 |
pattern: "{controller=Home}/{action=Index}/{id?}"); |
としているため、トップページにアクセスするときはhttps://example.lets-csharp.com/Home/を表示できるようにしておかないといけません。
そのためには.htaccessを以下のように変更します。これで表示されるようになります。
1 2 3 4 5 6 7 8 |
DirectoryIndex Home # この1行を追加 <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTPS} !on RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] RewriteRule ^(.*)$ http://localhost:5000/$1 [P,L] # 5000は適切な値に変更する </IfModule> |
次にサブディレクトリで公開するときですが、リンクや他のタグヘルパーが正常に機能するようにASP.NET Core のタグヘルパーを再定義します。
https://lets-csharp.com/samples/2207/first-mvc/Movies で公開しているので動作確認をしてみてください。
それからもうひとつ修正する箇所があります。MoviesControllerクラスのなかでRedirectToActionメソッドを呼び出していますが、これだと https://lets-csharp.com/samples/2207/first-mvc/Movies/Create で作成の処理が終わったら https://lets-csharp.com/samples/2207/first-mvc/Movies へ戻らないといけないのに https://lets-csharp.com/Movies にリダイレクトされてしまいます。そこでRedirectメソッドを呼び出して https://lets-csharp.com/samples/2207/first-mvc/Movies にリダイレクトさせています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class MoviesController : Controller { [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Title,ReleaseDate,Genre,Price")] Movie movie) { if (ModelState.IsValid) { _context.Add(movie); await _context.SaveChangesAsync(); // return RedirectToAction(nameof(Index));ではなく string url = Global.BaseUrl + "/Movies"; return Redirect(url); // ただし Global.BaseUrl == "https://lets-csharp.com/samples/2207/first-mvc"; } return View(movie); } } |
EditメソッドととDeleteConfirmedメソッドにも似たような箇所があるので修正しておきます。これでサブディレクトリでも公開できるはずです。