ASP.NET Core + TypeScript ってみる 1
はじめに
今回からちょっと間(挫折するか落ち着くまで) フロントエンドに挑戦してみます。
今のトレンドで言えば、以前触れた Angular のようなフレームワークを駆使して SPA ! 。。。って時代ももう落ち着いてきた?というところですが、今回はあえてフレームワークは使わず、 TypeScript + HTML + CSS(こっちはもう少し何か試すかも) で試してみたいと思います。
理由は使う先で Node.js が使えるとは限らないことと、(プライベートは良いのですが)頻繁にアップデートできるとは限らないことです。
まぁ後者については、 TypeScript 使っていいのか?という気がしないでもないですが。
準備
とにかく準備を進めていきますよ。
サーバーアプリは ASP.NET Core MVC 。このへん で使っていたプロジェクトを再利用しています。
Node.js
で、まず Node.js なのですが、 Windows だと nodist などのバージョン管理マネージャーを使うのが良さそうですね?
ただ、何も考えず Node.js をそのままインストールしてしまったので、今回はこれでいきます。
TypeScript
Rider の Terminal か PowerShell などでプロジェクト直下に移動し、 npm で TypeScript 、 tsc をインストールします(先に空で package.json 作っておいた方が良い?)。
npm install --save-dev typescript tsc
package.json の中身はこうなりました。
package.json
{ "devDependencies": { "tsc": "^1.20150623.0", "typescript": "^3.4.2" } }
最初グローバルにインストールしていたのですが、下記などを見るとローカルインストールの方が良さそうに思えます。
tsc --init
tsc を使って、 tsconfig.json を生成します。
npx tsc --init
今回はローカルインストールしているため、 npx tsc ~ のように実行します。
これでデフォルトの tsconfig.json が生成されます。
tsconfig.json
{ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ ~省略~ // "outDir": "./", /* Redirect output structure to the directory. */ ~省略~ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ ~省略~ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ ~省略~ } }
省略した項目も含め、一つずつ内容を見ていきたいところですが、とりあえず動かしてみる、ということで TypeScript -> JavaScript へ変換した出力先を wwwroot/js に変更します。
tsconfig.json
{ "compilerOptions": { ~省略~ "outDir": "wwwroot/js", /* Redirect output structure to the directory. */ ~省略~ } }
TypeScript ファイルを作る
順番が逆な気もしますが、コンパイル元となる TypeScript を用意します。
wwwroot/ts/Page.ts
function greeting(){ alert("hello world"); }
HTML ファイルを作る
とりあえずボタン押下で JavaScript を呼ぶだけとします。
wwwroot/pages/Index.html
< !DOCTYPE html> < html lang="jp"> < head> < meta charset="UTF-8"> < title>Home< /title> < /head> < body> Hello < button onclick="greeting()">Message< /button> < script src="../js/Page.js">< /script> < /body> < /html>
wwwroot 以下の静的ファイルを有効にする
UseStaticFiles で wwwroot 以下の静的ファイルを有効にしておきます。
Startup.cs
using EfCoreNpgsqlSample.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 EfCoreNpgsqlSample { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.Configure(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddDbContext (options => options.UseNpgsql("Host=localhost;Database=BookStore;Username=postgres;Password=XXX")); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseMvc(); } } }
これで下記のような構成になりました。
EfCoreNpgsqlSample L EfCoreNpgsqlSample L bin L Controllers L HomeController.cs L Models L Author.cs L Book.cs L Store.cs L EfCoreNpgsqlSample.cs L Properties L launchSettings.json L wwwroot L ts L Page.ts L pages L Index.html L appsettings.Development.json L appsettings.json L EfCoreNpgsqlSample.csproj L package.json L package-lock.json L Program.cs L Startup.cs L tsconfig.json
Controllers や Models など今回登場していないものもありますが、このへん 参照ということで。
( HomeController.cs については後で触れます)
TypeScript のコンパイル
TypeScript をコンパイルして、 JavaScript ファイルを作ります。
ここでも tsc を使用します。
npx tsc -b
これで Wwwroot > js 以下に Page.js が出力されます。
Page.js
"use strict"; function greeting() { alert("hello"); }
【Rider】変更時に自動でコンパイルしてほしい
このコンパイル作業、毎回手動で実行するのは結構大変です。
Gulp や npm などを使って自動でコンパイルすることもできますが、今回は JetBrains Rider の力を借りることにします。
(内部的にはこれらのツールを使っているかもですが)
- File > Settings... > Languages & Frameworks > TypeScript を開く
- Node interpreter に Node.js のパスを、 TypeScript に プロジェクト > node_modules にある TypeScript のパスを指定する
- Recompile on changes にチェックを入れる
これで TypeScript のファイルを変更すると、自動でコンパイルされるようになります。
注意点として、 tsc -b のようにフォルダ・ファイルの生成は実行してくれない、というものがあります。
そのため、 TypeScript のファイルを追加したときは tsc -b を実行し、ファイルを生成しておく、というのが良さそうです。
静的ファイルのルーティング
さて、 ASP.NET Core で静的ファイルを有効にすると、 wwwroot 以下のファイルは全て外部からアクセスできるようになります。
例えばログインユーザーのみに見せたいなど、 Controller クラスを使ってルーティングをしたい場合はどうすれば良いでしょうか。
ここで静的ファイルのパスを Route として指定しても効果はありませんでした。
HomeController.cs
~省略~ [Route("/")] [Route("/Home")] [Route("/pages/Index.html")] public string Index() { return "hello"; } ~省略~
これを実行しても、 wwwroot > pages > Index.html が表示されます。
まぁせっかくの ASP.NET Core なので、 Razor を使って View として返してやれば解決するのですが、今回は使わずに頑張ってみることにします。
UseStaticFiles でパス指定
Views の下に置いて。。。など試してみたのですが、結局 wwwroot 直下を公開するのではなく、その下の階層を公開し、ルーティングを行いたいファイル( HTML )を wwwroot 以下の別階層に置く、というのが良さそうという結果になりました。
Startup.cs
~省略~ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "js")), RequestPath = "/js" }); app.UseMvc(); } } }
これで localhost:5000/js/~ のファイルは表示されますが、 localhost:5000/pages/~ などのファイルは表示されなくなりました。
例えば wwwroot/css など他にも公開したい階層がある場合は、 app.UseStaticFiles をもう一つ追加します。
あとは、 Controller を使って表示されなくなった HTML ファイルを表示するようにします。
HomeController.cs
~省略~ [Route("/")] [Route("/Home")] [Route("/pages/Index.html")] public IActionResult Index() { return File("pages/Index.html", "text/html"); } ~省略~
これでルーティングが効くようになりました。
いったん切ります。