ASP.NET Core 3.1 & Vue.js 上で、Handsontableを動かす機会があったため、メモを残します。
目次
- 環境
- 環境構築
- ASP.NET CoreでJSONを返すAPIの作成と連携
- ASP.NET CoreでモデルのJSONを返すAPIの作成と連携
- ソースコード
環境
環境構築
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のカスタマイズもできそうでした。
そのため、このテンプレートを使うことにしました。
ASP.NET & 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"], // コンテキストメニューまわり // contextMenu: true, // trueにすると、ブラウザのコンテキストメニューが表示されない 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"> <!-- Handsontableの表示へと変更 <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript 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です。
ASP.NET CoreでJSONを返すAPIの作成と連携
まずは、定形JSONを返すASP.NET Core APOIを作成し、そのレスポンスをHandsontableへと反映させてみます。
Vueコンポーネントの作成
先ほどと同じようなコンポーネントを作成します。
違いとしては、
となります。
<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: [], // Name列だけ、幅を指定する colWidths: [null, 200, null], colHeaders: ["No", "Name", "Price"], rowHeaders: ["1st", "2nd", "3rd"], // コンテキストメニューまわり // contextMenu: true, // trueにすると、ブラウザのコンテキストメニューが表示されない allowInsertColumn: false, allowRemoveColumn: false, // 列のソートインディケータを表示 columnSorting: { indicator: true } } } }, created: function() { // ロードされた時にAPIを呼んで、Handsontableの初期値を取得する 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"> <!-- Handsontableの表示へと変更 <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript 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でモデルのJSONを返すAPIの作成と連携
最後に、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
Vue.jsのコンポーネントを作成
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: [], // Name列だけ、幅を指定する colWidths: [null, 200, null], colHeaders: ["No", "Name", "Age"], rowHeaders: ["1st", "2nd", "3rd"], // コンテキストメニューまわり // contextMenu: true, // trueにすると、ブラウザのコンテキストメニューが表示されない allowInsertColumn: false, allowRemoveColumn: false, // 列のソートインディケータを表示 columnSorting: { indicator: true } } } }, created: function() { // ロードされた時にAPIを呼んで、Handsontableの初期値を取得する 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"> <!-- Handsontableの表示へと変更 <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript 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
として、
- ログをコンソールに出力
- SQLiteを使う
の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