vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

ASP.NET Core + TypeScript ってみる 2

はじめに

さくさく進めていきますよ。

とりあえず環境は整ったっぽい(本当はバンドルとか minify とあるだろうけど)ので、実際に動かしてみますよ。

。。。とその前に

IE11 で async/await を使う

もう平成も終わるというのに。。。

まぁお察しくだしあ。くだしあ。

async/await を使ってみる

まず雑に async/await を使って TypeScript のコードを変更してみます。

Page.ts

async function greeting(){
    await setTimeout(() => {
        alert("hello");
    }, 1000);
}

これで greeting() が呼ばれた後 1 秒後にアラートが表示されるはずです。

が、 tsc -b を実行するとエラーが発生します。

wwwroot/ts/Page.ts:1:16 - error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor.  Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option.

1 async function greeting(){

Found 1 error.

Promise が無いからライブラリに追加しろと。

↑のような情報を参考に、 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": ["dom", "es2015"],                             /* Specify library files to be included in the compilation. */
    
    ~省略~
  }
}

これでコンパイルできるようになり、 Firefox などでは問題なく動作するようになりました。

ただし、 IE11 ではまだ Promise が見つからないとエラーになります( target を es3 にしても同様)。

↑を参考に、 Promise-X.X.X.min.js を追加で読み込むことで IE11 でも問題なく動くようになりました。

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="https://www.promisejs.org/polyfills/promise-7.0.4.min.js">< /script>

    < script src="../js/Page.js">< /script>
< /body>
< /html>

IE11 だけでなく、互換性表示を有効にしている場合でも問題なく動作することが確認できました。

ありがたやありがたや(..)_

fetch でアイテムを Get する

まずは CRUD ! 。。。と思ったのですが、まず先行してサーバーにアクセスする部分を書いてみたいと思います。

せっかくなので async / await を使って。

↑ などを見ると、 fetch または axios を使うのが良さそうです。

まずは fetch を試してみます。

https 問題再び

以前触れた気がしますが、現状 ASP.NET Core ではデバッグ実行するとデフォルトで https://localhost:5001 を開きます。

で、何も考えずに下記のようなコードを書いたところ、 loadBooks() でエラーになりました orz

Page.ts

async function loadBooks(): Promise< string>{
    return await fetch("http://localhost:5000/books",
        {
            mode: 'cors',
        })
        .then(function(response) {
            return response.json();
        })
        .then(function(myJson) {
            
            console.log(myJson);
            return myJson;
        });
}

async function greeting(){
    let message = await loadBooks();
    alert(message);
}

エラーの内容は下記の通り。

混在アクティブコンテンツ “http://localhost:5000/books” の読み込みをブロックしました[詳細] Page.js:41:45
TypeError: NetworkError when attempting to fetch resource.

↑によると、 HTTP リクエストと HTTPS リクエストが混在してしまうとセキュリティ上の問題が発生しうるためエラーになる、と。

なるほど。

というわけで、下記のように変更しました。

Page.ts

async function loadBooks(): Promise< string>{
    return await fetch("/books",
        {
            mode: 'cors',
        })
        .then(function(response) {
            return response.json();
        })
        .then(function(myJson) {
            
            console.log(myJson);
            return myJson;
        });
}
~省略~

なお Controller (サーバー側)はこちら。

HomeController.cs

        ~省略~
        [Route("/books")]
        [Produces("application/json")]
        public List< Book> GetBooks()
        {
            return new List< Book>
            {
                new Book
                {
                    Id = 1,
                    AuthorId = 1,
                    Name = "Sample",
                    Available = false,
                }
            };
        }
        ~省略~

Book.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.Serialization;
using Newtonsoft.Json;

namespace EfCoreNpgsqlSample.Models
{
    [Table("Book")]
    public class Book
    {
        [Key]
        [JsonProperty("id")]
        public int Id { get; set; }
        [JsonProperty("authorId")]
        public int AuthorId { get; set; }
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("available")]
        public bool Available { get; set; }       
    }
}

JSON から Model に変換

細かいところはさておき、とりあえずここまでのコードでサーバーから送られた Book クラスのリストを受け取ることができました。

で、せっかくの TypeScript なので、型を持ったデータとして扱いたいですね。

Page.ts

class Book{
    public id: number = -1;
    public authorId : string = "";
    public name: string = "";
    public available: boolean = false;
}
async function loadBooks(): Promise< Book[]>{
    return await fetch("/books",
        {
            mode: 'cors',
        })
        .then(function(response) {
            return response.json();
        })
        .then(function(myJson) {
            var newBooks = JSON.parse(JSON.stringify(myJson)) as Array< Book>;
            return newBooks;
        });
}

async function greeting(){
    let message = await loadBooks();
    alert(message[0].name);
}

結果を見るとシンプルですね。

注意点としては下記の二つぐらいでしょうか。

1. 2 つ目の then の myJson は直接 JSON に変換できない

response.json() の戻り値は Promise< any> です。

で、 2 つ目の myJson は any になるわけですが、これをそのまま JSON.parse に渡すとエラーになります。

そのため、 JSON.stringify でいったん string に変換して渡しています。

2. as T でキャストに失敗しても null にならない

C# に慣れすぎた。。。orz

C# の as は、キャストに失敗すると null になります。

で、そのオブジェクト(今回は newBooks )にアクセスすれば、 NullReferenceException になります。

が、 TypeScript では null にならず、下記は NullReferenceException ではなく、エラーも発生しません。

// 本当は Array< Book>
var newBooks = JSON.parse(JSON.stringify(myJson)) as Book;
            
if(newBooks == null){
    console.log("null death 1");
}
if(newBooks === null){
    console.log("null death 2");
}
// undefined. ただしエラーは発生しない.
var name = newBooks.name;

メソッドを呼んだ場合はエラー( undefined )が発生しますが、最初 JSON からの変換に失敗しているのかと確認に時間を費やしてしまいました/(^o^)\

C# と同じように書ける部分がありつつも、違うところは当然あるので、しっかり見分けないとハマりこんでしまいそうです。

次回は fetch の option ( fetch() の第二引数)からスタートの予定。

あと、 DOM 周りも触っておきたいところ。