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");
}
~省略~
これでルーティングが効くようになりました。
いったん切ります。