ASP.NET Core 3.1 & Vue.js 上で、Handsontableを動かす機会があったため、メモを残します。
目次
環境
環境構築
ASP.NET Core 3.1向けのVue.jsプロジェクトテンプレートについて
RiderなどのIDEでは、ASP.NET Coreで使えるVue.jsのテンプレートがGUIでは選択できません。
Vue向けの公式テンプレートは、dotnetコマンドでインストールできるようですが、試してみたところ、Core3.x系のテンプレートにはなりませんでした。
ASP.NET Core のテンプレートで Vue をインストール - Qiita
ほかのテンプレートを探したところ、starの多い aspnetcore-Vue-starter
がありました。
TrilonIO/aspnetcore-Vue-starter: NEW Asp.net Core & Vue.js (ES6) SPA Starter kit - Vuex, webpack, Web API, Docker, and more! By @TrilonIO
ただ、こちらもCore3系には対応していませんでした。
Please Upgrade to Core 3 · Issue #138 · TrilonIO/aspnetcore-Vue-starter
Core3系に対応したテンプレートを探したところ、以下がありました。
SoftwareAteliers/asp-net-core-vue-starter: ASP.NET Core + Vue.js starter project
また、
(Optional) Scaffold Vue.js app with custom configuration
と、Vue.jsのカスタマイズもできそうでした。
そのため、このテンプレートを使うことにしました。
テンプレートのインストール
dotnet new -i SoftwareAteliers.AspNetCoreVueStarter
テンプレートのリストを見ると、他のVue.js向けテンプレートをインストールしていたせいか、Short Nameが重複してしまいました。
そのため、テンプレートを使う時は、 Templatesに記載されている .NET Core Vue.js
を指定します。
$ dotnet new list
Templates Short Name
------------------------------------------------------------
...
ASP.NET Core with Vue.js vue
...
.NET Core Vue.js vue
プロジェクトの作成
# ソリューションファイルを作成
$ dotnet new sln -n HandsonTableVueOnAspNetCore
# テンプレート名を使って、プロジェクトを作成
$ dotnet new ".NET Core Vue.js" -o HandsonTableVueOnAspNetCore
# プロジェクトをソリューションに追加
$ dotnet sln add ./HandsonTableVueOnAspNetCore
プロジェクト `HandsonTableVueOnAspNetCore/HandsonTableVueOnAspNetCore.csproj` をソリューションに追加しました。
# Vue.jsのカスタマイズをするため、READMEにある通り、ClientAppを削除
$ rm -rf ClientApp/
Vue CLIによる、Vue.jsの環境構築
READMEに従い、 client-app
という名前で生成しました。
ひとまずRouterだけ追加しておきます。
$ vue create client-app
Vue CLI v4.1.2
? Please pick a preset: Manually select features
? Check the features needed for your project: Router
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
TypeScriptの追加
client-appに対し、TypeScriptを追加します。
ただし、今回はTypeScript化までは行わないため、不要な場合はパスしても大丈夫です。
現時点では、Handsontable向けには、TypeScriptは3.6系を使っておくのが良さそうなので、バージョン指定してインストールします。
Import declaration conflicts with local declaration of 'HotTableProps'. · Issue #145 · handsontable/vue-handsontable-official
$ cd client-app/
$ npm install --save-dev typescript@3.6.4
+ typescript@3.6.4
続いて、Vue.jsに追加します。
TSLintの代わりにESLintを使うため、TSLint以外はデフォルトのままで進めます。
ワーニングが出ますが、とりあえずこのままで進めます。
$ vue add typescript
? Still proceed? Yes
✔ Successfully installed plugin: @vue/cli-plugin-typescript
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use TSLint? No
? Convert all .js files to .ts? Yes
? Allow .js files to be compiled? No
WARN conflicting versions for project dependency "typescript":
- ^3.6.4 injected by generator "undefined"
- ~3.5.3 injected by generator "@vue/cli-plugin-typescript"
Using newer version (^3.6.4), but this may cause build errors.
ESLintの追加
$ npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue
+ eslint@6.8.0
+ eslint-plugin-vue@6.1.2
+ @typescript-eslint/parser@2.14.0
+ @typescript-eslint/eslint-plugin@2.14.0
Prettier
$ npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier
+ prettier@1.19.1
+ eslint-config-prettier@6.9.0
+ eslint-plugin-prettier@3.1.2
client-app内にある、不要なgitリポジトリを削除
今回はASP.NET Coreも含めてリポジトリ管理するため、削除しておきます。
$ rm -rf .git/
READMEにある通り、 client-app
から ClientApp
へと変更します。
$ cd ..
$ mv client-app/ ClientApp/
動作確認
dotnet run
コマンドで起動し、ASP.NET Core & Vue.jsが動作していることを確認します。
$ dotnet run
Vue.js & Handsontableの環境構築
vue-handsontable-officialのインストール
Vue.js向けとして、Handsontable公式が vue-handsontable-official
を提供しています。
handsontable/vue-handsontable-official: Vue Data Grid with Spreadsheet Look & Feel. Official Vue wrapper for Handsontable.
そのため、インストールします。
$ npm install handsontable @handsontable/vue
...
Handsontable is no longer released under the MIT license. Read more about this change on our blog at https://handsontable.com/blog.
+ handsontable@7.3.0
+ @handsontable/vue@4.1.1
実装
Handsontableを使ったコンポーネントを作成
ClientApp/src/components/HelloHandsontable.vue
としてコンポーネントを作成します。
<template>
<div>
<hot-table :settings="hotSettings" />
</div>
</template>
<script>
import {HotTable} from "@handsontable/vue";
export default {
name: "HelloHandsontable",
components: {
HotTable
},
data() {
return {
hotSettings: {
licenseKey: 'non-commercial-and-evaluation',
data: [
[1, "紅あずま", 10],
[2, "紅はるか", 20],
[3, "シルクスイート", 30],
],
colHeaders: ["No", "Name", "Price"],
rowHeaders: ["1st", "2nd", "3rd"],
allowInsertColumn: false,
allowRemoveColumn: false,
columnSorting: {
indicator: true
}
}
}
}
}
</script>
<style>
@import '~handsontable/dist/handsontable.full.css';
.handsontable thead th .relative {
background-color: deepskyblue;
}
</style>
App.vueの修正
templateタグの差し替えと、script内にコンポーネントを追加します。
<template>
<div id="app">
<div>
<h3>Hello Handsontable</h3>
<HelloHandsontable />
</div>
</div>
</template>
<script lang="ts">
import HelloHandsontable from "@/components/HelloHandsontable.vue";
@Component({
components: {
HelloHandsontable,
HelloWorld,
},
})
export default class App extends Vue {}
</script>
開発証明書の適用
もし開発証明書の信頼を行っていない場合、行っておきます。
Windows および macOS で ASP.NET Core HTTPS 開発証明書を信頼する | ASP.NET Core に HTTPS を適用する | Microsoft Docs
$ dotnet dev-certs https --trust
動作確認
ここまでで、ASP.NET Core & Vue.js & Handsontableの環境ができましたので、動作を確認します。
dotnet run
コマンドで起動します。
$ dotnet run
https://localhost:5000
へアクセスし、以下が表示されればOKです。
まずは、定形JSONを返すASP.NET Core APOIを作成し、そのレスポンスをHandsontableへと反映させてみます。
先ほどと同じようなコンポーネントを作成します。
違いとしては、
- hotSettingsに渡す
data
の初期値は、空の配列にする
- created()で、APIからJSONを受け取り、Handsontableに反映する
となります。
<template>
<div>
<hot-table :settings="hotSettings" />
</div>
</template>
<script>
import {HotTable} from "@handsontable/vue";
export default {
name: "ApiResponseHandsontable",
components: {
HotTable
},
data() {
return {
hotSettings: {
licenseKey: 'non-commercial-and-evaluation',
data: [],
colWidths: [null, 200, null],
colHeaders: ["No", "Name", "Price"],
rowHeaders: ["1st", "2nd", "3rd"],
allowInsertColumn: false,
allowRemoveColumn: false,
columnSorting: {
indicator: true
}
}
}
},
created: function() {
fetch('/api/const')
.then(res => { return res.json() })
.then(data => {
this.hotSettings.data = JSON.parse(data);
})
},
}
</script>
<style>
@import '~handsontable/dist/handsontable.full.css';
.handsontable thead th .relative {
background-color: deepskyblue;
}
</style>
App.vueの修正
コンポーネントを追加します。
<template>
<div id="app">
<div>
<h3>Hello Handsontable</h3>
<HelloHandsontable />
</div>
<hr>
<div>
<h3>Const Response Handsontable</h3>
<ConstResponseHandsontable />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ConstResponseHandsontable from "@/components/ConstResponseHandsontable.vue";
@Component({
components: {
ConstResponseHandsontable,
HelloHandsontable,
HelloWorld,
},
})
export default class App extends Vue {}
</script>
ASP.NET Coreのコントローラーを作成
/api/const
にアクセスしたときにJSONレスポンスを返すコントローラーを作成します。
コントローラークラスに
- ControllerBaseを継承
[Route("api/")]
属性で、APIのルーティングを設定
[ApiController]
属性で、API向けコントローラーとして動作するよう設定
[Produces(MediaTypeNames.Application.Json)]
属性で、レスポンスをJSONにするように指定
とします。
あとは、 GetConstResponse()
メソッドに、JSONでレスポンスする内容を記載します。
なお、Handsontableのデータ投入メソッド(loadData())に不具合があるようで、7.3.0時点では配列の配列でしかデータを投入できないようです。
Using loadData on an object data doesn't work · Issue #4204 · handsontable/handsontable
8系がリリースされるとオブジェクト配列で投入できそうなので、その時は以下のコードは書き換えたほうが良いかもしれません。
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
using Bogus;
using HandsonTableVueOnAspNetCore.Models;
using Microsoft.AspNetCore.Mvc;
namespace HandsonTableVueOnAspNetCore.Controllers
{
[Route("api/")]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
public class HandsontableApiController : ControllerBase
{
[HttpGet("const")]
public IActionResult GetConstResponse()
{
var apples = new List<dynamic>
{
new List<dynamic> {1, "秋映", 100},
new List<dynamic> {2, "シナノゴールド", 200},
new List<dynamic> {3, "ピンクレディ", 300}
};
return Ok(JsonSerializer.Serialize(apples));
}
}
}
動作確認
まずはAPIの動作を確認します。
localhost:5000/api/const
にアクセスすると、JSONが返ってくればOKです。
$ curl http://localhost:5000/api/const
"[[1,\"\\u79CB\\u6620\",100],[2,\"\\u30B7\\u30CA\\u30CE\\u30B4\\u30FC\\u30EB\\u30C9\",200],[3,\"\\u30D4\\u30F3\\u30AF\\u30EC\\u30C7\\u30A3\",300]]
続いて、 localhost:5000
にアクセスし、JSONの内容がHandosontableに反映されていればOKです。
最後に、ASP.NET CoreのモデルをHandsontableに表示してみます。
環境構築
今回はEntityFramework Core & SQLiteを使うため、必要なパッケージを追加します。
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SQLite
また、念のため、発行されたSQLの中身もコンソール出力するため、パッケージを追加します。
dotnet add package Microsoft.Extensions.Logging.Console
create()でアクセスするAPIのエンドポイントが変更となっただけで、あとは同じです。
<template>
<div>
<hot-table :settings="hotSettings" />
</div>
</template>
<script>
import {HotTable} from "@handsontable/vue";
export default {
name: "ModelResponseHandsontable",
components: {
HotTable
},
data() {
return {
hotSettings: {
licenseKey: 'non-commercial-and-evaluation',
data: [],
colWidths: [null, 200, null],
colHeaders: ["No", "Name", "Age"],
rowHeaders: ["1st", "2nd", "3rd"],
allowInsertColumn: false,
allowRemoveColumn: false,
columnSorting: {
indicator: true
}
}
}
},
created: function() {
fetch('/api/model')
.then(res => { return res.json() })
.then(data => {
this.hotSettings.data = JSON.parse(data);
})
},
}
</script>
<style>
@import '~handsontable/dist/handsontable.full.css';
.handsontable thead th .relative {
background-color: deepskyblue;
}
</style>
App.vueの修正
こちらも、コンポーネントを追加するだけです。
<template>
<div id="app">
<div>
<h3>Hello Handsontable</h3>
<HelloHandsontable />
</div>
<hr>
<div>
<h3>Const Response Handsontable</h3>
<ConstResponseHandsontable />
</div>
<hr>
<div>
<h3>Model Response Handsontable</h3>
<ModelResponseHandsontable />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
import HelloHandsontable from "@/components/HelloHandsontable.vue";
import ConstResponseHandsontable from "@/components/ConstResponseHandsontable.vue";
import ModelResponseHandsontable from "@/components/ModelResponseHandsontable.vue";
@Component({
components: {
ModelResponseHandsontable,
ConstResponseHandsontable,
HelloHandsontable,
HelloWorld,
},
})
export default class App extends Vue {}
</script>
モデルの作成
Models/Customer.cs
として作成します。
namespace HandsonTableVueOnAspNetCore.Models
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
DbContextの作成
Models/HandsontableContext.cs
として、
のDbContextを作成します。
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace HandsonTableVueOnAspNetCore.Models
{
public class HandsontableContext : DbContext
{
public HandsontableContext(DbContextOptions<HandsontableContext> options) : base(options) {}
public DbSet<Customer> Customers { get; set; }
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder =>
{
builder
.AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Information)
.AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
.EnableSensitiveDataLogging()
.UseLoggerFactory(MyLoggerFactory);
}
}
}
appsettings.Development.jsonの修正
SQLite向けの接続文字列を追加します。
今回は開発環境なので、 appsettings.Development.json
ファイルに追加します。
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"HandsontableContext": "Data Source=./WebApplication.db"
}
}
Startup.csの修正
ConfigureServicesに、DbContextを追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<HandsontableContext>(options => options.UseSqlite(
Configuration.GetConnectionString("HandsontableContext")));
}
モデルとDbContextができたため、マイグレーションを行います。
# マイグレーションファイルを作成
$ dotnet ef migrations add InitialCreate
# SQLiteへ反映
$ dotnet ef database update
コントローラーの修正
Seed用URLを作成
デフォルトデータとして投入する方法として以前は OnModelCreating()
をオーバーライドしました。
データシード処理-EF Core | Microsoft Docs
ただ、今回は何度でも使えるよう、Seed用URLを作成します。
c# - How to seed in Entity Framework Core 2? - Stack Overflow
また、デフォルトデータはランダムなもので良いので、 Bogus
を使います。
bchavez/Bogus: A simple and sane fake data generator for C#, F#, and VB.NET. Based on and ported from the famed faker.js.
パッケージをインストールします。
$ dotnet add package Bogus
あとは、コントローラーでDbContextを受け取り、Seed処理を実装します。
GETメソッドでのSeedでいいのか感はありますが、開発用途なのでヨシとします。
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
using Bogus;
using HandsonTableVueOnAspNetCore.Models;
using Microsoft.AspNetCore.Mvc;
namespace HandsonTableVueOnAspNetCore.Controllers
{
[Route("api/")]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
public class HandsontableApiController : ControllerBase
{
private readonly HandsontableContext _context;
public HandsontableApiController(HandsontableContext context)
{
this._context = context;
}
[HttpGet("seed")]
public IActionResult Seed()
{
var faker = new Faker<Customer>("ja")
.RuleFor(r => r.Name, f => $"{f.Name.LastName()} {f.Name.FirstName()}")
.RuleFor(r => r.Age, f => f.Random.Number(20, 60));
var fakes = faker.Generate(3);
_context.Customers.AddRange(fakes.ToArray());
_context.SaveChanges();
var customers = fakes.Select(a => new ArrayList
{
a.Id, a.Name, a.Age
});
return Ok(JsonSerializer.Serialize(customers));
}
}
}
モデルからデータを読み込んでJSONレスポンスするAPI作成
同じくコントローラーに追加します。
/api/model
でJSONレスポンスを行います。
[HttpGet("model")]
public IActionResult GetApplesResponse()
{
var response = _context.Customers.Select(c => new ArrayList
{
c.Id, c.Name, c.Age
});
return Ok(JsonSerializer.Serialize(response));
}
動作確認
データベースへのSeedを行います。
$ curl http://localhost:5000/api/seed
"[[1,\"\\u9AD8\\u6A4B \\u9678\\u6597\",40],[2,\"\\u677E\\u672C \\u7F8E\\u7FBD\",56],[3,\"\\u658E\\u85E4 \\u592A\\u4E00\",52]]"
モデルAPIのレスポンスも確認します。
$ curl http://localhost:5000/api/model
"[[1,\"\\u9AD8\\u6A4B \\u9678\\u6597\",40],[2,\"\\u677E\\u672C \\u7F8E\\u7FBD\",56],[3,\"\\u658E\\u85E4 \\u592A\\u4E00\",52]]"
最後に localhost:5000
にアクセスし、表示できればOKです。
(中身はBogusによるダミーデータです)
Githubに上げました。
thinkAmi-sandbox/Handsontable_On_ASP_NET_Core_Vue-sample