ASP.NET Core
x
Angular CLI
で始める SPA 開発
About me
ASP.NET Core 2.0
.NET Standard 2.0 を対象
- .NET Standard 2.0 に準拠した
- .NET Core 2.0
- .Net Framework 4.6.1
- で動作する
.NET Core 2.0 の動作環境
ASP.NET Core metapackage
- 実際には Nuget から何もダウンロードされない
-
Runtime Store(後述) の全パッケージを参照
- 個別パッケージの管理から解放
-
※.NET Framework 4.6.1 の場合は引き続き個別指定
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
</Project>
Rumtime Store
-
GAC のようなもの
-
Windows:C:/Program Files/dotnet/store
-
macOS/Linux:/usr/local/share/dotnet/store
-
- メリット(framework-dependent deployment )
- アプリ自体のサイズの削減、展開の高速化
- デプロイ先のマシンのディスク使用量の削減
Runtime Store
App1.dll + 3rdparty.dll
App2.dll + 3rdparty.dll
ASP.NET Core 2.0
SPA Templates
SPA Template を使うための条件
- Node.js 6+
- node -v
- npm -v
- VS 2017 に入っているのは
- Node.js 5.4.1 ❌
- npm 3.3.4
- Node.js 8 系がオススメ
- 同梱されている npm 5 はダウンロードが速い
注意点
- インストールした Node.js をVS で認識するための設定
Templates on VS 2017
Templates on .NET Core CLI
dotnet new
Templates on .NET Core CLI
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Angular Template
Features
- Client Routing
- Server Side Prerendering
- Webpack dev middleware(In Development Mode)
- 編集のたびにビルド
- Hot Module replacement(In Development Mode)
- リロードなしに直接DOMを書き換え
Folder structure - Client
Built code for client
Built code for SSPR
Bootstrapping code
Application code
Test code
Angular Fundamentals
-
Component は部品
-
Module は 部品を1つの機能としてまとめる仕組み
-
Angular アプリケーションは1つ以上の Module から構成
-
アプリ起動時に呼び出されるモジュールはルートモジュールと呼ばれ、慣例的に AppModule と命名される。アプリのエントリーポイント。
AppModule
Comp1
Comp2
Comp3
Bootstrapping
ルートモジュールの起動
import { AppModule } from './app/app.module.browser';
const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);
AppModule
- アプリのエントリーポイント
- AppComponent は最初に起動されるルートコンポーネント
@NgModule({
bootstrap: [ AppComponent ],
imports: [
BrowserModule,
AppModuleShared
],
providers: [
{ provide: 'BASE_URL', useFactory: getBaseUrl }
]
})
export class AppModule {
}
AppModuleShared
ルーティングの設定
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
CounterComponent,
FetchDataComponent,
HomeComponent
],
imports: [
CommonModule,
HttpModule,
FormsModule,
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: '**', redirectTo: 'home' }
])
]
})
export class AppModuleShared {
}
AppComponent
router-outlet の部分が動的に切り替わる
<div class='container-fluid'>
<div class='row'>
<div class='col-sm-3'>
<nav-menu></nav-menu>
</div>
<div class='col-sm-9 body-content'>
<router-outlet></router-outlet>
</div>
</div>
</div>
URL | Tag |
---|---|
"/home" | <home></home> |
"/counter" | <counter></counter> |
"/fetch-data" | <fetchdata></fetchdata> |
"/", ”/hoge” | <home></home> |
How to fetch data from Web API
fetchdaga.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'fetchdata',
templateUrl: './fetchdata.component.html'
})
export class FetchDataComponent {
public forecasts: WeatherForecast[];
constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
http.get(baseUrl + 'api/SampleData/WeatherForecasts')
.subscribe(result => {
this.forecasts = result.json() as WeatherForecast[];
}, error => console.error(error));
}
}
interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
SampleDataController.cs
private static string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet("[action]")]
public IEnumerable<WeatherForecast> WeatherForecasts()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
});
}
public class WeatherForecast
{
public string DateFormatted { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF
{
get
{
return 32 + (int)(TemperatureC / 0.5556);
}
}
}
Folder structure - Server
Server
Server Side Prerendering
- 事前(ビルド時)にレンダリングしておく
- ASP.NET Core は、レンダリング結果を cshtml に取り込んでクライアントに返却
-
Views/Home/Index.cshtml の中でプリレンダリングされた main-server.js の内容で置き換え
@{
ViewData["Title"] = "Home Page";
}
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
<script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts {
<script src="~/dist/main-client.js" asp-append-version="true"></script>
}
Server Side Rendering(SSR)
- ASP.NET MVC、Java Servlet、PHPなどがこれまでやってきたような単にサーバーサイドでHTMLをレンダリングする、ということではない。
- SPA の問題
- 基本的に DOM の描画はJSフレームワークがクライアントで行う
- サーバー側にコンテンツがないため、検索エンジンが情報をクロールできない
- クライアントの描画に時間がかかる
- JSフレームワークがやっている描画処理を、サーバーでも行うことにより上記の問題を解消
- SEOの向上
- パフォーマンスの向上
Problems with SSR
- Node.js サーバを実行できる環境が必要
-
環境セットアップ、実装の複雑化
-
サーバーのCPU負荷が上がる
-
キャッシュ対策が必要になる
-
動的コンテンツはそもそもキャッシュしてもあまり意味がない
-
Angular CLI
Why Angular CLI?
- Angular アプリケーションを作成する必須ツール
- コマンド一発でプロジェクト作成
- ビルド環境、実行環境、テスト環境がすぐに揃う
- Angular Team の培ったベストプラクティスに倣っている
- Scafolding Component、Service、Module etc...
- Angular CLI をアップデートすることで Angular パッケージ全てをアップデート(パッケージ個別の管理から解放)
Installation & Usage
- https://github.com/angular/angular-cli
- 動作環境
- Node.js 6.9.0+
- npm 3+
# Instrallation
npm install -g @angular/cli
# Create
ng new my-app
# Run
cd my-app
ng serve -o
# Build
ng build --prod
# Test
ng test
ASP.NET Core
x
Angular CLI
ASP.NET Core x Angular CLI
- Create an ASP.NET Core Web API project
ASP.NET Core x Angular CLI
2. Run the Web API via Whack Whack Terminal and check using Postman.
ASP.NET Core x Angular CLI
3. Add dotnet watch tool to the project
Then "dotnet restore"
ASP.NET Core x Angular CLI
4. Move to the solution folder(not to the project folder)
ASP.NET Core x Angular CLI
5. Create an Angular application via Angular CLI command
ASP.NET Core x Angular CLI
6. Add the Angular project as an existing website to the solution.
ASP.NET Core x Angular CLI
7. Run the Angular project via NPM Task Runner
ASP.NET Core x Angular CLI
8. Import HttpClientModule to the AppModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ASP.NET Core x Angular CLI
8. Inject HttpClient to the AppComponent and fetch data
import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app';
values: string[];
constructor(private _httpClient: HttpClient) { }
ngOnInit() {
this._httpClient.get<string[]>('/api/values')
.subscribe(response => this.values = response);
}
}
<ul>
<li *ngFor="let value of values">
{{ value }}
</li>
</ul>
app.component.ts
app.component.html
ASP.NET Core x Angular CLI
9. Proxy API call
{
"name": "client",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
・・・
}
{
"/api": {
"target": "http://localhost:1714",
"secure": false
}
}
package.json
poxy.conf.json
ASP.NET Core x Angular CLI
10. Release build configuration
{
"name": "client",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve -o --proxy-config proxy.conf.json",
"build": "ng build",
"build:prod": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
・・・
}
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "client"
},
"apps": [
{
"root": "src",
"outDir": "../Server/wwwroot",
"assets": [
"assets",
"favicon.ico"
],
・・・
}
package.json
angular-cli.json
ASP.NET Core x Angular CLI
11. SPA Routing
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});
}
Startup.cs
ASP.NET Core x Angular CLI
12. Add HomeController.cs and always return index.html
public class HomeController : Controller
{
// GET: /<controller>/
public IActionResult Index()
{
return File("/index.html", "text/html");
}
}
HomeController.cs
ASP.NET Core x Angular CLI
13. Deploy to Azure
http://netconf2017tokyo.azurewebsites.net/
Summary
- ASP.NET Core プロジェクト作成
- Angular CLI プロジェクト作成しWebSiteとして追加
- Proxyの設定
- Angular CLI プロジェクトのビルド結果を wwwroot へ出力先を変更
- index.html のみを返すようASP.NET Core のルーティングを設定
Useful Tools
Scenarios
- 再構築
- クライアント側をごっそり変えたい
- サーバー側をごっそり変えたい
- →プロジェクトごと取り替え
- 移行
- 既存のASP.NET MVC アプリケーションがあって、部分的にSPAを採用したい
- 既存のASP.NET MVC アプリケーションがあって、完全 SPA に移行したい
- →Angular CLI プロジェクトを追加するだけ
- サーバープロジェクトとクライアントプロジェクトが完全に分離されていることで、取り替え、付け外しが簡単
AngularMix
- https://angularmix.com/#!/
- Angular と Enterprise テクノロジーに関する大規模 カンファレンス
- サーバーサイドテクノロジーとして Node.js と並んで ASP.NET Core が取り上げられる予定
- Sessions
- ASP.NET CORE APIS FOR ANGULAR
- ASP.NET CORE FOR ANGULAR DEVELOPERS
ASP.NET Core x Angular CLI で始める SPA 開発
By Tatsushi Kiryu
ASP.NET Core x Angular CLI で始める SPA 開発
- 6,549