ASP.NET Core

x

Angular CLI

で始める SPA 開発

About me

  • 桐生 達嗣(きりゅう たつし)
  •  
  • Developer Support Engineer
  • Front End Engineer
  • Social

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 の動作環境

  • Windows
    • 7 SP1+, 8.1, 10 Version 1607+
    • Visual Studio 2017 verson 15.3+
  • macOS
    • macOS 10.12 "Sierra"+

    • Visual Studio for Mac 7.1+

  • Linux

    • Red Hat Enterprise Linux 7
    • and many other distributions...

 

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

  1. 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,562