前回は Riot.js を使って Hello World してみました。
今回は Web API からデータを取得して一覧を表示するところをやってみます。
- ASP.NET Core で Riot.js その1
- ASP.NET Core で Riot.js その2
開発環境
Windows 10 Pro
Visual Studio Code
Server-side
Server-side に Web API を実装します。
今回作成する HTTP メソッドは以下となります。
API | 概要 | Request body | Response body |
---|---|---|---|
GET /api/v1/person | Person の一覧を取得する。 | なし | Personの一覧 |
GET /api/v1/person/{id} | id に紐付く Person を取得する。 | なし | Person |
POST /api/v1/person | Person を作成する。 | Person | Person |
PUT /api/v1/person/{id} | id に紐付く Person を更新する。 | Person | なし |
DELETE /api/v1/person/{id} | id に紐付く Person を削除する。 | なし | なし |
今回は一覧を表示するだけなので一番上の GET メソッドだけあればよいのですが、 CRUD 操作に必要なメソッドを一通り作成しておきます。まずは HTTPメソッドでやり取りするために必要な Model となるクラスを作成していきます。
Model
Model を作成していきます。今回は Building Your First Web API with ASP.NET Core MVC and Visual Studio を参考に Repository パターンを使ってやってみたいと思います。
ここでは以下のことをおこないます。
- Model クラスの作成
- Repository クラスの作成
- 作成したRepository クラスの登録 (DI)
まずは Models フォルダに必要なファイルを作成します。Visual Studio Code のターミナルから以下のコマンドを入力してください。
mkdir Models & cd Models
yo aspnet:Class Person
yo aspnet:Interface IPersonRepository
yo aspnet:Class PersonRepository
cd ..
Model クラスの作成
Models フォルダに作成した Person.cs のコードを以下のように変更します。
namespace AspRiotApp.Models
{
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
Repository クラスの作成
Repository を作成します。
まずは IPersonRepository
に Repository クラスで実装するデータアクセスロジックを定義します。Models フォルダに作成した IPersonRepository.cs のコードを以下のように変更します。
using System.Collections.Generic;
namespace AspRiotApp.Models
{
public interface IPersonRepository
{
void Add(Person person);
IEnumerable<Person> GetAll();
Person Find(string id);
void Remove(string id);
void Update(Person person);
}
}
続いて作成したインターフェイスに定義されているデータアクセスロジックを PersonRepository
に実装します。 Models フォルダに作成した PersonRepository.cs のコードを以下のように変更します。
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace AspRiotApp.Models
{
public class PersonRepository : IPersonRepository
{
private static ConcurrentDictionary<string, Person> _people =
new ConcurrentDictionary<string, Person>();
public PersonRepository()
{
Add(new Person { Name = "Eiji", Age = 35 });
Add(new Person { Name = "Akemi", Age = 35 });
Add(new Person { Name = "Satoru", Age = 7 });
Add(new Person { Name = "Madoka", Age = 7 });
}
public IEnumerable<Person> GetAll() => _people.Values;
public void Add(Person person)
{
person.Id = Guid.NewGuid().ToString();
_people[person.Id] = person;
}
public Person Find(string id)
{
var person = default(Person);
_people.TryGetValue(id, out person);
return person;
}
public void Remove(string id)
{
var person = default(Person);
_people.TryRemove(id, out person);
}
public void Update(Person person) => _people[person.Id] = person;
}
}
実際は Database からデータを取得してマッピングしたりなどすると思いますが、今回はモックとして private な 静的メンバとして _people
コレクションを用意して、適当なデータを突っ込んでいます。
private static ConcurrentDictionary<string, Person> _people = new ConcurrentDictionary<string, Person>();
System.Collections.Concurrent.ConcurrentDictionary は値のペアのコレクションです。値のペアからなるコレクションということで System.Collections.Generic.Dictionary と似ていますが ConcurrentDictionary は同時に複数のスレッドからアクセスすることができる(スレッド セーフ)という特徴を持っています。
作成したRepository クラスの登録
作成したRepository クラスをサービスとして登録します。ASP.NET Core プロジェクトで新しくサービスを登録する場合は Startup.cs に定義されている ConfigureServices()
メソッドに処理を追加していきます。
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// リポジトリを登録
services.AddSingleton<IPersonRepository, PersonRepository>();
}
AddSingleton()
メソッドを呼んで作成した Repository クラスをひとつだけインスタンス化するよう DI コンテナに追加しています。ここで登録したサービスは Controller などのコンストラクタで受取ることで利用できます。
これで Model の作成は完了です。つぎは Controller を作成してWeb API として今回提供する HTTP メソッドを実装します。
Controller
ターミナルから以下のコマンドを入力して Controllers フォルダに PersonController.cs を作成します。
cd Controllers
yo aspnet:WebApiController PersonController
cd ..
作成したらコードを以下のように変更します。
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using AspRiotApp.Models;
namespace AspRiotApp.Controllers
{
[Route("api/v1/[controller]")]
public class PersonController : Controller
{
private IPersonRepository People { get; set; }
public PersonController(IPersonRepository people)
{
People = people;
}
// GET api/v1/person
[HttpGet]
public IEnumerable<Person> Get() => People.GetAll();
// GET api/v1/person/{id}
[HttpGet("{id}", Name="GetPerson")]
public IActionResult Get(string id)
{
var person = People.Find(id);
if(person == null)
return NotFound();
return new ObjectResult(person);
}
// POST api/v1/person/
[HttpPost]
public IActionResult Post([FromBody]Person person)
{
if (person == null)
return BadRequest();
People.Add(person);
return new CreatedResult("GetPerson", person);
}
// PUT api/v1/person/{id}
[HttpPut("{id}")]
public IActionResult Put(string id, [FromBody]Person person)
{
if (person == null || person.Id != id)
return BadRequest();
if (People.Find(id) == null)
return NotFound();
People.Update(person);
return new NoContentResult();
}
// DELETE api/v1/person/{id}
[HttpDelete("{id}")]
public void Delete(string id) => People.Remove(id);
}
}
Injection
コントラクタで先ほど登録した Repository サービスをインジェクションして受取ります。
private IPersonRepository People { get; set; }
public PersonController(IPersonRepository people)
{
People = people;
}
Attribute
クラスに Attribute を付加してルーティングします。(api/v1/person)
[Route("api/v1/[controller]")]
public class PersonController : Controller
{
...
}
GET/POST/PUT/DELETE など Web API として今回提供する HTTP メソッドを定義するときは対応する Attribute をメソッドに付加します。
// GET api/v1/person
[HttpGet]
public IEnumerable<Person> Get() => People.GetAll();
付加できる Attribute は以下となります
- HttpDelete
- HttpGet
- HttpHead
- HttpOptions
- HttpPatch
- HttpPost
- HttpPut
URLにパラメータを指定する場合は Attribute の引数に指定します。
// DELETE api/v1/person/{id}
[HttpDelete("{id}")]
public void Delete(string id) => People.Remove(id);
アクション名を指定する場合は Attribute の Name
に指定します。
// GET api/v1/person/{id}
[HttpGet("{id}", Name="GetPerson")]
public IActionResult Get(string id)
{
...
}
戻り値
メソッドの戻り値には IEnumerable<Person>
など任意の型を返すことができます。ステータスコードは 200 (OK) となります。
[HttpGet]
public IEnumerable<Person> Get() => People.GetAll();
また、 戻り値がないメソッドも扱えます。 この場合のステータスコードは 204 (No Content) となります。
[HttpDelete("{id}")]
public void Delete(string id) => People.Remove(id);
IActionResult は処理結果に応じてステータスコードを返す必要がある場合に使います。例として Put()
メソッドでは条件によって BadRequest()
や NotFound()
などそれぞれのステータスコードに対応するメソッドを呼び出しています。
[HttpPut("{id}")]
public IActionResult Put(string id, [FromBody]Person person)
{
if (person == null || person.Id != id)
return BadRequest();
if (People.Find(id) == null)
return NotFound();
People.Update(person);
return new NoContentResult();
}
Put()
メソッドの条件とステータスコードは以下となります。
ステータスコード | 条件 |
---|---|
400 (Bad Request) | 引数で与えられてる値に不正がある場合 |
404 (Not Found) | 引数の id からデータを検索して一致するデータが見つからなかった場合 |
204 (No Content) | 更新に成功した場合 |
最後にプロジェクト作成時にデフォルトで作成されている ValuesController.cs は使わないので削除すれば Server-side の実装は終わりです。続いて Client-side の実装をおこないます。
Client-side
Client-side では index.html と app.tag の修正をおこないます。
html
index.html は以下となります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ASP.NET Core で Riot.js</title>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
<!-- app.tag の読込み -->
<script src="app.tag" type="riot/tag"></script>
<!-- riot+compiler.min.js の読込み -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/riot/2.5.0/riot+compiler.min.js"></script>
</head>
<body>
<!-- app タグ -->
<app></app>
<!-- 作成したタグをマウントする -->
<script>
riot.mount('*');
</script>
</body>
</html>
Web API とのやり取りを jQuery を使って Ajax 通信でおこないたいので jQuery を読込むようにします。
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
あと前回 app
タグの title
属性に値を入れていましたが今回は使わないので取りました。
<app></app>
Tag
app.tag に Ajax 通信で GET してレスポンス結果を一覧で表示する処理を実装します。
<app>
<h1>People</h1>
<ul>
<li each="{ person in people }" title="{ person.name }">
<a href="#item/{person.id}">{ person.name } ({ person.age })</a>
</li>
</ul>
<style scoped>
:scope { display: block }
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
margin: 0.8em 0;
}
a {
background: #eee;
border-radius:0.3em;
color: inherit;
display: block;
padding: 1.2em;
text-decoration: none;
}
a:hover {
background: #7ACBE2
}
</style>
// 一覧
this.people = [];
var self = this;
// GET
$.ajax({
url : '/api/v1/person',
type : 'GET'
})
.then(
function (result) {
// 取得結果を一覧に設定
self.people = result;
// 更新
self.update();
},
function () {
console.log('失敗');
}
);
</app>
Ajax
$.ajax()
で GET リクエストして people
にレスポンス結果を設定しています。
// GET
$.ajax({
url : '/api/v1/person',
type : 'GET'
})
.then(
function (result) {
// 取得結果を一覧に設定
self.people = result;
// 更新
self.update();
},
function () {
console.log('失敗');
}
);
今回は $.ajax()
のオプションに url
と type
だけ指定してますが、本番では timeout
を指定したりする必要があると思います。また、Web API の内容を変更した場合など再度 GETリクエストしたときに IE などで取得結果がうまく反映されないことがあると思います。そのときは cache : false
を指定してあげます。
each
ul
タグ内では取得した結果を一覧で表示するために each="{ person in people }"
で people
コレクションをループして要素を person
として取り出して内容を li
タグにバインドしています。
<li each="{ person in people }" title="{ person.name }">
<a href="#item/{person.id}">{ person.name } ({ person.age })</a>
</li>
これで Client-side の実装は終わりです。dotnet run
してブラウザから localhost に接続すると一覧が表示されます。
次回
次回は Server-side に実装した HTTP メソッドを利用して、Client-side でデータの追加や変更などができるようにします。