はじめに
2022 年 11 月に .NET 6 と Visual Studio 2022 がリリースされました。ということで .NET 6 版の Blazor WebAssembly / ASP.NET Core Web API / Entity Framework を使って CRUD アプリケーションを作っていきたいと思います。
(以前書いたこれの .NET 6 版です)
完成イメージ
環境
- Windows 10 Pro
- Microsoft Visual Studio Community 2022 (64 ビット)
- .NET 6.0
プロジェクト作成
Visual Studio を起動して「新しいプロジェクトの作成」を選択します。
「Blazor WebAssembly アプリ」を選択して「次へ」。
プロジェクト名とソリューション名を「BlazorApps」、任意の場所を選択して「次へ」。
フレームワークに .NET 6.0を選択し、「HTTPS 用の構成」と「ASP.NET Core でホストされた」を有効にして「作成」。
以下のようにプロジェクトが3つ生成されます。Client がフロントエンド、Server がバックエンドで、Shared には共通で利用するクラスを配置します。
プロジェクトファイルを見てみましょう。
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\BlazorApp.Shared.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\BlazorApp.Client.csproj" />
<ProjectReference Include="..\Shared\BlazorApp.Shared.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
</Project>
.NET 6.0 では既定で Null 許容参照型と ImplicitUsings が有効になります。
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
データベース作成(コードファースト)
データベース用のモデルクラスを作成していきます。あとでクライアント側でも同じクラスを使うので、BlazorApp.Shared
プロジェクト直下に Models
フォルダーを作成して、その配下に Book.cs
を作成します。
namespace BlazorApp.Shared.Models
{
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public Book(int bookId, string title, string author)
{
BookId = bookId;
Title = title;
Author = author;
}
}
}
パッケージマネージャーコンソールから Entity Framework Core のパッケージをインストールします。
Install-Package -ProjectName BlazorApp.Server -Id Microsoft.EntityFrameworkCore.SqlServer
Install-Package -ProjectName BlazorApp.Server -Id Microsoft.EntityFrameworkCore.Tools
次にエンティティクラスとテーブルをマッピングするためのデータベースコンテキストクラスを作成していきます。BlazorApp.Server
プロジェクト直下に Data
フォルダーを作成し、AppDbContext
クラスを作成します。
using BlazorApp.Shared.Models;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Book> Books => Set<Book>();
}
}
BlazorApp.Server
プロジェクトの Program.cs
にコンテクストクラスをサービスとして登録します。
using BlazorApp.Server.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); //追加
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
appsettings.json
に接続文字列を追加します。下記では LocalDb に BlazorAppDb という名前のデータベースを指定しています。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BlazorAppDb;Trusted_Connection=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
パッケージマネージャーコンソールで以下のコマンドを実行します。(Migrations フォルダーと Migration クラスが作成される)
Add-Migration -Name Initial
続けて以下のコマンドを実行。(データベースとテーブルが作成される)
Update-Database
SQL Server Managment Studio か何かで以下のクエリを実行してレコードを追加しておきます。
INSERT INTO Books (Title, Author) VALUES (N'たったひとつの冴えたやりかた',N'ジェイムズ・ティプトリー・ジュニア');
INSERT INTO Books (Title, Author) VALUES (N'アンドロイドは電気羊の夢を見るか?',N'フィリップ・K・ディック');
INSERT INTO Books (Title, Author) VALUES (N'夏への扉',N'ロバート・A. ハインライン');
INSERT INTO Books (Title, Author) VALUES (N'幼年期の終り',N'アーサー C クラーク');
INSERT INTO Books (Title, Author) VALUES (N'われはロボット',N'アイザック・アシモフ');
Read(全件)
バックエンド
全件取得用の Web API を準備します。BlazorApp.Server
プロジェクトの Controllers
フォルダーに BooksController
コントローラーを追加します。
using BlazorApp.Server.Data;
using BlazorApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly AppDbContext context;
public BooksController(AppDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Book>>> ListAsync()
{
var books = await context.Books.ToListAsync();
return Ok(books);
}
}
}
デバッグ実行して /api/books
にアクセスして、以下の情報が取得できることを確認しておきます。
[
{
"bookId": 1,
"title": "たったひとつの冴えたやりかた",
"author": "ジェイムズ・ティプトリー・ジュニア"
},
{
"bookId": 2,
"title": "アンドロイドは電気羊の夢を見るか?",
"author": "フィリップ・K・ディック"
},
{
"bookId": 3,
"title": "夏への扉",
"author": "ロバート・A. ハインライン"
},
{
"bookId": 4,
"title": "幼年期の終り",
"author": "アーサー C クラーク"
},
{
"bookId": 5,
"title": "われはロボット",
"author": "アイザック・アシモフ"
}
]
Ant Design Blazor インストール
今回は UI コンポーネントライブラリを使ってフロントエンドを開発していきます。ライブラリは Awesome Blazor によると一番人気がありそうな Ant Design Blazor を使います。
パッケージマネージャーから以下のコマンドを実行して AntDesign
をインストールします。
Install-Package -ProjectName BlazorApp.Client -Id AntDesign
BlazorApp.Client
プロジェクトの Program.cs
にサービスを登録します。
using BlazorApp.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddAntDesign(); //追加
await builder.Build().RunAsync();
BlazorApp.Client
プロジェクト wwwroot/index.html
に CSS と JavaScript を追加します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>BlazorApp</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="BlazorApp.Client.styles.css" rel="stylesheet" />
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" /><!--追加-->
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/AntDesign/js/ant-design-blazor.js"></script><!--追加-->
</body>
</html>
BlazorApp.Client
プロジェクト直下の _imports.razor
に AntDesign
を追加します。
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorApp.Client
@using BlazorApp.Client.Shared
@using AntDesign
BlazorApp.Client
プロジェクト直下の App.razor
に <AntContainer />
を追加します。
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<AntContainer /><!--追加-->
Ant Design Blazor を使う準備は以上です。
フロントエンド
BlazorApp.Client
プロジェクトの Pages
フォルダー配下に BookList
Razor コンポーネントを追加します。
BookList
コンポーネントを以下のように編集します。
@page "/booklist"
@using BlazorApp.Shared.Models
@inject HttpClient Http
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<Table TItem="Book" DataSource="@books">
<Column @bind-Field="@context.BookId" Title="ID" />
<Column @bind-Field="@context.Title" />
<Column @bind-Field="@context.Author" />
</Table>
}
@code {
private List<Book>? books;
protected override async Task OnInitializedAsync()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
}
コンポーネントを開く際に OnInitializedAsycn()
メソッドが呼ばれるため、そこで先ほど作った API を呼んで、テーブルに書き出すという流れです。
このままだと動線がないので、Shared/NavMenu.razor
のリンクに BookList
へのリンクを追加しておきます。
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">BlazorApp</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</div>
<!--追加-->
<div class="nav-item px-3">
<NavLink class="nav-link" href="booklist">
<span class="oi oi-book" aria-hidden="true"></span> Book list
</NavLink>
</div>
<!--追加ここまで-->
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
ここまで出来たら実行して以下のように表示されれば OK です。
Read(1 件)
バックエンド
1 件取得用の Web API を準備します。BooksController.cs
に GetAsync
メソッドを追加します。
using BlazorApp.Server.Data;
using BlazorApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly AppDbContext context;
public BooksController(AppDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Book>>> ListAsync()
{
var books = await context.Books.ToListAsync();
return Ok(books);
}
//追加
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
return Ok(book);
}
}
}
フロントエンド
BookDetail
コンポーネントを追加します。(Detail と言っても今回は一覧と内容同じだけど)
@using BlazorApp.Shared.Models
@if (Book == null)
{
<p><em>Loading...</em></p>
}
else
{
<Descriptions>
<DescriptionsItem Title="ID" Span="3">@Book.BookId</DescriptionsItem>
<DescriptionsItem Title="Title" Span="3">@Book.Title</DescriptionsItem>
<DescriptionsItem Title="Author" Span="3">@Book.Author</DescriptionsItem>
</Descriptions>
}
@code {
[Parameter]
public Book? Book { get; set; }
}
ID をクリックすると BookDetail
コンポーネントをモーダルに表示するように BookList
コンポーネントを修正します。
@page "/booklist"
@using BlazorApp.Shared.Models
@inject HttpClient Http
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<Table TItem="Book" DataSource="@books">
<Column @bind-Field="@context.BookId" Title="ID">
<Button Type="link" OnClick="() => OpenDetailModal(context.BookId)">@context.BookId</Button>
</Column>
<Column @bind-Field="@context.Title" />
<Column @bind-Field="@context.Author" />
</Table>
<Modal Title="Detail" Visible="@showDetailModal" OnCancel="@CloseModal" Footer="@null">
<BookDetail Book="@book"></BookDetail>
</Modal>
}
@code {
private List<Book>? books;
private Book? book;
private bool showDetailModal = false;
protected override async Task OnInitializedAsync()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
public async Task OpenDetailModal(int id)
{
book = await Http.GetFromJsonAsync<Book>($"api/books/{id}");
showDetailModal = true;
}
public void CloseModal()
{
showDetailModal = false;
}
}
こんな感じになります。
Create
バックエンド
BooksController
に Create
メソッドを追加します。
using BlazorApp.Server.Data;
using BlazorApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly AppDbContext context;
public BooksController(AppDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Book>>> ListAsync()
{
var books = await context.Books.ToListAsync();
return Ok(books);
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
return Ok(book);
}
//追加
[HttpPost]
public async Task<ActionResult<Book>> CreateAsync(Book book)
{
if (book.BookId != 0)
{
return BadRequest();
}
context.Books.Add(book);
await context.SaveChangesAsync();
return CreatedAtAction("Get", new { id = book.BookId }, book);
}
}
}
フロントエンド
BookCreate
コンポーネントを追加します。
@using BlazorApp.Shared.Models
@using System.Text.Json
@inject HttpClient Http
@inject NotificationService Notice
@if (Book == null)
{
<p><em>Loading...</em></p>
}
else
{
<Form Model="@Book" OnFinish="Add" LabelColSpan="4" WrapperColSpan="20">
<FormItem Label="Title">
<Input @bind-Value="@context.Title" />
</FormItem>
<FormItem Label="Author">
<Input @bind-Value="@context.Author" />
</FormItem>
<FormItem WrapperColSpan="24" Style="text-align: center">
<Button Type="primary" HtmlType="submit">Add</Button>
</FormItem>
</Form>
}
@code {
[Parameter]
public Book? Book { get; set; }
[Parameter]
public EventCallback Callback { get; set; }
private async Task Add()
{
var response = await Http.PostAsJsonAsync("api/books", Book);
var createdBookJson = await response.Content.ReadAsStringAsync();
await Callback.InvokeAsync();
await Notice.Open(new NotificationConfig()
{
Message = "New Book is Created!",
Description = createdBookJson
});
}
}
ボタンをクリックすると BookCreate
コンポーネントをモーダルに表示するように BookList
コンポーネントを修正します。
@page "/booklist"
@using BlazorApp.Shared.Models
@inject HttpClient Http
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<Button Type="primary" OnClick="OpenCreateModal" Block>New Book</Button>
<Divider></Divider>
<Table TItem="Book" DataSource="@books">
<Column @bind-Field="@context.BookId" Title="ID">
<Button Type="link" OnClick="() => OpenDetailModal(context.BookId)">@context.BookId</Button>
</Column>
<Column @bind-Field="@context.Title" />
<Column @bind-Field="@context.Author" />
</Table>
<Modal Title="Detail" Visible="@showDetailModal" OnCancel="@CloseModal" Footer="@null">
<BookDetail Book="@book"></BookDetail>
</Modal>
<Modal Title="Create" Visible="@showCreateModal" OnCancel="@CloseModal" Footer="@null">
<BookCreate Book="@book" Callback="@OnCallback"></BookCreate>
</Modal>
}
@code {
private List<Book>? books;
private Book? book;
private bool showDetailModal = false;
private bool showCreateModal = false;
protected override async Task OnInitializedAsync()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
public async Task OpenDetailModal(int id)
{
book = await Http.GetFromJsonAsync<Book>($"api/books/{id}");
showDetailModal = true;
}
public void CloseModal()
{
showDetailModal = false;
showCreateModal = false;
}
private void OpenCreateModal()
{
book = new(0, "", "");
showCreateModal = true;
}
private async Task OnCallback()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
showCreateModal = false;
}
}
こんな感じになります。
Delete
バックエンド
BooksController
に Delete
メソッドを追加します。
using BlazorApp.Server.Data;
using BlazorApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly AppDbContext context;
public BooksController(AppDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Book>>> ListAsync()
{
var books = await context.Books.ToListAsync();
return Ok(books);
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
return Ok(book);
}
[HttpPost]
public async Task<ActionResult<Book>> CreateAsync(Book book)
{
if (book.BookId != 0)
{
return BadRequest();
}
context.Books.Add(book);
await context.SaveChangesAsync();
return CreatedAtAction("Get", new { id = book.BookId }, book);
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
context.Books.Remove(book);
await context.SaveChangesAsync();
return NoContent();
}
}
}
フロントエンド
ボタンをクリックするとアイテムを削除するように BookList コンポーネントを修正します。
@page "/booklist"
@using BlazorApp.Shared.Models
@inject HttpClient Http
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<Button Type="primary" OnClick="OpenCreateModal" Block>New Book</Button>
<Divider></Divider>
<Table TItem="Book" DataSource="@books">
<Column @bind-Field="@context.BookId" Title="ID">
<Button Type="link" OnClick="() => OpenDetailModal(context.BookId)">@context.BookId</Button>
</Column>
<Column @bind-Field="@context.Title" />
<Column @bind-Field="@context.Author" />
<ActionColumn>
<Popconfirm Title="本当に削除しますか?" OkText="はい" CancelText="いいえ" OnConfirm="() => Delete(context.BookId)">
<Button Danger>Delete</Button>
</Popconfirm>
</ActionColumn>
</Table>
<Modal Title="Detail" Visible="@showDetailModal" OnCancel="@CloseModal" Footer="@null">
<BookDetail Book="@book"></BookDetail>
</Modal>
<Modal Title="Create" Visible="@showCreateModal" OnCancel="@CloseModal" Footer="@null">
<BookCreate Book="@book" Callback="@OnCallback"></BookCreate>
</Modal>
}
@code {
private List<Book>? books;
private Book? book;
private bool showDetailModal = false;
private bool showCreateModal = false;
protected override async Task OnInitializedAsync()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
public async Task OpenDetailModal(int id)
{
book = await Http.GetFromJsonAsync<Book>($"api/books/{id}");
showDetailModal = true;
}
public void CloseModal()
{
showDetailModal = false;
showCreateModal = false;
}
private void OpenCreateModal()
{
book = new(0, "", "");
showCreateModal = true;
}
private async Task OnCallback()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
showCreateModal = false;
}
public async Task Delete(int bookId)
{
await Http.DeleteAsync($"api/books/{bookId}");
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
}
こんな感じになります。
Update
バックエンド
BooksController
に Update
メソッドを追加します。
using BlazorApp.Server.Data;
using BlazorApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazorApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly AppDbContext context;
public BooksController(AppDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Book>>> ListAsync()
{
var books = await context.Books.ToListAsync();
return Ok(books);
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
return Ok(book);
}
[HttpPost]
public async Task<ActionResult<Book>> CreateAsync(Book book)
{
if (book.BookId != 0)
{
return BadRequest();
}
context.Books.Add(book);
await context.SaveChangesAsync();
return CreatedAtAction("Get", new { id = book.BookId }, book);
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteAsync(int id)
{
var book = await context.Books.SingleOrDefaultAsync(b => b.BookId.Equals(id));
if (book == null)
{
return NotFound();
}
context.Books.Remove(book);
await context.SaveChangesAsync();
return NoContent();
}
[HttpPut]
public async Task<ActionResult> Update(Book book)
{
if (await context.Books.AsNoTracking().SingleOrDefaultAsync(b => b.BookId == book.BookId) == null)
{
return NotFound();
}
context.Entry(book).State = EntityState.Modified;
await context.SaveChangesAsync();
return NoContent();
}
}
}
フロントエンド
BookEdit
コンポーネントを追加します。
@using BlazorApp.Shared.Models
@using System.Text.Json
@inject HttpClient Http
@inject NotificationService Notice
@if (Book == null)
{
<p><em>Loading...</em></p>
}
else
{
<Form Model="@Book" OnFinish="Update" LabelColSpan="4" WrapperColSpan="20">
<FormItem Label="ID">
@context.BookId
</FormItem>
<FormItem Label="Title">
<Input @bind-Value="@context.Title" />
</FormItem>
<FormItem Label="Author">
<Input @bind-Value="@context.Author" />
</FormItem>
<FormItem WrapperColSpan="24" Style="text-align: center">
<Button Type="primary" HtmlType="submit">Update</Button>
</FormItem>
</Form>
}
@code {
[Parameter]
public Book? Book { get; set; }
[Parameter]
public EventCallback Callback { get; set; }
private async Task Update()
{
await Http.PutAsJsonAsync("api/books", Book);
await Callback.InvokeAsync();
await Notice.Open(new NotificationConfig()
{
Message = "更新しました!"
});
}
}
ボタンをクリックすると BookEdit
コンポーネントをモーダルに表示するように BookList
コンポーネントを修正します。
@page "/booklist"
@using BlazorApp.Shared.Models
@inject HttpClient Http
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<Button Type="primary" OnClick="OpenCreateModal" Block>New Book</Button>
<Divider></Divider>
<Table TItem="Book" DataSource="@books">
<Column @bind-Field="@context.BookId" Title="ID">
<Button Type="link" OnClick="() => OpenDetailModal(context.BookId)">@context.BookId</Button>
</Column>
<Column @bind-Field="@context.Title" />
<Column @bind-Field="@context.Author" />
<ActionColumn>
<Button OnClick="() => OpenEditModal(context.BookId)">Edit</Button>
</ActionColumn>
<ActionColumn>
<Popconfirm Title="本当に削除しますか?" OkText="はい" CancelText="いいえ" OnConfirm="() => Delete(context.BookId)">
<Button Danger>Delete</Button>
</Popconfirm>
</ActionColumn>
</Table>
<Modal Title="Detail" Visible="@showDetailModal" OnCancel="@CloseModal" Footer="@null">
<BookDetail Book="@book"></BookDetail>
</Modal>
<Modal Title="Create" Visible="@showCreateModal" OnCancel="@CloseModal" Footer="@null">
<BookCreate Book="@book" Callback="@OnCallback"></BookCreate>
</Modal>
<Modal Title="Edit" Visible="@showEditModal" OnCancel="@CloseModal" Footer="@null">
<BookEdit Book="@book" Callback="@OnCallback"></BookEdit>
</Modal>
}
@code {
private List<Book>? books;
private Book? book;
private bool showDetailModal = false;
private bool showCreateModal = false;
private bool showEditModal = false;
protected override async Task OnInitializedAsync()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
public async Task OpenDetailModal(int id)
{
book = await Http.GetFromJsonAsync<Book>($"api/books/{id}");
showDetailModal = true;
}
public void CloseModal()
{
showDetailModal = false;
showCreateModal = false;
showEditModal = false;
book = null;
}
private void OpenCreateModal()
{
book = new(0, "", "");
showCreateModal = true;
}
private async Task OnCallback()
{
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
this.CloseModal();
}
public async Task Delete(int bookId)
{
await Http.DeleteAsync($"api/books/{bookId}");
books = await Http.GetFromJsonAsync<List<Book>>("api/books");
}
private async Task OpenEditModal(int id)
{
book = await Http.GetFromJsonAsync<Book>($"api/books/{id}");
showEditModal = true;
}
}
こんな感じになります。
ソースコード