SlideShare ist ein Scribd-Unternehmen logo
1 von 238
Downloaden Sie, um offline zu lesen
C#, .NET 6, Blazor WebAssembly,
ASP.NET Web API, Azure による
アプリ開発 – その4
鈴⽊ 章太郎
Elastic テクニカルプロダクトマーケティングマネージャー/エバンジェリスト
デジタル庁 省庁業務グループ ソリューションアーキテクト
Elastic
Technical Product Marketing
Manager/Evangelist
デジタル庁
省庁業務グループ
ソリューションアーキテクト
元 Microsoft Technical Evangelist
Twitter : @shosuz
Shotaro Suzuki
l 前回までの復習
l Blazor 概要
l 今回作成する Web アプリケーションの概要
l Blazor WebAssembly プロジェクト作成
l Web API コントローラー追加、モデル追加
l Entity Framework による Code First データベース作成
l 商品サービス、商品リスト、カテゴリーサービス等必要なサービス、
CRUD 処理等の実装
l 検索サービスの追加と検索コンポーネントの実装
l UI/UX の変更、カートサービス
l 認証・ユーザー登録機能、その他の実装 (p.151-p.219)
アジェンダ
今回の範囲
l 2⽉、3⽉、4⽉の復習
l 認証・ユーザー登録機能の実装、その他 (p.151-p.219)
セッションでご紹介した EC アプリ .NET 5版ですが、参考にさせて戴きました。
https://github.com/patrickgod/PreviewYT
Blazor 概要
Modern Web UI with .NET & Blazor
Server WebAssembly Hybrid
HTML、CSS、.NET、C#... JavaScript の代わりに Open Web 標準でアプリ開発
どこにでもホストできる
MVC
Razor
Pages
Blazor
HTTP
APIs
SignalR
Part of the ASP.NET Core family
Web UI Services
Worker gRPC
SPA
Blazor – .NET 5 まで
Blazor Server Blazor WebAssembly
DOM
Blazor
WebAssembly
.NET
Razor Components
Blazor
.NET
Razor Components
DOM
SignalR
ü DB アクセス含むサーバー機能へのフルアクセス
ü ⾼速なスタートアップ
ü コードがサーバーから離れない
ü 古いブラウザとシンクライアントをサポート
ü 永続的な接続が必要
ü UI の遅延が⾼い
ü完全にクライアント側で実⾏
ü必要なサーバー コンポーネントなし
ü静的サイトとしてホスト
üオフラインで実⾏可能
ü⼤きなダウンロードサイズ
üランタイムパフォーマンスの低下
Blazor Server (.NET 5) Blazor WebAssembly (.NET 5)
Blazor – .NET 6 による強化
Blazor Server Blazor WebAssembly
DOM
Blazor
WebAssembly
.NET
Razor Components
Blazor
.NET
Razor Components
DOM
SignalR
Blazor WebAssembly の事前 (AOT) コンパイル対応
Blazor WebAssembly アプリのダウンロードサイズの縮⼩
Error Boundaries
Razor コンポーネント型の推論とジェネリック型の制約
動的コンポーネント
プリレンダリング中の Blazor コンポーネント状態の永続性
Hot Reload, Native File Reference, 他多数
.NET 6
Blazor Server と Blazor WebAssembly の
開発モデルの違い
Blazor Server Blazor WebAssembly
DOM
Blazor
WebAssembly
.NET
Razor Components
Blazor
.NET
Razor Components
DOM
SignalR
Blazor Server
• 開発モデルは C/S 型に近い
• DOM(ブラウザ UI)と Blazor ランタイム(仮想 DOM)
がやりとりし UI 描画(差分更新)
• 画⾯の⼊出⼒部分のみをリモートデスクトップのようにブラウザ
側に持ってきているとみなせる
• SignalR(Web ソケット通信)
• DB に直接アクセス可能
• Web アプリケーションを Client - Server 型に近いモデルで
開発可能
• Web サーバとの常時接続が必要
• サーバ側でリソース効率の⾼いアプリの作り⽅が必要
• Hot Reload
Blazor WebAssembly
• サンドボックス制限
• DB アクセス不可 → Native File Reference による
ローカル DBアクセス
• Web API を介して DB アクセス
• 静的な Web サーバにホスト
• アプリ全体がダウンロード(⼤きくなりがち)
• DOM(ブラウザ UI)と Blazor ランタイム(仮想
DOM)がやりとりしUI 描画(差分更新)、ランタイム
が Blazor アプリ(UI ロジック)とやりとりする
• Hot Reload (デバッグなしで実⾏)
Web Assembly(WASM) とは
• Web ブラウザ上でバイナリコードを直接実⾏できる
• 2019 年 12 ⽉ W3C 勧告、正式なウェブ標準に認定
• 様々な⾔語のバイナリコードを主要なブラウザのサンドボックス内で動作可能
• Web Assembly バイナリコードへのコンパイラなどのツールセットが必要
Edge
Chrome
Safari
Firefox
Web Assembly
バイナリコード
(W3C 標準技術)
C++ WASM
コンパイラ
Rust WASM
コンパイラ
C WASM
コンパイラ
SQLite ソースコード(C)
Rust ソースコード
C++ ソースコード
.NET 6 における
Blazor WebAssembly 新機能
• 事前 (AOT) 実⾏コンパイル
• カスタム要素
• ⼩規模なアプリサイズ
• Native File Reference
• Hot Reload
• Component, .NET, HTML, CSS…
…その他数⼗個の更新あり
Blazor WebAssembly ⼩規模なアプリサイズ
.NET 5
• Publish size: 1.7 MB
.NET 6
• Publish size: 1.0 MB
• ~40% size reduction
Blazor WebAssembly のホスティング
ASP.NET
Blazor
WebAssem
bly
APIs
Globally
distributed
hosting
Blazor
WebAssem
bly
Serverless
functions
APIs
App Services Azure Static Web Apps
ASP.NET
Globally
distributed
hosting Microservices
Blazor
WebAssembly
APIs
Blazor
WebAssembly
APIs
Get started with Blazor
• Go to https://blazor.net
• Install the .NET SDK
• .NET Conf 2021 https://www.dotnetconf.net/
• .NET Conf 2021 – videos/slides/demos
https://github.com/dotnet-presentations/dotNETConf/tree/master/2021/MainEvent/Technical
Visual Studio Visual Studio for Mac Visual Studio Code
+ C# extension
今回作成する Web アプリケーションの概要
ASP.NET Core Blazor プロジェクトの構造
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/project-structure?view=aspnetcore-6.0
Blazor WebAssembly アプリの初期ファイルとディレクトリ構造
[Client]
• Connected Service
• Dependencies
• Pages
• Properties
• Shared
• wwwrooot
• _imports.razor
• App.razor
• Program.cs
[Server]
• Connected Service
• Dependencies
• Controllers
• Pages
• Properties
• appsettings.json
• Program.cs
[Shared]
• Connected Service
• Dependencies
• WeatherForecast.cs
ASP.NET Core Blazor のホスティング モデル
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/hosting-models?view=aspnetcore-6.0
• Blazor WebAssembly hosting model を使⽤すると、次のようになります。
• Blazor アプリ、その依存関係、.NET ランタイムが並⾏してブラウザーにダウンロードされます。
• アプリがブラウザー UI スレッド上で直接実⾏されます。
• 次の展開戦略がサポートされています。
• ASP.NET Core でのホストされた展開
• Blazor アプリは、ASP.NET Core アプリによって提供されます。
• "ホストされたデプロイ" により、 WebAssembly アプリが、Web サーバー上で実⾏されている ASP.NET Core アプリからブラウザーに提供されます。
• クライアント Blazor WebAssembly アプリは、サーバー アプリの他の静的な Web アセットと共に、サーバーアプリの /bin/Release/{TARGET
FRAMEWORK}/publish/wwwroot フォルダーに発⾏されます。
• 2 つのアプリが⼀緒に展開されます。 ASP.NET Core アプリをホストできる Web サーバーが必要です。 ホストされている展開の場合、Visual
Studio には WebAssembly アプリ プロジェクト テンプレートが含まれており (dotnet new コマンドを使⽤する場合は blazorwasm テンプレー
ト)、 Hosted オプションが選択されています (dotnet new コマンドを使⽤する場合は -ho|--hosted)。
• スタンドアロン展開
• Blazor アプリは、Blazor アプリの提供に .NET が使⽤されていない静的ホスティング Web サーバーまたはサービス上に配置されます。
• "スタンドアロン デプロイ" により、 WebAssembly アプリが、クライアントによって直接要求される静的ファイルのセットとして提供されます。 任意の静
的ファイル サーバーで Blazor アプリを提供できます。
• スタンドアロンのデプロイアセットは、/bin/Release/{TARGET FRAMEWORK}/publish/wwwroot フォルダーに発⾏されます。
• Azure App Service
• Blazor WebAssembly アプリは、Blazor 上でアプリをホストするために使⽤される Windows 上の Azure App Service にデプロイできます。
• スタンドアロンの Blazor WebAssembly アプリを Azure App Service for Linux にデプロイすることは、現在サポートされていません。 現時点で
は、アプリをホストする Linux サーバー イメージは使⽤できません。 このシナリオを可能にするための取り組みが進⾏中です。
• Azure Static Web Apps
• 詳細については、「Tutorial: Building a static web app with Blazor in Azure Static Web Apps」を参照してください。
• IIS
EC デモアプリの画⾯遷移例
トップ
検索
Movies Books Video Games
選択
カート
決済・ログイン
ユーザー登録
EC Demo アプリの構成 1
Azure
SQL Database
Elastic Cloud
東⽇本リージョン
マスターノード x 1
データノード x 2
ML ノード x 1
https://f79...c67.japaneast
.azure.elastic-
cloud.com:9243/
全⽂検索クエリ
検索・更新 UI
Azure サブスクリプション
Azure
App Service
Elastic APM
Endpoint に送信
Blazor
Server
APM .NET Agent
Blazor
WebAssembly
CRUD
Visual
Studio
2022 for
Mac Azure Data Studio
EC Demo アプリの構成 2
Azure
SQL Database
Elastic Cloud
東⽇本リージョン
マスターノード x 1
データノード x 2
ML ノード x 1
https://f79...c67.japaneast
.azure.elastic-
cloud.com:9243/
CRUD
Azure サブスクリプション
Visual
Studio
2022 for
Mac
Azure
App Service
Elastic APM
Endpoint に送信
Azure Data Studio
ASP.NET 6 Web API
Azure
Static Web Apps
Blazor
WebAssembly
検索・更新 UI
APM .NET Agent
Blazor
WebAssembly
全⽂検索クエリ
ASP.NET Core Blazor
のホスティング モデル
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/hosting-models?view=aspnetcore-6.0#blazor-webassembly
ホスティング モデルの選択
Blazor サーバー Blazor WebAssembly
完全な .NET Core API の互換性 ✔ ❌
サーバー ソースへの直接アクセス ✔ ❌
⼩さいペイロード サイズと
⾼速な初期読み込み時間
✔ ❌
サーバー上でのアプリ コードの
セキュリティ保護と⾮公開
✔ ❌†
ダウンロードしたアプリを
オフラインで実⾏
❌ ✔
静的サイトのホスティング ❌ ✔
クライアントへの処理のオフロード ❌ ✔
Blazor WebAssembly プロジェクト作成
Blazor WebAssembly プロジェクト⽣成
チェックを⼊れる︕
Product Model の追加
Product Model の追加
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorECommerceApp.Shared
{
public class Product
{
public int Id { get; set; }
public string Title { get; set; };
public string Description { get; set; };
public string ImageUrl { get; set; };
public decimal Price { get; set; }
}
}
---
@using BlazorECommerceApp.Shared
---
ProductList.Razor の追加
ProductList.Razor の追加 1
<h3>ProductList</h3>
---
@code {
public static List<Product> Products = new List<Product>
{
new Product {
Id = "1",
Title = "The Hitchhiker's Guide to the Galaxy",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg",
Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが⽣み出したコメディ
SFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年のテレビシリーズ、1984年のテキストベー
スのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。",
Price. = 9.99m
}
new Product {
Id = "2",
Title = "Ready Player One",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg",
Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年のディストピアを舞台
に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作者の財産を相続することになるというス
トーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ(ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8
⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、章のひとつで少し触れているウィル・ウィートンである[3][4]。 20 2012年には
アメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞を受賞し[5] 、2011年にはプロメテウス賞を 受賞した[6]。”,
Price. = 7.99m
}
new Product {
Id = "3",
Title = "Nineteen Eighty-Four”,
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg",
Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語である。1949年6⽉8
⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者であるオーウェルは、スターリン主義のロシ
アとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。 より広く、この⼩説では政治における真実と事実の役割と、それらが操られる⽅法を検証し
ている。" ,
Price = 6.99m }
}
ProductList.Razor の追加 2
<h3>ProductList</h3>
<ul class="list-unstyled">
@foreach (var product in ProductService.Products)
{
<li class="media my-3">
<div class="media-img-wrapper mr-2">
<a href="/product/@product.Id">
<img class="media-img" src="@product.ImageUrl" alt="@product.Title" />
</a>
</div>
<div class="media-body">
<a href="/product/@product.Id">
<h4 class="mb-0">@product.Title</h4>
</a>
<p>@product.Description</p>
<h5 class="price">
@GetPriceText(product)
</h5>
</div>
</li>
}
</ul>
---
Index.Razor の変更
@page "/"
<ProductList />
https://localhost:7226/#
Web API コントローラー追加、モデル追加
API コントローラーの追加
ProductController.cs の追加 1
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private static List <Product> Products = new List <Product> {
new Product {
Id = "1",
Title = "The Hitchhiker's Guide to the Galaxy",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg",
Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが
⽣み出したコメディSFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年の
テレビシリーズ、1984年のテキストベースのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。",
Price. = 9.99m
}
new Product {
Id = "2",
Title = "Ready Player One",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg",
Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年の
ディストピアを舞台に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作
者の財産を相続することになるというストーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ
(ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、
章のひとつで少し触れているウィル・ウィートンである[3][4]。2012年にはアメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞
を受賞し[5] 、2011年にはプロメテウス賞を受賞した[6]。”,
Price. = 7.99m
}
new Product {
Id = "3",
Title = "Nineteen Eighty-Four”,
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg",
Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語
である。1949年6⽉8⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者で
あるオーウェルは、スターリン主義のロシアとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。より広く、この⼩説では政治
における真実と事実の役割と、それらが操られる⽅法を検証している。" ,
Price = 6.99m }
}
---
ProductController.cs の追加 2
---
[HttpGet]
public async Task<ActionResult<<List<Product>>> GetProducts()
{
rerurn Ok(Product)
var result = await _productService. GetProductsAsync();
return Ok(result);
}
https://localhost:7226/#
ProductList.Razor の変更(クライアントからの呼び出し)
---
@inject HttpClient Http
<ul class="list-unstyled">
@foreach (var product in ProductService.Products)
{
<li class="media my-3">
<div class="media-img-wrapper mr-2">
<a href="/product/@product.Id">
<img class="media-img" src="@product.ImageUrl" alt="@product.Title" />
</a>
</div>
<div class="media-body">
<a href="/product/@product.Id">
<h4 class="mb-0">@product.Title</h4>
</a>
<p>@product.Description</p>
<h5 class="price">
@GetPriceText(product)
</h5>
</div>
</li>
}
</ul>
---
code@ {
private static List<Product> Products {get; set;} = new List<Product>();
protected override async TaskOnInitializedAsync()
{
Products = await Http.GetFromJsonAsync<List<Product>> ("api/product");
}
}
Entity Framework による Code First
データベース作成
Blazor アプリのデバッグその他の TIPS
dotnet watch run
public class xxx
prop → snippets が出て予測してくれる
swagger インストールその他
• https://localhost:(ポート番
号)/swagger/index.html
---
// AddRazorPages の後
builer.Services.AddEndpointApiExploler();
builer.Services.AddSwaggerGen();
//var ap = buildder.Build();の後
app.UserSwaggerUI();
// app.UseHttpsRedirection();の前
app.UseSwagger();
// Swagger UI で Products の shema が表⽰されない場合
// Public Async Task を書き換え
Task<Action> GetProduct()
→
Task<ActionResult<List<Product>>> GetProduct()
.NET Core Entity Framework 6.0 インストール
• Microsoft.EntityFrameworkCore
• Microsoft.EntityFrameworkCore.
Design
• Microsoft.EntityFrameworkCore.
SqlServer
• Mac の場合は、唯⼀の選択肢︕
Windows の場合は、SQL Server
Express Edition をインストールして使う
⼿もあり
appsettings.json で
”ConnectionString”
とうつと⾃動的に出てくる
この⽂字列をコピペして修正すればOK
※ 注意点
EF で Code First で Database を⾃動⽣成した場合、巨⼤なインスタンスになっ
ている(3⽇くらいで数千円レベル)。
instance のサイズだけはすぐに修正して⼩さいものBasic2TB等にする。
これなら⽉額数百円。
Azure SQL Database 接続⽂字列追加
{
"ConnectionStrings": {
"DefaultConnection":
"Server=tcp:xxx.database.windows.net,1433;Initial Catalog=BlazorECommerceApp;Persist
Security Info=False;User ID=(UserID);Password=(Password);
MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
dotnet ef migration add CreateInitial // Migrations フォルダーと Migration クラス作成
dotnet ef Update Database // Azure SQL データベースとテーブル作成
Product Model の追加
• BlazorECommerceApp.Shared フォルダに、
Product クラスを作成
• Book.cs に右のコードを記載
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorECommerceApp.Shared
{
public class Product
{
public int Id { get; set; }
[Required]
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ImageUrl { get; set; } = string.Empty;
public Category? Category { get; set; }
public int CategoryId { get; set; }
public bool Featured { get; set; } = false;
public List<ProductVariant> Variants { get; set; } =
new List<ProductVariant>();
public bool Visible { get; set; } = true;
public bool Deleted { get; set; } = false;
[NotMapped]
public bool Editing { get; set; } = false;
[NotMapped]
public bool IsNew { get; set; } = false;
}
}
DataContext 作成
• Class を追加
• DataContext.class
• Serverプロジェクト側の Program.cs
修正
• global using
Microsoft.EntityFramework.Core を⼊
れておくと楽
namespace BlazorECommerceApp.Server.Data
{
public class DataContext : DbContext
{
// DataContext を作るのに ctor とタイプするとできる
// 全体的に IntelliCode が補完
public DataContext(DbContextOptions<DataContext>
options) : base(options)
{
}
}
}
• Server プロジェクト側の Program.cs
• DataContext.cs
global using Microsoft.EntityFrameworkCore;
Entity Framework を使った最初の DB Migration
//最初に名前を決めておく
dotnet ef migrations add CreateInitial
//成功したら Migration フォルダを開いて内容を確認
//データベース作成
dotnet ef database update
データのシード(2回⽬のマイグレーション)
---
Protected override void OnModelCreating(ModelBuilder
modelBuilder)
{
modelBuilder.Entity<Product>().HasData(
---<ここに new Product 3エントリをコピペ>---
);
}
dotnet ef migrations add ProductSeeding
dotnet ef database
update
(参考)旧 ProductController.cs
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private static List <Product> Products = new List <Product> {
new Product {
Id = "1",
Title = "The Hitchhiker's Guide to the Galaxy",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg",
Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが
⽣み出したコメディSFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年の
テレビシリーズ、1984年のテキストベースのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。",
Price. = 9.99m
}
new Product {
Id = "2",
Title = "Ready Player One",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg",
Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年の
ディストピアを舞台に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作
者の財産を相続することになるというストーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ
(ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、
章のひとつで少し触れているウィル・ウィートンである[3][4]。2012年にはアメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞
を受賞し[5] 、2011年にはプロメテウス賞を受賞した[6]。”,
Price. = 7.99m
}
new Product {
Id = "3",
Title = "Nineteen Eighty-Four”,
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg",
Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語
である。1949年6⽉8⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者で
あるオーウェルは、スターリン主義のロシアとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。より広く、この⼩説では政治
における真実と事実の役割と、それらが操られる⽅法を検証している。" ,
Price = 6.99m }
}
---
ProductController.cs 内のデータを削除
• Server Program.cs を開き global
using Blazorxxx.Server.Data; を追加
• private readonly DataContext
context;
⽣成されるので、これを修正
• しかしこれを⾃動的に実施したい
//context → _context に変更
public ProductController(DataContext context)
{
_cotext = context;
}
• Server プロジェクト側の Program.cs
global using Blazorxxx.Server.Data;
ツール → オプションから
テキストエディタ → C# → CodeStyle → Naming → Manage Naming Style
Naming Style Title : _fieldName
Capitalizatin : camel Case Name
これを追加したらprivate or internal Style に追加
_fieldName、Suggestion を選択
エディタに戻って create field context を選択する
[HttpGet] GetProduct() 変更
• ProductList
• ProductController
• DataContext
• [HttpGet] GetProduct() 変更
var products = await
_cotext.Products.ToListAsync();
return Ok(products)
商品サービス、商品リスト、カテゴリーサービス
等必要なサービス、CRUD 処理等の実装
Blazor WebAssembly の追加・改修等
• ProductDetail.razor.css 追加
• ProductDetail.razor 編集
@page "/product/{id:int}"
@inject IProductService ProductService
@inject ICartService CartService
Product 単品をクライアントで取得する
Category を実装する
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorECommerceApp.Shared
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
public bool Visible { get; set; } = true;
public bool Deleted { get; set; } = false;
[NotMapped]
public bool Editing { get; set; } = false;
[NotMapped]
public bool IsNew { get; set; } = false;
}
}
Category の Seeding と Migration(3回⽬)
•
•
---
modelBuilder.Entity<Category>().HasData(
new Category
{
Id = 1,
Name = "Books",
Url = "books"
},
new Category
{
Id = 2,
Name = "Movies",
Url = "movies"
},
new Category
{
Id = 3,
Name = "Video Games",
Url = "video-games"
}
);
---
• DataContext.cs
Category サービスの Client 側 への実装 - 1
•
•
namespace Blazorxxxxxxxx.Client.Services.CategoryService
{
public class CategoryService : ICategoryService
{
private readonly HttpClient _http;
public CategoryService(HttpClient http)
{
_http = http;
}
---
• CategoryServices.cs
Category サービスの Client 側 への実装 - 2
•
CategoryService
• global using で⼀番上に追加
//builder.Services.AddScoped<IProductService,ProductSe
rvice>();の下に追加
builder.Services.AddScoped<ICategoryService,
CategoryService>();
//⼀番上に追加
global using
Blazorxxxxxxxx.Client.Services.CategoryService;
• Program.cs
Category サービスの Client 側 への実装 - 3
•
@using Blazorxxxxxxxx.Client.Services.ProductService
• _Imports.razor
iCategoryServices の実装
•
namespace
Blazorxxxxxxx.Client.Services.CategoryService
{
public interface ICategoryService
{
event Action OnChange;
List<Category> Categories { get; set; }
List<Category> AdminCategories { get; set; }
Task GetCategories();
Task GetAdminCategories();
Task AddCategory(Category category);
Task UpdateCategory(Category category);
Task DeleteCategory(int categoryId);
Category CreateNewCategory();
}
}
• iCategoryServices.cs
NavMenu への Category の表⽰
• NavMenu.razor の編集
• @inject ICategoryService
CategoryService を冒頭に追加
• @code の後半部分に追加
• NavMenuCssClass の追加
protected override async Task OnInitializedAsync()
{
await CategoryService.GetCategories();
}
• NavMenu.razor
• NavMenuCssClass
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
Home
</NavLink>
</div>
@foreach (var category in CategoryService.Categories)
{
<div class="nav-item px-3">
<NavLink class="nav-link" href="@category.Url">
@category.Name
</NavLink>
</div>
}
</nav>
</div>
Server の Category サービスから Product を取得 - 1
•
•
Task<ServiceResponse<Product>> GetProduct(int productId);
• iProductService.cs
public async Task GetProducts(string? categoryUrl = null)
{
var result = categoryUrl == null ?
await
_http.GetFromJsonAsync<ServiceResponse
<List<Product>>>("api/product/featured") :
await _http.GetFromJsonAsync<ServiceResponse
<List<Product>>>($"api/product/category/{categoryUrl}");
if (result != null && result.Data != null)
Products = result.Data;
CurrentPage = 1;
PageCount = 0;
if (Products.Count == 0)
Message = "商品がみつかりません。";
ProductsChanged.Invoke();
}
• ProductService.cs
Server の Category サービスから Product を取得 - 2
•
•
• https://locahost:(ポート番
号)/swagger/index.html
---
[HttpGet("category/{categoryUrl}")]
public async Task<ActionResult<ServiceResponse
<List<Product>>>>
GetProductsByCategory(string categoryUrl)
{
var result =
await _productService.
GetProductsByCategory(categoryUrl);
return Ok(result);
}
---
• ProductController.cs
Client の Category サービスから Product を取得 - 1
•
• Task GetProducts を実装追加
• event ProductChanged を追加
•
public async Task GetProducts(string? categoryUrl = null)
{
var result = categoryUrl == null ?
//この2⾏がポイント
await _http.GetFromJsonAsync<ServiceResponse
<List<Product>>>("api/product/featured") :
await _http.GetFromJsonAsync<ServiceResponse
<List<Product>>>($"api/product/category/{categoryUrl}");
if (result != null && result.Data != null)
Products = result.Data;
CurrentPage = 1;
PageCount = 0;
if (Products.Count == 0)
Message = "商品がみつかりません。";
//ここもポイント
ProductsChanged.Invoke();
}
• iProductService.cs
• iProductService.cs
Task GetProducts(string? categoryUrl = null);
event Action ProductsChanged;
• iProductService.cs
Client の Category サービスから Product を取得 - 2
•
@page "/"
@page "/search/{searchText}/{page:int}"
@page "/{categoryUrl}"
@inject IProductService ProductService
<PageTitle>マイショップ</PageTitle>
@if (SearchText == null && CategoryUrl == null)
{
<FeaturedProducts />
}
else
{
<ProductList />
}
@code {
[Parameter]
public string? CategoryUrl { get; set; } = null;
[Parameter]
public string? SearchText { get; set; } = null;
[Parameter]
public int Page { get; set; } = 1;
protected override async Task OnParametersSetAsync()
{
if (SearchText != null)
{
await ProductService.SearchProducts(SearchText, Page);
}
else
{
await ProductService.GetProducts(CategoryUrl);
}
}
}
• Index.razor
Client の Category サービスから Product を取得 - 3
•
•
•
•
---
@code {
protected override void OnInitialized()
{
ProductService.ProductsChanged += StateHasChanged;
}
• ProductList.razor
---
public void Dispose()
{
ProductService.ProductsChanged -= StateHasChanged;
}
Shared に ProductVariant.cs を追加
•
•
•
• ProductVariant.cs
---
namespace BlazorECommerceApp.Shared
{
public class ProductVariant
{
[JsonIgnore]
public Product? Product { get; set; }
public int ProductId { get; set; }
public ProductType? ProductType { get; set; }
public int ProductTypeId { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal OriginalPrice { get; set; }
public bool Visible { get; set; } = true;
public bool Deleted { get; set; } = false;
[NotMapped]
public bool Editing { get; set; } = false;
[NotMapped]
public bool IsNew { get; set; } = false;
}
}
Composite Primary Key の追加と Seeding の実施(4回⽬)
•
•
•
•
• ProductTypes
• ProductVariants
---
modelBuilder.Entity<ProductVariant>().HasData(
new ProductVariant
{
ProductId = 1,
ProductTypeId = 2,
Price = 9.99m,
OriginalPrice = 19.99m
},
new ProductVariant
{
ProductId = 1,
ProductTypeId = 3,
Price = 7.99m
},
new ProductVariant
{
ProductId = 1,
ProductTypeId = 4,
Price = 19.99m,
OriginalPrice = 29.99m
},
new ProductVariant
{
ProductId = 2,
ProductTypeId = 2,
Price = 7.99m,
OriginalPrice = 14.99m
},
new ProductVariant
{
ProductId = 3,
ProductTypeId = 2,
Price = 6.99m
},
new ProductVariant
{
ProductId = 4,
ProductTypeId = 5,
Price = 3.99m
},
new ProductVariant
{
ProductId = 4,
ProductTypeId = 6,
Price = 9.99m
},
new ProductVariant
{
ProductId = 4,
ProductTypeId = 7,
Price = 19.99m
},
new ProductVariant
{
ProductId = 5,
ProductTypeId = 5,
Price = 3.99m,
},
new ProductVariant
{
ProductId = 6,
ProductTypeId = 5,
Price = 2.99m
},
new ProductVariant
{
ProductId = 7,
ProductTypeId = 8,
Price = 19.99m,
OriginalPrice = 29.99m
},
new ProductVariant
{
ProductId = 7,
ProductTypeId = 9,
Price = 69.99m
},
new ProductVariant
{
ProductId = 7,
ProductTypeId = 10,
Price = 49.99m,
OriginalPrice = 59.99m
},
new ProductVariant
{
ProductId = 8,
ProductTypeId = 8,
Price = 9.99m,
OriginalPrice = 24.99m,
},
new ProductVariant
{
ProductId = 9,
ProductTypeId = 8,
Price = 14.99m
},
new ProductVariant
{
ProductId = 10,
ProductTypeId = 1,
Price = 159.99m,
OriginalPrice = 299m
},
new ProductVariant
{
ProductId = 11,
ProductTypeId = 1,
Price = 79.99m,
OriginalPrice = 399m
}
);
}
---
Product Variants と Types を Product Service に含める - 1
•
•
• タブは Network
• フィルターは Fetch/XHR で実⾏
---
public async Task<ServiceResponse<Product>> GetProductAsync(int productId)
{
var response = new ServiceResponse<Product>();
Product product = null;
if (_httpContextAccessor.HttpContext.User.IsInRole("Admin"))
{
product = await _context.Products
.Include(p => p.Variants.Where(v => !v.Deleted))
.ThenInclude(v => v.ProductType)
.FirstOrDefaultAsync(p => p.Id == productId && !p.Deleted);
}
else
{
---
public async Task<ServiceResponse<List<Product>>> GetProductsAsync()
{
var response = new ServiceResponse<List<Product>>
{
Data = await _context.Products
.Where(p => p.Visible && !p.Deleted)
.Include(p => p.Variants.Where(v => v.Visible && !v.Deleted))
.ToListAsync()
---
public async Task<ServiceResponse
<List<Product>>> GetProductsByCategory(string categoryUrl)
{
var response = new ServiceResponse<List<Product>>
{
Data = await _context.Products
.Where(p => p.Category.Url.ToLower().Equals(categoryUrl.ToLower()) &&
p.Visible && !p.Deleted)
.Include(p => p.Variants.Where(v => v.Visible && !v.Deleted))
.ToListAsync()
---
Product Variants と Types を Product Service に含める - 2
•
• Product は取れている
• movies のところの下で Productを クリック
し、variants の中に Product が列挙される
ように出⼒されていることが確認できる
• id 指定してないと ProductType が⼊ってい
ないが、1と指定してリロードすると id に対応
した ProductType がちゃんと⼊っているのが
⾒える
検索サービスの追加と検索コンポーネントの実装
Product Search 機能の追加と実装 - 1
Server Service ProductService IProductService.cs
---
//追加
Task SearchProducts(string searchText, int page);
---
Product Search 機能の追加と実装 – 2
Server → Services → ProductService → ProductService.cs
---
public async Task SearchProducts(string searchText, int page)
{
LastSearchText = searchText;
var result = await _http
.GetFromJsonAsync<ServiceResponse<ProductSearchResult>>($"api/product/search/{searchText}/{page
}");
if (result != null && result.Data != null)
{
Products = result.Data.Products;
CurrentPage = result.Data.CurrentPage;
PageCount = result.Data.Pages;
}
if (Products.Count == 0) Message = "商品がみつかりません。";
ProductsChanged?.Invoke();
}
---
Product Search 機能の追加と実装 – 3
Server → Services → ProductService → ProductService.cs
---
//ついで
public async Task<List<string>> GetProductSearchSuggestions(string searchText)
{
var result = await _http
.GetFromJsonAsync<ServiceResponse<List<string>>>
($"api/product/searchsuggestions/{searchText}");
return result.Data;
}
//上記の通り実装
Product Search 機能の追加と実装 – 4
Server → Services → ProductService → ProductService.cs
---
public async Task<ServiceResponse<ProductSearchResult>> SearchProducts(string searchText, int page)
{
var pageResults = 2f;
var pageCount = Math.Ceiling((await FindProductsBySearchText(searchText)).Count / pageResults);
var products = await _context.Products
.Where(p => p.Title.ToLower().Contains(searchText.ToLower()) ||
p.Description.ToLower().Contains(searchText.ToLower()) &&
p.Visible && !p.Deleted)
.Include(p => p.Variants)
.Skip((page - 1) * (int)pageResults)
.Take((int)pageResults)
.ToListAsync();
var response = new ServiceResponse<ProductSearchResult>
{
Data = new ProductSearchResult
{
Products = products,
CurrentPage = page,
Pages = (int)pageCount
}
};
return response;
}
//上記の通り実装
---
Product Search 機能の追加と実装 – 5
• デバッグ実⾏
• https://localhost:(port 番号)/swagger/index.html
• ⼩説、等で実⾏。Response Body に1件ずつ全項⽬が表⽰される
Search Suggestions の実装 - 1
Server → Services → ProductService → ProductService.cs
---
public async Task<ServiceResponse<List<string>>> GetProductSearchSuggestions(string searchText)
{
var products = await FindProductsBySearchText(searchText);
List<string> result = new List<string>();
foreach (var product in products)
{
if (product.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase))
{
result.Add(product.Title);
}
if (product.Description != null)
{
var punctuation = product.Description.Where(char.IsPunctuation)
.Distinct().ToArray();
var words = product.Description.Split()
.Select(s => s.Trim(punctuation));
foreach (var word in words)
{
if (word.Contains(searchText, StringComparison.OrdinalIgnoreCase)
&& !result.Contains(word))
{
result.Add(word);
}
}
}
}
return new ServiceResponse<List<string>> { Data = result };
}
---
Search Suggestions の実装 - 2
Server → Controllers → ProductController.cs
---
[HttpGet("searchsuggestions/{searchText}")]
public async Task<ActionResult<ServiceResponse
<List<Product>>>> GetProductSearchSuggestions(string searchText)
{
var result = await _productService.GetProductSearchSuggestions(searchText);
return Ok(result);
}
---
Search Suggestions の実装 – 3
Server → Services → ProductService → ProductService.cs
---
if (product.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase))
{
result.Add(product.Title);
}
if (product.Description != null)
{
var punctuation = product.Description.Where(char.IsPunctuation)
.Distinct().ToArray();
var words = product.Description.Split()
.Select(s => s.Trim(punctuation));
foreach (var word in words)
{
if (word.Contains(searchText, StringComparison.OrdinalIgnoreCase)
&& !result.Contains(word))
{
result.Add(word);
}
}
}
---
• まず、句読点を取得し、句読点の助けを借りて、説明⽂のすべての単語を取得
• その後、単純に任意の単語が検索テキストを含むかどうかをチェックし、もしそうなら、結果に追加する
Search Suggestions の実装 - 4
• デバッグ実⾏
• https://localhost:(port 番号)/swagger/index.html
• ⼩説、等で実⾏。Response Body に出てくるものは Search ボックス内でサジェストされる(ここでは5件)
Search Suggestions の実装 – Client 側 1
Client → Services → ProductService → IProductService.cs
---
namespace BlazorECommerceApp.Client.Services.ProductService
{
public interface IProductService
{
---
Task SearchProducts(string searchText, int page);
Task<List<string>> GetProductSearchSuggestions(string searchText);
---
}
}
---
//を追加
• 「商品が⾒つかりませんでした」というようなメッセージを表⽰する
• その後ユーザーにいくつかの情報を与えるために、サービスが開始される
• リストの⽂字列を送信すると、商品検索候補を取得
Search Suggestions の実装 – Client 側 2
Client → Services → ProductService → ProductService.cs
---
namespace BlazorECommerceApp.Client.Services.ProductService
{
public interface IProductService
{
---
Task SearchProducts(string searchText, int page);
Task<List<string>> GetProductSearchSuggestions(string searchText);
---
}
}
---
//を追加
• 「商品が⾒つかりませんでした」というようなメッセージを表⽰する
• その後ユーザーにいくつかの情報を与えるために、サービスが開始される
• リストの⽂字列を送信すると、商品検索候補を取得
Search Suggestions の実装 – Client 側 3-a
Client → Services → ProductService → ProductService.cs
---
namespace BlazorECommerceApp.Client.Services.ProductService
{
public class ProductService : IProductService
//IProductService.cs インターフェイスをインプリする
{
private readonly HttpClient _http;
public ProductService(HttpClient http)
{
_http = http;
}
---
Search Suggestions の実装 – Client 側 3-b
Client → Services → ProductService → ProductService.cs
---
public List<Product> Products { get; set; } = new List<Product>();
public string Message { get; set; } = "商品をロードしています...";
//メッセージを追加
public int CurrentPage { get; set; } = 1;
public int PageCount { get; set; } = 0;
public string LastSearchText { get; set; } = string.Empty;
public List<Product> AdminProducts { get; set; }
public event Action ProductsChanged;
public async Task<Product> CreateProduct(Product product)
{
var result = await _http.PostAsJsonAsync("api/product", product);
var newProduct = (await result.Content
.ReadFromJsonAsync<ServiceResponse<Product>>()).Data;
return newProduct;
}
---
Search Suggestions の実装 – Client 側 3-c
Client → Services → ProductService → ProductService.cs
---
public async Task DeleteProduct(Product product)
{
var result = await _http.DeleteAsync($"api/product/{product.Id}");
}
public async Task GetAdminProducts()
{
var result = await _http
.GetFromJsonAsync<ServiceResponse<List<Product>>>("api/product/admin");
AdminProducts = result.Data;
CurrentPage = 1;
PageCount = 0;
if (AdminProducts.Count == 0)
Message = "商品がみつかりません。";
}---
Search Suggestions の実装 – Client 側 3-e
Client → Services → ProductService → ProductService.cs
---
public async Task<ServiceResponse<Product>> GetProduct(int productId)
{
var result = await
_http.GetFromJsonAsync<ServiceResponse<Product>>($"api/product/{productId}");
return result;
}
public async Task GetProducts(string? categoryUrl = null)
{
var result = categoryUrl == null ?
await
_http.GetFromJsonAsync<ServiceResponse<List<Product>>>
("api/product/featured") :
await
_http.GetFromJsonAsync<ServiceResponse<List<Product>>>
($"api/product/category/{categoryUrl}");
if (result != null && result.Data != null)
Products = result.Data;
CurrentPage = 1;
PageCount = 0;
if (Products.Count == 0)
Message = "商品がみつかりません。";
ProductsChanged.Invoke();
}
---
Search Suggestions の実装 – Client 側 3-e
Client → Services → ProductService → ProductService.cs
---
public async Task<List<string>> GetProductSearchSuggestions(string searchText)
{
var result = await _http
.GetFromJsonAsync<ServiceResponse<List<string>>>($"api/product/searchsuggestions/{searchText}");
return result.Data;
}
public async Task SearchProducts(string searchText, int page)
{
LastSearchText = searchText;
var result = await _http
.GetFromJsonAsync<ServiceResponse<ProductSearchResult>>($"api/product/search/{searchText}/{page}");
if (result != null && result.Data != null)
{
Products = result.Data.Products;
CurrentPage = result.Data.CurrentPage;
PageCount = result.Data.Pages;
}
if (Products.Count == 0) Message = "商品がみつかりません。";
ProductsChanged?.Invoke();
}
public async Task<Product> UpdateProduct(Product product)
{
var result = await _http.PutAsJsonAsync($"api/product", product);
var content = await result.Content.ReadFromJsonAsync<ServiceResponse<Product>>();
return content.Data;
}
}
}
---
URL を介した検索の実装
Client → Pages → index.razor
---
@page "/"
@page "/search/{searchText}/{page:int}"
@page "/{categoryUrl}"
---
@code {
[Parameter]
public string? CategoryUrl { get; set; } = null;
[Parameter]
public string? SearchText { get; set; } = null;
[Parameter]
public int Page { get; set; } = 1;
protected override async Task OnParametersSetAsync()
{
if (SearchText != null)
{
await ProductService.SearchProducts(SearchText, Page);
}
else
{
await ProductService.GetProducts(CategoryUrl);
}
}
}
---
//を追加
• 単純にフォワードスラッシュ
• 開始ページまたは検索
• 検索テキストまたはカテゴリ URL
• 必要なのはこの新しいパラメータ、検索テキスト
• コードブロック全体の実装
Seach コンポーネント作成 - 1
Client → Shared → Search.razor
---
//先に@code部分作成
@Inject NavigationManager NavigationManager
@inject IProductService ProductService
---
@code {
private string searchText = string.Empty;
private List<string> suggestions = new List<string>();
protected ElementReference searchInput;
protected override async Task OnAfterRenderAsync
(bool firstRender)
{
if (firstRender)
{
await searchInput.FocusAsync();
}
}
public void SearchProducts()
{
NavigationManager.NavigateTo($"search/{searchText}/1");
}
---
• すでにあるものを注⼊する
• Product Service
• いくつかの呼び出し
• Navigation Manager
• ユーザーを特定のページに誘導したい
• NavigateTo メソッドを使⽤
Seach コンポーネント作成 - 2
Client → Shared → Search.razor
---
//先に@code部分作成
@Inject NavigationManager NavigationManager
@inject IProductService ProductService
---
public async Task HandleSearch(KeyboardEventArgs args)
{
if (args.Key == null || args.Key.Equals("Enter"))
{
SearchProducts();
}
else if (searchText.Length > 1)
{
suggestions = await
ProductService.
GetProductSearchSuggestions(searchText);
}
}
• すでにあるものを注⼊する
• Product Service
• いくつかの呼び出し
• Navigation Manager
• ユーザーを特定のページに誘導したい
• NavigateTo メソッドを使⽤
Seach コンポーネント作成 - 3
Client → Shared → Search.razor
---
//最後に HTML 部分
@Inject NavigationManager NavigationManager
@inject IProductService ProductService
---
<div class="input-group">
<input @bind-value="searchText"
@bind-value:event="oninput"
type="search"
list="products"
@onkeyup="HandleSearch"
class="form-control"
placeholder="検索..."
@ref="searchInput" />
<datalist id="products">
@foreach (var suggestion in suggestions)
{
<option>@suggestion</option>
}
</datalist>
<div class="input-group-append">
<button class="btn btn-primary" @onclick="SearchProducts">
<span class="oi oi-magnifying-glass"></span>
</button>
</div>
</div>
• すでにあるものを注⼊する
• Product Service
• いくつかの呼び出し
• Navigation Manager
• ユーザーを特定のページに誘導したい
• NavigateTo メソッドを使⽤
Seach コンポーネント作成 - 4
Client → Shared → MainLayout.razor
---
MainLayout.razor
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<Search />
//ここに Search コンポーネントを⼊れる
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
---
• すでにあるものを注⼊する
• Product Service
• いくつかの呼び出し
• Navigation Manager
• ユーザーを特定のページに誘導したい
• NavigateTo メソッドを使⽤
UI/UX の変更
レイアウトの変更 - 1
Client → Shared → MainLayout.razor
---
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(ShopLayout)">
<NotAuthorized>
<h3>Whoops! You're not allowed to see this page.</h3>
<h5>Please
<a href="login">login</a> or
<a href="register">register
</a> for a new account.</h5>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(ShopLayout)">
<p role="alert">
Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
---
• MainLayout
• → App.razor で定義
• MainLayout.razor をコピーすると css
もコピーされる
• ShopLayout.razor と
• ShopLayout.razor.css を作成
• App.razor を編集
• ShopLayout をパラメーターに設定
レイアウトの変更 - 2 Client → Shared → NavMenu.razor
---
@inject ICategoryService CategoryService
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">BlazorECommerceApp</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">
Home
</NavLink>
</div>
@foreach (var category in CategoryService.Categories)
{
<div class="nav-item px-3">
<NavLink class="nav-link" href="@category.Url">
@category.Name
</NavLink>
</div>
}
</nav>
</div>
• ShopNavMenu.razor
• ShopNavMenu.razor.css
• 共に編集し最終形にする
• これによって上のナビゲーションメニュー
ボタンができ、左のメニューが消える
• css は⾯倒だがこの機会にある程度
詳しくなると、他のプラットフォームでも
使いこなせる
• css に慣れるためにも Hot Reload
を活⽤してください︕楽しくなります
レイアウトの変更 - 3
Client → Shared → MainLayout.razor
---
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<Search />
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
---
• ShopNavMenu.razor
• ShopNavMenu.razor.css
• 共に編集し最終形にする
• これによって上のナビゲーションメニュー
ボタンができ、左のメニューが消える
• css は⾯倒だがこの機会にある程度
詳しくなると、他のプラットフォームでも
使いこなせる
• css に慣れるためにも Hot Reload
を活⽤してください︕楽しくなります
レイアウトの変更 - 4 Client → Shared → ShopNavMenu.razor
---
@inject ICategoryService CategoryService
@implements Idisposable
<div class="top-row ps-3 navbar navbar-dark navbar-toggler-wrapper">
<div class="container-fluid">
<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-nav">
<div class="nav-item px-2">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
Home
</NavLink>
</div>
@foreach (var category in CategoryService.Categories)
{
<div class="nav-item px-2">
<NavLink class="nav-link" href="@category.Url">
@category.Name
</NavLink>
</div>
}
</nav>
</div>
---
• MainLayout
• → App.razor で定義
• MainLayout.razor をコピーすると css
もコピーされる
• ShopLayout.razor と
• ShopLayout.razor.css を作成
• App.razor を編集
• ShopLayout をパラメーターに設定
レイアウトの変更 - 5 Client → Shared → ShopNavMenu.razor
---
@inject ICategoryService CategoryService
@implements IDisposable
---
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
protected override async Task OnInitializedAsync()
{
await CategoryService.GetCategories();
CategoryService.OnChange += StateHasChanged;
}
public void Dispose()
{
CategoryService.OnChange -= StateHasChanged;
}
}
---
• MainLayout
• → App.razor で定義
• MainLayout.razor をコピーすると css
もコピーされる
• ShopLayout.razor と
• ShopLayout.razor.css を作成
• App.razor を編集
• ShopLayout をパラメーターに設定
HomeButton.razor - 1
Client → Shared → HomuButton.razor
---
@inject NavigationManager NavigationManager
<button @onclick="GoToHome"
class="btn btn-outline-primary home-button">
マイショップ
</button>
@code {
private void GoToHome()
{
NavigationManager.NavigateTo("");
}
}
---
• Home Button の配置
• razor の作成
HomeButton.razor - 2
Client → Shared → HomuButton.razor.css
---
HomeButton.razor.css
.home-button {
white-space: nowrap;
margin-right: 10px;
transform: rotate(-5deg);
}
---
• Home Button の配置
• css の追加
HomeButton.razor - 3
Client → Shared → ShopLayout.razor
---
@inherits LayoutComponentBase
<div class="page">
<main>
<div class="top-row px-2">
//これを追加
<HomeButton />
<Search />
</div>
<div class="nav-menu">
<ShopNavMenu />
</div>
<article class="content px-2">
@Body
</article>
</main>
</div>
---
• Home Button の配置
• ShopLayout.razor の修正
注⽬の商品 - 1
Shared → Product.cs
---
//新しいプロパティを⼊れる
---
namespace BlazorECommerceApp.Shared
{
public class Product
{
public int Id { get; set; }
[Required]
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ImageUrl { get; set; } = string.Empty;
public Category? Category { get; set; }
public int CategoryId { get; set; }
//これを⼊れる
public bool Featured { get; set; } = false;
public List<ProductVariant> Variants { get; set; } =
new List<ProductVariant>();
---
}
}
---
• Featured Products として3つを top
ページにリコメンドして表⽰する
注⽬の商品 - 2 DataContext.cs
---
//新しいプロパティを追加する(3つのみ)
---
Seed を変更する(5回⽬の Migration)
---
new Product
{
Id = 5,
CategoryId = 2,
Title = "Back to the Future",
Description = "「バック・トゥ・ザ・フューチャー」は、ロバート・ゼメキス監督による1985年のアメ
リカのSF映画である。ゼメキスとボブ・ゲイルの脚本で、マイケル・J・フォックス、クリスト
ファー・ロイド、リア・トンプソン、クリスピン・グローバー、トーマス・F・ウィルソンらが出演し
ています。1985年を舞台に、マーティ・マクフライ(フォックス)は、友⼈の⾵変わりな科
学者、エメット博⼠(ロイド)が作ったタイムトラベル可能なデロリアンに乗って、偶然に
も1955年に戻されることになります。ブラウン(ロイド)。過去に閉じ込められたマーティは、
うっかり未来の両親の出会いを邪魔してしまい、⾃分の存在意義を脅かされてしまう。",
ImageUrl = "https://upload.wikimedia.org/wikipedia/en/d/d2/
Back_to_the_Future.jpg",
//ここを追加。3つのみ
Featured = true
},
---
• Featured Products として3つを top
ページにリコメンドして表⽰する
注⽬の商品 - 3
• Package Manager Console で
Migration 実施
cd ./BlazorECommerceApp
cd ./Server
dot net ef Migrations add FeaturedProducts
注⽬の商品 - 4
• Migrations フォルダ →
FeaturedProducts を開いて内容
を確認
• カラムが追加されることを確認
• フラグが⽴つプロダクトの Id を確認
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BlazorEcommerce.Server.Migrations
{
public partial class FeaturedProducts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Featured",
table: "Products",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.UpdateData(
table: "Products",
keyColumn: "Id",
keyValue: 1,
column: "Featured",
value: true);
migrationBuilder.UpdateData(
table: "Products",
keyColumn: "Id",
keyValue: 5,
column: "Featured",
value: true);
migrationBuilder.UpdateData(
table: "Products",
keyColumn: "Id",
keyValue: 9,
column: "Featured",
value: true);
}
---
注⽬の商品 - 5
• Package Manager Console で
DB 作成
• Azure Data Studio で確認
• dbo.Products
• Featured 列が増えている
• フラグが⽴っている
dotnet ef database update
注⽬の商品のローディング - 1
Server → Services → ProductService → iProductService.cs
---
namespace BlazorEcommerceApp.Server.Services.ProductService
{
public interface IProductService
{
---
Task<ServiceResponse<ProductSearchResult>> SearchProducts
(string searchText, int page);
Task<ServiceResponse<List<string>>> GetProductSearchSuggestions(string searchText);
//これを追加する
Task<ServiceResponse<List<Product>>> GetFeaturedProducts();
---
}
}
---
注⽬の商品のローディング - 2
Server → Services → ProductService → ProductService.cs
---
//インターフェイスを実装
public async Task<ServiceResponse<List<Product>>> GetFeaturedProducts()
{
var response = new ServiceResponse<List<Product>>
{
Data = await _context.Products
.Where(p => p.Featured)
.Include(p => p.Variants)
.ToListAsync()
};
return response;
}
---
注⽬の商品のローディング – 3
Server → Controllers → ProductController.cs
//下記を追加
---
[HttpGet("featured")]
public async Task<ActionResult<ServiceResponse<List<Product>>>> GetFeaturedProducts()
{
var result = await _productService.GetFeaturedProducts();
return Ok(result);
}
---
注⽬の商品のローディング – 4
Client → Shared → FeaturedProducts.razor.cs
---
@inject IProductService ProductService
@implements Idisposable
---
@code {
protected override void OnInitialized()
{
ProductService.ProductsChanged += StateHasChanged;
}
public void Dispose()
{
ProductService.ProductsChanged -= StateHasChanged;
}
}
---
• 新しいコンポーネントを作ってフィーチャード
プロダクトを表⽰する
• 先に @code 部分から
注⽬の商品のローディング – 5
Client → Shared → FeaturedProducts.razor.cs
---
@inject IProductService ProductService
@implements Idisposable
---
<center><h2>今⽇の⼈気商品</h2></center>
@if (ProductService.Products == null || ProductService.Products.Count == 0)
{
<span>@ProductService.Message</span>
}
else
{
<div class="container">
@foreach (var product in ProductService.Products)
{
@if (product.Featured)
{
<div class="featured-product">
<div>
<a href="product/@product.Id">
<img src="@product.ImageUrl">
</a>
</div>
<h4><a href="product/@product.Id">@product.Title</a></h4>
@if (product.Variants != null && product.Variants.Count > 0)
{
<h5 class="price">
$@product.Variants[0].Price
</h5>
}
</div>
}
}
</div>
}---
• 新しいコンポーネントを作ってフィーチャード
プロダクトを表⽰する
• @code に続いて View 部分
注⽬の商品のローディング – 6
Client → Shared → FeaturedProducts.razor.cs → FeaturedProducts.razor.css
---
.container {
display: flex;
flex-direction: row;
overflow-x: auto;
justify-content: center;
}
img {
max-width: 200px;
max-height: 200px;
border-radius: 6px;
transition: transform .2s;
margin-bottom: 10px;
}
img:hover {
transform: scale(1.1) rotate(5deg);
}
//ここを追加
.featured-product {
margin: 10px;
text-align: center;
padding: 10px;
border: 1px solid lightgray;
border-radius: 10px;
max-width: 200px;
}
@media (max-width: 1023.98px) {
.container {
justify-content: flex-start;
}
}
• FeaturedProducts.razor.css 作成
• Chrome Dev Tool のモバイルビューなど
にも切り替えながら検証する
• Hot Reload は css にこそ有効
注⽬の商品のローディング – 7
Client → Pages → Index.razor
---
@page "/"
@page "/search/{searchText}/{page:int}"
@page "/{categoryUrl}"
@inject IProductService ProductService
---
<PageTitle>マイショップ</PageTitle>
@if (SearchText == null && CategoryUrl == null)
{
<FeaturedProducts /> //ここを追加
}
else
{
<ProductList />
}
@code {
[Parameter]
public string? CategoryUrl { get; set; } = null;
[Parameter]
public string? SearchText { get; set; } = null;
[Parameter]
public int Page { get; set; } = 1;
protected override async Task OnParametersSetAsync()
{
if (SearchText != null)
{
await ProductService.SearchProducts(SearchText, Page);
}
else
{
await ProductService.GetProducts(CategoryUrl); //ここで Go to Implementation
}
}
• Index.razor を修正
注⽬の商品のローディング – 8
Client → Services → ProductServices → ProductService.cs -
GetProducts
---
---
public async Task GetProducts(string? categoryUrl = null)
{
var result = categoryUrl == null ?
await
_http.GetFromJsonAsync<ServiceResponse
<List<Product>>>
("api/product/featured") :
await
_http.GetFromJsonAsync<ServiceResponse
<List<Product>>>
($"api/product/category/{categoryUrl}");
if (result != null && result.Data != null)
Products = result.Data;
CurrentPage = 1;
PageCount = 0;
if (Products.Count == 0)
Message = "商品がみつかりません。";
ProductsChanged.Invoke();
}
---
}
• Client.Services.ProductServices
ProductService.cs の GetProducts
を修正
検索結果の
ページネーション - 1
Shared → Products → ProductSerchResult.cs
---
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorEcommerceApp.Shared
{
public class ProductSearchResult
{
public List<Product> Products { get; set; } = new
List<Product>();
public int Pages { get; set; }
public int CurrentPage { get; set; }
}
}
---
• データベースに多くの製品が登録されている
場合、分割して表⽰させたい
• 1ページに2つの商品を表⽰し、2ページ⽬、
3ページ⽬......と進む
• タイトルと説明⽂だけ表⽰させる
• 商品からデータ転送オブジェクトを作成し、
DTO は商品タイトルと説明⽂だけを返す
• ProductSerchResult.cs という DTO
オブジェクト
• この DTO で、製品のリストを取得し、ペー
ジ数を取得し、情報として現在のページを
取得
検索結果の
ページネーション - 2
Server → Services → ProductServices → IProductService.cs
---
Server Services ProductServices IProductService.cs
namespace BlazorEcommerceApp.Server.Services.ProductService
{
public interface IProductService
{
---
//ここを追加
Task<ServiceResponse<ProductSearchResult>>
SearchProducts(string searchText, int page);
---
}
}---
• ProductSerchResult.cs という DTO
オブジェクト
• この DTO で、製品のリストを取得し、ペー
ジ数を取得し、情報として現在のページを
取得
検索結果の
ページネーション - 3
Server → Services → ProductServices → ProductService.cs
---
public async Task<ServiceResponse<ProductSearchResult>> SearchProducts(string
searchText, int page)
//リターン値を ProductSearchResult にしてパラメーターに page も追加
{
//下記の両者を定義しておく
var pageResults = 2f;
var pageCount = Math.Ceiling((await
FindProductsBySearchText(searchText))
.Count / pageResults);
var products = await _context.Products
.Where(p =>
p.Title.ToLower().Contains(searchText.ToLower())
||p.Description.ToLower().Contains(searchText.ToLower()) &&
p.Visible && !p.Deleted)
.Include(p => p.Variants)
.Skip((page - 1) * (int)pageResults)
.Take((int)pageResults)
.ToListAsync();
//ここもProductSearchResultに変更
var response = new ServiceResponse<ProductSearchResult>
{
Data = new ProductSearchResult
{
Products = products,
CurrentPage = page,
Pages = (int)pageCount
}
};
return response;
}
---
• Server 上のページネーション
検索結果の
ページネーション - 4
Server → Controllers → ProductContoroller.cs
---
//page 追加
[HttpGet("search/{searchText}/{page}")]
public async
Task<ActionResult<ServiceResponse<ProductSearchResult>>>
//page 追加、デフォルト値=1
SearchProducts(string searchText, int page = 1)
{
var result = await
_productService.SearchProducts(searchText, page);
return Ok(result);
}
------
• コントローラにも変更を加える必要あり
• Product コントローラの Search メソッドに、
もうひとつパラメータを追加(Page)
• デフォルトで1に設定
• アプリケーションを起動
• Swagger ページを開く
• 検索テキストを⼊⼒してテスト
検索結果の
ページネーション - 5
Client → Services → ProductService → IProductService.cs
---
namespace BlazorEcommerceApp.Client.Services.ProductService
{
public interface IProductService
{
--
Task<ServiceResponse<Product>>
GetProduct(int productId);
Task SearchProducts(string searchText, int page);
---
---
• クライアントの変更を実装
Client → Services → ProductService → IProductService.cs
---
namespace BlazorEcommerceApp.Client.Services.ProductService
{
---
public List<Product> Products { get; set; } = new
List<Product>();
//下記3⾏を追加
public string Message { get; set; } =
"商品をロードしています...";
public int CurrentPage { get; set; } = 1;
public int PageCount { get; set; } = 0;
---
検索結果の
ページネーション - 6
Client → Services → ProductService → ProductService.cs
---
public async Task GetProducts(string? categoryUrl = null)
{
---
CurrentPage = 1;
PageCount = 0;
if (Products.Count == 0)
Message = "商品がみつかりません。";
ProductsChanged.Invoke();}
---
• Client 側 GetProducts を修正
• Client 側 SearchProducts を修正
Client → Services → ProductService → ProductService.cs
---
public async Task SearchProducts(string searchText, int page)
//page パラメータを追加
{
LastSearchText = searchText;
var result = await _http
.GetFromJsonAsync<ServiceResponse<ProductSearchResult>>
//List<Products> をProductSearchResult に変更
($"api/product/search/{searchText}/{page}");
//page 部分を追加
if (result != null && result.Data != null)
{
Products = result.Data.Products;
CurrentPage = result.Data.CurrentPage;
PageCount = result.Data.Pages;
}
if (Products.Count == 0) Message = "商品がみつかりません。";
ProductsChanged?.Invoke();
}
---
検索結果の
ページネーション - 7
Client → Shared → Search.razor
---
public void SearchProducts()
{
---
//1 をデフォルト値として追加
NavigationManager.NavigateTo($"search/{searchText}/1");
---
• ページネーションのコンポーネントへの追加
• Search.razor、 Index.razor を修正
Client → Pages → Index.razor
---
//int を追加
@page "/search/{searchText}/{page:int}"
---
// パラメータ追加
[Parameter]
public int Page { get; set; } = 1;
---
//修正
---
protected override async Task OnParametersSetAsync()
{
if (SearchText != null)
{
//page 追加
await ProductService.SearchProducts(SearchText, Page);
}
---
---
検索結果の
ページネーション - 8
Client → Shared → Product.razor
---
//下記を追加
for (var i = 1; i <= ProductService.PageCount; i++)
{
<a class="btn
@(i == ProductService.CurrentPage ?
"btn-info" : "btn-outline-info")
page-selection"
href="/search/@ProductService.LastSearchText/@i">@i</a>
}
---
• ボタンの追加
• これでページネーションは完成 Client → Shared → Product.razor.css
---
//追加
---
.page-selection {
margin-right: 15px;
margin-bottom: 30px;
}
//追加
---
.page-selection {
margin-right: 15px;
margin-bottom: 30px;
}
---
カートサービスの実装
ショッピングカート - 1
Client → Program.cs
---
//下記を追加
---
using Blazored.LocalStorage;
---
builder.Services.AddBlazoredLocalStorage();
---
• ローカルストレージを使う
• Client プロジェクトに NuGet パッケージ
追加
• Blazer Local Storage
Client → Imports.razor
---
//追加
---
@using Blazored.LocalStorage
---
---
ショッピングカート - 2
Client → Shared → CartCounter.razor
---
@inject ICartService CartService
@inject ISyncLocalStorageService LocalStorage
@implements IDisposable
<a href="cart" class="btn btn-info">
<i class="oi oi-cart"></i>
<span class="badge">@GetCartItemsCount()</span>
</a>
@code {
private int GetCartItemsCount()
{
var count = LocalStorage.GetItem<int>("cartItemsCount");
return count;
}
protected override void OnInitialized()
{
CartService.OnChange += StateHasChanged;
}
public void Dispose()
{
CartService.OnChange -= StateHasChanged;
}
}
---
• カートを追加
• デバッグ実⾏して画⾯のカート部分を確認
ショッピングカート - 3
Client → CartItem.cs
---
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorEcommerceApp.Shared
{
public class CartItem
{
public int ProductId { get; set; }
public int ProductTypeId { get; set; }
}
}---
• カートを追加
• デバッグ実⾏して画⾯のカート部分を確認
クライアント側の
CartService 実装 - 1
Client → Program.cs
---
builder.Services.AddScoped<ICategoryService,
CategoryService>();
---
• Client → Service →
ICartService.cs を追加
• Client → Service →
CartService.cs を追加
Client → _Imports.razor
---
@using BlazorEcommerceApp.Client.Services.CartService
---
クライアント側の
CartService 実装 - 2
Client → Client → Service → ICartService.cs
---
namespace
BlazorEcommerceApp.Client.Services.CartService
{
public interface ICartService
{
event Action OnChange;
Task AddToCart(CartItem cartItem);
Task<List<CartProductResponse>>
GetCartProducts();
Task RemoveProductFromCart(int productId,
int productTypeId);
Task UpdateQuantity(CartProductResponse
product);
Task StoreCartItems(bool emptyLocalCart);
Task GetCartItemsCount();
}
}
---
• Client → Service →
ICartService.cs を追加
クライアント側の
CartService 実装 - 3
Client → Client → Service → CartService.cs
---
using Blazored.LocalStorage;
namespace BlazorEcommerceApp.Client.Services.CartService
{
public class CartService : ICartService
{
private readonly ILocalStorageService _localStorage;
private readonly HttpClient _http;
private readonly IAuthService _authService;
public CartService(ILocalStorageService localStorage, HttpClient http,
IAuthService authService)
{
_localStorage = localStorage;
_http = http;
_authService = authService;
}
public event Action OnChange;
public async Task AddToCart(CartItem cartItem)
{
if (await _authService.IsUserAuthenticated())
{
await _http.PostAsJsonAsync("api/cart/add", cartItem);
}
else
{
• Client → Service → CartService.cs
を追加
var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart");
if (cart == null)
{
cart = new List<CartItem>();
}
var sameItem = cart.Find(x => x.ProductId ==
cartItem.ProductId &&
x.ProductTypeId == cartItem.ProductTypeId);
if (sameItem == null)
{
cart.Add(cartItem);
}
else
{
sameItem.Quantity += cartItem.Quantity;
}
await _localStorage.SetItemAsync("cart", cart);
}
await GetCartItemsCount();
}
public async Task GetCartItemsCount()
{
if (await _authService.IsUserAuthenticated())
{
var result = await
_http.GetFromJsonAsync<ServiceResponse<int>>("api/cart/count");
var count = result.Data;
await _localStorage.SetItemAsync<int>
("cartItemsCount", count);
}
else
{
var cart = await
_localStorage.GetItemAsync<List<CartItem>>("cart");
await _localStorage.SetItemAsync<int>
("cartItemsCount", cart != null ? cart.Count : 0);
}
OnChange.Invoke();
}
---
クライアント側の
CartService 実装 - 4
• ProductDetail に Add to Cart ボタン
を追加
Client Pages ProductDetails.razor
// 先に View 側に追加
---
@inject ICartService CartService
---
---
<button class="btn btn-primary" @onclick="AddToCart">
<i class="oi oi-cart"></i>&nbsp;&nbsp;&nbsp;Add
to Cart
</button>
---
---
// 次いで@code側に追加
---
private async Task AddToCart()
{
var productVariant = GetSelectedVariant();
var cartItem = new CartItem
{
ProductId = productVariant.ProductId,
ProductTypeId = productVariant.ProductTypeId
};
await CartService.AddToCart(cartItem);
}
---
クライアント側の
CartService 実装 - 5
• デバッグ実⾏
• Chrome Developer Tools 起動
• アイテムを⼀つ選択
• アプリケーションタブに切り替え
• ローカルストレージ表⽰
• 当該アイテムを Add to Cart で追加
• 値を確認する
クライアント側の
CartService 実装 - 6
• CartCounter 数字のインクリメント
• @Code 部分を先に実装
Client → Shared → CartCounter.razor
// 先に @code 部分を実装
---
@inject ICartService CartService
@inject ISyncLocalStorageService LocalStorage
@implements IDisposable
---
@code {
private int GetCartItemsCount()
{
var count =
LocalStorage.GetItem<int>("cartItemsCount");
return count;
}
protected override void OnInitialized()
{
CartService.OnChange += StateHasChanged;
}
public void Dispose()
{
CartService.OnChange -= StateHasChanged;
}
}
---
クライアント側の
CartService 実装 - 7
• CartCounter 数字のインクリメント
• 続いて view 部分を実装
• CartService.cs →
OnChange.Invoke() を追加
Client → Shared → CartCounter.razor
// View 部分を実装
---
<a href="cart" class="btn btn-info">
<i class="oi oi-cart"></i>
<span class="badge">@GetCartItemsCount()</span>
</a>
---
Client → Service → CartService → CartService.cs
---
OnChange.Invoke();
---
CartItem のサーバー側
Products への送付- 1
• Shared の CartProductResponse.cs
Shared → CartProductResponse.cs
---
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorEcommerceApp.Shared
{
public class CartProductResponse
{
public int ProductId { get; set; }
public string Title { get; set; } = string.Empty;
public int ProductTypeId { get; set; }
public string ProductType { get; set; } =
string.Empty;
public string ImageUrl { get; set; } =
string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
}
}
---
CartItem のサーバー側
Products への送付- 2
• Server → Services → CartService
フォルダ作成
• Server → Services → ICartService
• Server → Services → CartService
を追加
Server → Program.cs
//下記を追加
---
builder.Services.AddScoped<ICartService, CartService>();
---
global using
BlazorEcommerceApp.Server.Services.CartService;
---
CartItem のサーバー側
Products への送付- 3
• Server → Services → CartServiceProgram.cs 実装
Server → Services → CartServiceProgram.cs
---
using System.Security.Claims;
namespace BlazorEcommerceApp.Server.Services.CartService
{
public class CartService : ICartService
{
private readonly DataContext _context;
public CartService(DataContext context, IAuthService authService)
{
_context = context;
}
public async Task<ServiceResponse<List<CartProductResponse>>>
GetCartProducts(List<CartItem> cartItems)
{
var result = new ServiceResponse<List<CartProductResponse>>
{
Data = new List<CartProductResponse>()
};
foreach (var item in cartItems)
{
var product = await _context.Products
.Where(p => p.Id == item.ProductId)
.FirstOrDefaultAsync();
if (product == null)
{
continue;
}
var productVariant = await _context.ProductVariants
.Where(v => v.ProductId == item.ProductId
&& v.ProductTypeId == item.ProductTypeId)
.Include(v => v.ProductType)
.FirstOrDefaultAsync();
if (productVariant == null)
{
continue;
}
var cartProduct = new CartProductResponse
{
ProductId = product.Id,
Title = product.Title,
ImageUrl = product.ImageUrl,
Price = productVariant.Price,
ProductType = productVariant.ProductType.Name,
ProductTypeId = productVariant.ProductTypeId,
Quantity = item.Quantity
};
result.Data.Add(cartProduct);
}
return result;
}
---
CartItem のサーバー側
Products への送付- 4
• Server → Controller→
CartController.cs 実装
Server → Controller → CartController.cs
//
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace BlazorEcommerceApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CartController : ControllerBase
{
private readonly ICartService _cartService;
public CartController(ICartService cartService)
{
_cartService = cartService;
}
[HttpPost("products")]
public async
Task<ActionResult<ServiceResponse<List<CartProductResponse>>>>
GetCartProducts(List<CartItem> cartItems)
{
var result = await _cartService.GetCartProducts(cartItems);
return Ok(result);
}
---
---
クライアント側の
CartProduct 取得
• Client → Services →
ICartService.cs 実装
• Client → Services →
CartService.cs 実装
Client → Services → ICartService.cs
---
namespace BlazorEcommerceApp.Client.Services.CartService
{
public interface ICartService
{
event Action OnChange;
Task AddToCart(CartItem cartItem);
//ここを追加
Task<List<CartProductResponse>> GetCartProducts();
Task RemoveProductFromCart(int productId, int productTypeId);
}
}
---
Client → Services → CartService.cs
---
// このメソッドで取得する
public async Task<List<CartProductResponse>> GetCartProducts()
{
if (await _authService.IsUserAuthenticated())
{
var response = await
_http.GetFromJsonAsync
<ServiceResponse<List<CartProductResponse>>>
("api/cart");
return response.Data;
}
else
{
var cartItems = await
_localStorage.GetItemAsync<List<CartItem>>("cart");
if (cartItems == null)
return new List<CartProductResponse>();
var response = await
_http.PostAsJsonAsync
("api/cart/products", cartItems);
var cartProducts =
await
response.Content.ReadFromJsonAsync
<ServiceResponse<List<CartProductResponse>>>();
return cartProducts.Data;
}
}
---
Cart ページの実装 - 1
• Client → Pages → Cart.razor
• 先に @code 部分を実装する
Client → Pages → Cart.razor
---
// 先に@codeを実装する
@page "/cart"
@inject ICartService CartService
@inject IOrderService OrderService
@inject IAuthService AuthService
@inject NavigationManager NavigationManager
---
@code {
List<CartProductResponse> cartProducts = null;
string message = "Loading cart...";
bool isAuthenticated = false;
protected override async Task OnInitializedAsync()
{
isAuthenticated = await AuthService.IsUserAuthenticated();
await LoadCart();
}
private async Task RemoveProductFromCart(int productId, int productTypeId)
{
await CartService.RemoveProductFromCart(productId, productTypeId);
await LoadCart();
}
private async Task LoadCart()
{
await CartService.GetCartItemsCount();
cartProducts = await CartService.GetCartProducts();
if (cartProducts == null || cartProducts.Count == 0)
{
message = "Your cart is empty.";
}
}
private async Task UpdateQuantity(ChangeEventArgs e, CartProductResponse
product)
{
product.Quantity = int.Parse(e.Value.ToString());
if (product.Quantity < 1)
product.Quantity = 1;
await CartService.UpdateQuantity(product);
}
private async Task PlaceOrder()
{
string url = await OrderService.PlaceOrder();
NavigationManager.NavigateTo(url);
}
}
---
Cart ページの実装 - 2
• Client → Pages → Cart.razor
• 続いて View 部分を実装する
Client → Pages → Cart.razor
---
<PageTitle>Shopping Cart</PageTitle>
<h3>ショッピングカート</h3>
@if (cartProducts == null || cartProducts.Count == 0)
{
<span>@message</span>
}
else
{
<div>
@foreach (var product in cartProducts)
{
<div class="container">
<div class="image-wrapper">
<img src="@product.ImageUrl" class="image" />
</div>
<div class="name">
<h5><a
href="/product/@product.ProductId">@product.Title</a></h5>
<span>@product.ProductType</span><br />
<input type="number" value="@product.Quantity"
@onchange="@((ChangeEventArgs e) =>
UpdateQuantity(e, product))"
class="form-control input-quantity"
min="1" />
<button class="btn-delete" @onclick="@(() =>
RemoveProductFromCart
(product.ProductId, product.ProductTypeId))">
Delete
</button>
</div>
<div class="cart-product-price">
$@(product.Price * product.Quantity)</div>
</div>
}
<div class="cart-product-price">
Total (@cartProducts.Count):
$@cartProducts.Sum
(product => @product.Price * product.Quantity)
</div>
</div>
@if (isAuthenticated)
{
<div>
<h5>Delivery Address</h5>
<AddressForm />
</div>
}
<button @onclick="PlaceOrder" class="btn alert-success
float-end mt-1">Checkout</button>
}
---
Cart ページの実装 - 3
• Client → Pages → Cart.razor.css
実装
Client → Pages → Cart.razor.css
---
.container {
display: flex;
padding: 6px;
}
.image-wrapper {
width: 150px;
text-align: center;
}
.image {
max-height: 150px;
max-width: 150px;
padding: 6px;
}
.name {
flex-grow: 1;
padding: 6px;
}
.cart-product-price {
font-weight: 600;
text-align: right;
}
.btn-delete {
background: none;
border: none;
padding: 0px;
color: red;
font-size: 12px;
}
.btn-delete:hover {
text-decoration: underline;
}
.input-quantity {
width: 70px;
}
---
Cart から Item を削除 - 1
• Client → Services → CartService
ICartService.cs 追加
• Client → Services → CartService
ICartService.cs 修正
• RemoveProductFromCart 追加
Client → Services → CartService → ICartService.cs
---
//追加
Task RemoveProductFromCart(int productId, int productTypeId);
---
Client → Services → CartService → CartService.cs
---
//修正 RemoveProductFromCart 追加
public async Task RemoveProductFromCart(int productId, int productTypeId)
{
if (await _authService.IsUserAuthenticated())
{
await _http.DeleteAsync($"api/cart/{productId}/{productTypeId}");
}
else
{
var cart = await
_localStorage.GetItemAsync<List<CartItem>>("cart");
if (cart == null)
{
return;
}
var cartItem = cart.Find(x => x.ProductId == productId
&& x.ProductTypeId == productTypeId);
if (cartItem != null)
{
cart.Remove(cartItem);
await _localStorage.SetItemAsync("cart", cart);
}
}
}
---
Cart から Item を削除 - 2
• Client → Pages → Cart.razor
• View 部分修正
• @Code 部分修正
Client → Pages → Cart.razor
---
//View 部分修正
<button class="btn-delete" @onclick="@(() =>
RemoveProductFromCart(product.ProductId,
product.ProductTypeId))">
Delete
</button>---
Client → Pages → Cart.razor
---
// @Code 部分修正(追加)
private async Task RemoveProductFromCart
(int productId, int productTypeId)
{
await CartService.RemoveProductFromCart(productId, productTypeId);
await LoadCart();
}
---
private async Task LoadCart()
{
await CartService.GetCartItemsCount();
cartProducts = await CartService.GetCartProducts();
if (cartProducts == null || cartProducts.Count == 0)
{
message = "Your cart is empty.";
}
}
---
Cart から Item を削除 - 3
• Client → Pages → Cart.razor.css
への追加
Client → Pages → Cart.razor.css
---
// 追加
---
.btn-delete {
background: none;
border: none;
padding: 0px;
color: red;
font-size: 12px;
}
--- ---
Cart から Item を削除 - 4
• デバッグ実⾏
• カートを表⽰
• Chrome Dev Tools アプリケーションタブ
に移動
• ローカルストレージを表⽰
• delete ボタン押下してテスト
Cart モデルに数量を追加
- 1
• Shared →
CartProductResponse.cs を修正
Shared → CartProductResponse.cs
---
// 追加
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorEcommerceApp.Shared
{
public class CartProductResponse
{
public int ProductId { get; set; }
public string Title { get; set; } = string.Empty;
public int ProductTypeId { get; set; }
public string ProductType { get; set; } =
string.Empty;
public string ImageUrl { get; set; } =
string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
}
}
---
Cart モデルに数量を追加
- 2a
• Server → Services → CartService
→ CartService.cs を修正
Server → Services → CartService → CartService.cs
---
// 追加
---
var cartProduct = new CartProductResponse
{
ProductId = product.Id,
Title = product.Title,
ImageUrl = product.ImageUrl,
Price = productVariant.Price,
ProductType = productVariant.ProductType.Name,
ProductTypeId = productVariant.ProductTypeId,
Quantity = item.Quantity
};
---
Cart モデルに数量を追加
- 2b
• Server → Services → CartService
→ CartService.cs を修正
Server → Services → CartService → CartService.cs
---
public async Task<ServiceResponse<bool>>
AddToCart(CartItem cartItem)
{
cartItem.UserId = _authService.GetUserId();
var sameItem = await _context.CartItems
.FirstOrDefaultAsync(ci => ci.ProductId ==
cartItem.ProductId &&
ci.ProductTypeId ==
cartItem.ProductTypeId &&
ci.UserId == cartItem.UserId);
if (sameItem == null)
{
_context.CartItems.Add(cartItem);
}
else
{
//この箇所を追加する
sameItem.Quantity += cartItem.Quantity;
}
await _context.SaveChangesAsync();
return new ServiceResponse<bool> { Data = true };
}
---
Cart モデルに数量を追加
- 3a
• Client → Services → CartService
→ ICartService.cs を修正
Client → Services → CartService → ICartService.cs
---
namespace BlazorEcommerceApp.Client.Services.CartService
{
public interface ICartService
{
---
Task UpdateQuantity(CartProductResponse product);
Task StoreCartItems(bool emptyLocalCart);
//ここを追加
Task GetCartItemsCount();
}
} ---
Cart モデルに数量を追加
- 3b
• Client → Services → CartService
→ CartService.cs を修正
Client → Services → CartService → CartService.cs
---
public async Task UpdateQuantity(CartProductResponse product)
{
if (await _authService.IsUserAuthenticated())
{
var request = new CartItem
{
ProductId = product.ProductId,
Quantity = product.Quantity,
ProductTypeId = product.ProductTypeId
};
await _http.PutAsJsonAsync("api/cart/update-quantity", request);
}
else
{
var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart");
if (cart == null)
{
return;
}
var cartItem = cart.Find(x => x.ProductId == product.ProductId
&& x.ProductTypeId == product.ProductTypeId);
if (cartItem != null)
{
cartItem.Quantity = product.Quantity;
await _localStorage.SetItemAsync("cart", cart);
}
}
}
---
Cart モデルに数量を追加
- 4a
• 数値⼊⼒フィールドで数量を更新する
Client → Pages → Cart.razor
//追加
---
<div class="name">
<h5><a
href="/product/@product.ProductId">@product.Title</a>
</h5>
<span>@product.ProductType</span><br />
<input type="number" value="@product.Quantity"
@onchange="@((ChangeEventArgs e) => UpdateQuantity(e, product))"
class="form-control input-quantity"
min="1"
/>
---
Cart モデルに数量を追加
- 4b
• UpdateQuantity 追加する
Client → Pages → Cart.razor
//追加
---
private async Task UpdateQuantity
(ChangeEventArgs e, CartProductResponse product)
{
product.Quantity = int.Parse(e.Value.ToString());
if (product.Quantity < 1)
product.Quantity = 1;
await CartService.UpdateQuantity(product);
}
---
Cart モデルに数量を追加
- 4c
• UpdateQuantity 追加する
Client → Pages → Cart.razor
//追加
---
private async Task UpdateQuantity
(ChangeEventArgs e, CartProductResponse product)
{
product.Quantity = int.Parse(e.Value.ToString());
if (product.Quantity < 1)
product.Quantity = 1;
await CartService.UpdateQuantity(product);
}
---
Cart モデルに数量を追加
- 4c
• デバッグ実⾏
• カートを表⽰
• Chrome Dev Tools アプリケーションタブ
に移動
• ローカルストレージを表⽰
• update ボタン押下してテスト
認証・ユーザー登録、その他の機能の実装
認証 全体の流れ
• 新しいページの追加
• 最初のユーザーを登録
• パスワードのハッシュを作成し、パスワードを解決
• JSON Web Token
• Authorized View の利⽤
UserRegister Model の作成
• ユーザー登録には
新しいモデルが必要
• モデルの名前は
UserRegister
• Shared Project
を右クリックし、ここに
新 Class を追加
• Public Class
shared → UserRegister
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlazorEcommerceApp.Shared
{
public class UserRegister
{
[Required, EmailAddress]
public string Email { get; set; } = string.Empty;
[Required, StringLength(100, MinimumLength = 6)]
public string Password { get; set; } = string.Empty;
[Compare("Password", ErrorMessage = "The passwords do not match.")]
public string ConfirmPassword { get; set; } = string.Empty;
}
}
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf
Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf

Weitere ähnliche Inhalte

Was ist angesagt?

Concourseで快適な自動化の旅
Concourseで快適な自動化の旅Concourseで快適な自動化の旅
Concourseで快適な自動化の旅Kazuto Kusama
 
Gaming on aws 〜ゲームにおけるAWS最新活用術〜
Gaming on aws 〜ゲームにおけるAWS最新活用術〜Gaming on aws 〜ゲームにおけるAWS最新活用術〜
Gaming on aws 〜ゲームにおけるAWS最新活用術〜Amazon Web Services Japan
 
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とは
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とはがんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とは
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とはJun-ichi Sakamoto
 
Building the Game Server both API and Realtime via c#
Building the Game Server both API and Realtime via c#Building the Game Server both API and Realtime via c#
Building the Game Server both API and Realtime via c#Yoshifumi Kawai
 
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #1320210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13Amazon Web Services Japan
 
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)Trainocate Japan, Ltd.
 
async/await のしくみ
async/await のしくみasync/await のしくみ
async/await のしくみ信之 岩永
 
ストリーム処理を支えるキューイングシステムの選び方
ストリーム処理を支えるキューイングシステムの選び方ストリーム処理を支えるキューイングシステムの選び方
ストリーム処理を支えるキューイングシステムの選び方Yoshiyasu SAEKI
 
The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発Amazon Web Services Japan
 
知っておきたいFirebase の色んな上限について
知っておきたいFirebase の色んな上限について知っておきたいFirebase の色んな上限について
知っておきたいFirebase の色んな上限について健一 辰濱
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCdisc99_
 
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -Yoichi Kawasaki
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計Yoshinori Matsunobu
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NETterurou
 
[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テストTakahiro Moteki
 
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティスAmazon Web Services Japan
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)NTT DATA Technology & Innovation
 
JenkinsとCodeBuildとCloud Buildと私
JenkinsとCodeBuildとCloud Buildと私JenkinsとCodeBuildとCloud Buildと私
JenkinsとCodeBuildとCloud Buildと私Shoji Shirotori
 
20210526 AWS Expert Online マルチアカウント管理の基本
20210526 AWS Expert Online マルチアカウント管理の基本20210526 AWS Expert Online マルチアカウント管理の基本
20210526 AWS Expert Online マルチアカウント管理の基本Amazon Web Services Japan
 

Was ist angesagt? (20)

Concourseで快適な自動化の旅
Concourseで快適な自動化の旅Concourseで快適な自動化の旅
Concourseで快適な自動化の旅
 
Gaming on aws 〜ゲームにおけるAWS最新活用術〜
Gaming on aws 〜ゲームにおけるAWS最新活用術〜Gaming on aws 〜ゲームにおけるAWS最新活用術〜
Gaming on aws 〜ゲームにおけるAWS最新活用術〜
 
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とは
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とはがんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とは
がんばらなくても C# で Single Page Web アプリケーションが書けてしまう「Blazor」とは
 
Building the Game Server both API and Realtime via c#
Building the Game Server both API and Realtime via c#Building the Game Server both API and Realtime via c#
Building the Game Server both API and Realtime via c#
 
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #1320210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13
20210127 今日から始めるイベントドリブンアーキテクチャ AWS Expert Online #13
 
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)
AWSとオンプレミスを繋ぐときに知っておきたいルーティングの基礎知識(CCSI監修!)
 
async/await のしくみ
async/await のしくみasync/await のしくみ
async/await のしくみ
 
ストリーム処理を支えるキューイングシステムの選び方
ストリーム処理を支えるキューイングシステムの選び方ストリーム処理を支えるキューイングシステムの選び方
ストリーム処理を支えるキューイングシステムの選び方
 
The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発
 
知っておきたいFirebase の色んな上限について
知っておきたいFirebase の色んな上限について知っておきたいFirebase の色んな上限について
知っておきたいFirebase の色んな上限について
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPC
 
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -
Azure Functions&Logic Appではじめるサーバレスアプリケーション開発 - 入門編 -
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NET
 
[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト
 
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
 
NGINXをBFF (Backend for Frontend)として利用した話
NGINXをBFF (Backend for Frontend)として利用した話NGINXをBFF (Backend for Frontend)として利用した話
NGINXをBFF (Backend for Frontend)として利用した話
 
JenkinsとCodeBuildとCloud Buildと私
JenkinsとCodeBuildとCloud Buildと私JenkinsとCodeBuildとCloud Buildと私
JenkinsとCodeBuildとCloud Buildと私
 
20210526 AWS Expert Online マルチアカウント管理の基本
20210526 AWS Expert Online マルチアカウント管理の基本20210526 AWS Expert Online マルチアカウント管理の基本
20210526 AWS Expert Online マルチアカウント管理の基本
 

Ähnlich wie Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf

Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Shotaro Suzuki
 
Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Shotaro Suzuki
 
Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Shotaro Suzuki
 
New Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASMNew Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASMShotaro Suzuki
 
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...Shotaro Suzuki
 
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要.NET 6 と Blazor で作るクロスプラットフォームアプリ概要
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要Akira Inoue
 
.NET の過去、現在、そして未来 ~ .NET 最新アップデート
.NET の過去、現在、そして未来 ~ .NET 最新アップデート.NET の過去、現在、そして未来 ~ .NET 最新アップデート
.NET の過去、現在、そして未来 ~ .NET 最新アップデートAkira Inoue
 
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた日本マイクロソフト株式会社
 
.NET の過去、現在、そして未来
.NET の過去、現在、そして未来.NET の過去、現在、そして未来
.NET の過去、現在、そして未来Akira Inoue
 
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ 【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ 日本マイクロソフト株式会社
 
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!Akira Inoue
 
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜日本マイクロソフト株式会社
 
2021/03/19 パブリッククラウドを活かす運用プロセス自動化
2021/03/19 パブリッククラウドを活かす運用プロセス自動化2021/03/19 パブリッククラウドを活かす運用プロセス自動化
2021/03/19 パブリッククラウドを活かす運用プロセス自動化Issei Hiraoka
 
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像ASP.NET vNext / Visual Studio "14" に見る .NET の未来像
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像Akira Inoue
 
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめWebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめAkira Inoue
 
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NETAkira Inoue
 
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築Joni
 
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめWebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめAkira Inoue
 

Ähnlich wie Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf (20)

Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...
 
Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...
 
Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...Application development with c#, .net 6, blazor web assembly, asp.net web api...
Application development with c#, .net 6, blazor web assembly, asp.net web api...
 
New Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASMNew Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASM
 
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...
Building simple-app-using-.net 6 asp.net core web api-blazor web assembly-ela...
 
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要.NET 6 と Blazor で作るクロスプラットフォームアプリ概要
.NET 6 と Blazor で作るクロスプラットフォームアプリ概要
 
.NET の過去、現在、そして未来 ~ .NET 最新アップデート
.NET の過去、現在、そして未来 ~ .NET 最新アップデート.NET の過去、現在、そして未来 ~ .NET 最新アップデート
.NET の過去、現在、そして未来 ~ .NET 最新アップデート
 
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた
【de:code 2020】 「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた
 
.NET の過去、現在、そして未来
.NET の過去、現在、そして未来.NET の過去、現在、そして未来
.NET の過去、現在、そして未来
 
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ 【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ
【BS14】Blazor WebAssemblyとJavaScriptのインターオペラビリティ
 
[Japan Tech summit 2017] APP 001
[Japan Tech summit 2017] APP 001[Japan Tech summit 2017] APP 001
[Japan Tech summit 2017] APP 001
 
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!
Cloud から IoT まで、なんでもおまかせ ~ .NET 5 正式リリース!
 
【BS2】.NET 6 最新アップデート
【BS2】.NET 6 最新アップデート【BS2】.NET 6 最新アップデート
【BS2】.NET 6 最新アップデート
 
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜
【de:code 2020】 Build 2020 最新情報 〜 Azure & Visual Studio & .NET 〜
 
2021/03/19 パブリッククラウドを活かす運用プロセス自動化
2021/03/19 パブリッククラウドを活かす運用プロセス自動化2021/03/19 パブリッククラウドを活かす運用プロセス自動化
2021/03/19 パブリッククラウドを活かす運用プロセス自動化
 
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像ASP.NET vNext / Visual Studio "14" に見る .NET の未来像
ASP.NET vNext / Visual Studio "14" に見る .NET の未来像
 
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめWebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure Web Sites を使ったスマートフォンサイト構築のすすめ
 
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET
本格化するクラウド ネイティブに向けて進化する開発プラットフォームと .NET
 
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築
.NET Core 3.0 で Blazor を使用した​フルスタック C# Web アプリ​の構築
 
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめWebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめ
WebMatrix 2 と Azure を使ったスマートフォンサイト構築のすすめ
 

Mehr von Shotaro Suzuki

This is how our first offline technical event in three years was able to succ...
This is how our first offline technical event in three years was able to succ...This is how our first offline technical event in three years was able to succ...
This is how our first offline technical event in three years was able to succ...Shotaro Suzuki
 
Introducing the new features of the Elastic 8.6 release.pdf
Introducing the new features of the Elastic 8.6 release.pdfIntroducing the new features of the Elastic 8.6 release.pdf
Introducing the new features of the Elastic 8.6 release.pdfShotaro Suzuki
 
NET MAUI for .NET 7 for iOS, Android app development
 NET MAUI for .NET 7 for iOS, Android app development  NET MAUI for .NET 7 for iOS, Android app development
NET MAUI for .NET 7 for iOS, Android app development Shotaro Suzuki
 
What's New in the Elastic 8.5 Release
What's New in the Elastic 8.5 ReleaseWhat's New in the Elastic 8.5 Release
What's New in the Elastic 8.5 ReleaseShotaro Suzuki
 
Centralized Observability for the Azure Ecosystem
Centralized Observability for the Azure EcosystemCentralized Observability for the Azure Ecosystem
Centralized Observability for the Azure EcosystemShotaro Suzuki
 
What's New in the Elastic 8.4 Release
What's New in the Elastic 8.4 ReleaseWhat's New in the Elastic 8.4 Release
What's New in the Elastic 8.4 ReleaseShotaro Suzuki
 
Power Apps x .NET ~ Transforming Business Applications with Fusion Development
Power Apps x .NET ~ Transforming Business Applications with Fusion DevelopmentPower Apps x .NET ~ Transforming Business Applications with Fusion Development
Power Apps x .NET ~ Transforming Business Applications with Fusion DevelopmentShotaro Suzuki
 
devreljapan2022evaadvoc-final.pdf
devreljapan2022evaadvoc-final.pdfdevreljapan2022evaadvoc-final.pdf
devreljapan2022evaadvoc-final.pdfShotaro Suzuki
 
elastic-mabl-co-webinar-20220729
elastic-mabl-co-webinar-20220729elastic-mabl-co-webinar-20220729
elastic-mabl-co-webinar-20220729Shotaro Suzuki
 
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...Shotaro Suzuki
 
Building a search experience with Elastic – Introducing Elastic's latest samp...
Building a search experience with Elastic – Introducing Elastic's latest samp...Building a search experience with Elastic – Introducing Elastic's latest samp...
Building a search experience with Elastic – Introducing Elastic's latest samp...Shotaro Suzuki
 
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...Shotaro Suzuki
 
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...Shotaro Suzuki
 
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...Shotaro Suzuki
 
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -What's New in the Elastic 8.2 Release - Seamless User Experience with Search -
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -Shotaro Suzuki
 
Building Software Reliability through Distributed Tracing.pdf
Building Software Reliability through Distributed Tracing.pdfBuilding Software Reliability through Distributed Tracing.pdf
Building Software Reliability through Distributed Tracing.pdfShotaro Suzuki
 
Building a Flutter Development Environment with VSCode and Useful Extensions
Building a Flutter Development Environment with VSCode and Useful ExtensionsBuilding a Flutter Development Environment with VSCode and Useful Extensions
Building a Flutter Development Environment with VSCode and Useful ExtensionsShotaro Suzuki
 
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...Shotaro Suzuki
 
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...Shotaro Suzuki
 
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...Shotaro Suzuki
 

Mehr von Shotaro Suzuki (20)

This is how our first offline technical event in three years was able to succ...
This is how our first offline technical event in three years was able to succ...This is how our first offline technical event in three years was able to succ...
This is how our first offline technical event in three years was able to succ...
 
Introducing the new features of the Elastic 8.6 release.pdf
Introducing the new features of the Elastic 8.6 release.pdfIntroducing the new features of the Elastic 8.6 release.pdf
Introducing the new features of the Elastic 8.6 release.pdf
 
NET MAUI for .NET 7 for iOS, Android app development
 NET MAUI for .NET 7 for iOS, Android app development  NET MAUI for .NET 7 for iOS, Android app development
NET MAUI for .NET 7 for iOS, Android app development
 
What's New in the Elastic 8.5 Release
What's New in the Elastic 8.5 ReleaseWhat's New in the Elastic 8.5 Release
What's New in the Elastic 8.5 Release
 
Centralized Observability for the Azure Ecosystem
Centralized Observability for the Azure EcosystemCentralized Observability for the Azure Ecosystem
Centralized Observability for the Azure Ecosystem
 
What's New in the Elastic 8.4 Release
What's New in the Elastic 8.4 ReleaseWhat's New in the Elastic 8.4 Release
What's New in the Elastic 8.4 Release
 
Power Apps x .NET ~ Transforming Business Applications with Fusion Development
Power Apps x .NET ~ Transforming Business Applications with Fusion DevelopmentPower Apps x .NET ~ Transforming Business Applications with Fusion Development
Power Apps x .NET ~ Transforming Business Applications with Fusion Development
 
devreljapan2022evaadvoc-final.pdf
devreljapan2022evaadvoc-final.pdfdevreljapan2022evaadvoc-final.pdf
devreljapan2022evaadvoc-final.pdf
 
elastic-mabl-co-webinar-20220729
elastic-mabl-co-webinar-20220729elastic-mabl-co-webinar-20220729
elastic-mabl-co-webinar-20220729
 
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...
Discover what's new in the Elastic 8.3 release - Find, monitor, and protect e...
 
Building a search experience with Elastic – Introducing Elastic's latest samp...
Building a search experience with Elastic – Introducing Elastic's latest samp...Building a search experience with Elastic – Introducing Elastic's latest samp...
Building a search experience with Elastic – Introducing Elastic's latest samp...
 
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
 
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...
Elastic x Microsoft Azure Integration Evolution - Integrated Monitoring for S...
 
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...
Building 3D mobile apps using Power Apps Mixed Reality controls, Azure SQL Da...
 
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -What's New in the Elastic 8.2 Release - Seamless User Experience with Search -
What's New in the Elastic 8.2 Release - Seamless User Experience with Search -
 
Building Software Reliability through Distributed Tracing.pdf
Building Software Reliability through Distributed Tracing.pdfBuilding Software Reliability through Distributed Tracing.pdf
Building Software Reliability through Distributed Tracing.pdf
 
Building a Flutter Development Environment with VSCode and Useful Extensions
Building a Flutter Development Environment with VSCode and Useful ExtensionsBuilding a Flutter Development Environment with VSCode and Useful Extensions
Building a Flutter Development Environment with VSCode and Useful Extensions
 
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...
Introducing Elastic 8.1 Release - More Integration, Faster Indexing Speed, Lo...
 
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...
Introducing the elastic 8.0 release a new era of speed, scale, relevance, and...
 
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...
Developers-Summit-2022_Improving-Digital-Customer-Experience-with-Enterprise_...
 

Application development with c#, .net 6, blazor web assembly, asp.net web api, azure, part 4.pdf

  • 1. C#, .NET 6, Blazor WebAssembly, ASP.NET Web API, Azure による アプリ開発 – その4 鈴⽊ 章太郎 Elastic テクニカルプロダクトマーケティングマネージャー/エバンジェリスト デジタル庁 省庁業務グループ ソリューションアーキテクト
  • 3. l 前回までの復習 l Blazor 概要 l 今回作成する Web アプリケーションの概要 l Blazor WebAssembly プロジェクト作成 l Web API コントローラー追加、モデル追加 l Entity Framework による Code First データベース作成 l 商品サービス、商品リスト、カテゴリーサービス等必要なサービス、 CRUD 処理等の実装 l 検索サービスの追加と検索コンポーネントの実装 l UI/UX の変更、カートサービス l 認証・ユーザー登録機能、その他の実装 (p.151-p.219) アジェンダ
  • 4. 今回の範囲 l 2⽉、3⽉、4⽉の復習 l 認証・ユーザー登録機能の実装、その他 (p.151-p.219) セッションでご紹介した EC アプリ .NET 5版ですが、参考にさせて戴きました。 https://github.com/patrickgod/PreviewYT
  • 6. Modern Web UI with .NET & Blazor Server WebAssembly Hybrid HTML、CSS、.NET、C#... JavaScript の代わりに Open Web 標準でアプリ開発 どこにでもホストできる
  • 7. MVC Razor Pages Blazor HTTP APIs SignalR Part of the ASP.NET Core family Web UI Services Worker gRPC SPA
  • 8. Blazor – .NET 5 まで Blazor Server Blazor WebAssembly DOM Blazor WebAssembly .NET Razor Components Blazor .NET Razor Components DOM SignalR ü DB アクセス含むサーバー機能へのフルアクセス ü ⾼速なスタートアップ ü コードがサーバーから離れない ü 古いブラウザとシンクライアントをサポート ü 永続的な接続が必要 ü UI の遅延が⾼い ü完全にクライアント側で実⾏ ü必要なサーバー コンポーネントなし ü静的サイトとしてホスト üオフラインで実⾏可能 ü⼤きなダウンロードサイズ üランタイムパフォーマンスの低下 Blazor Server (.NET 5) Blazor WebAssembly (.NET 5)
  • 9. Blazor – .NET 6 による強化 Blazor Server Blazor WebAssembly DOM Blazor WebAssembly .NET Razor Components Blazor .NET Razor Components DOM SignalR Blazor WebAssembly の事前 (AOT) コンパイル対応 Blazor WebAssembly アプリのダウンロードサイズの縮⼩ Error Boundaries Razor コンポーネント型の推論とジェネリック型の制約 動的コンポーネント プリレンダリング中の Blazor コンポーネント状態の永続性 Hot Reload, Native File Reference, 他多数 .NET 6
  • 10. Blazor Server と Blazor WebAssembly の 開発モデルの違い Blazor Server Blazor WebAssembly DOM Blazor WebAssembly .NET Razor Components Blazor .NET Razor Components DOM SignalR Blazor Server • 開発モデルは C/S 型に近い • DOM(ブラウザ UI)と Blazor ランタイム(仮想 DOM) がやりとりし UI 描画(差分更新) • 画⾯の⼊出⼒部分のみをリモートデスクトップのようにブラウザ 側に持ってきているとみなせる • SignalR(Web ソケット通信) • DB に直接アクセス可能 • Web アプリケーションを Client - Server 型に近いモデルで 開発可能 • Web サーバとの常時接続が必要 • サーバ側でリソース効率の⾼いアプリの作り⽅が必要 • Hot Reload Blazor WebAssembly • サンドボックス制限 • DB アクセス不可 → Native File Reference による ローカル DBアクセス • Web API を介して DB アクセス • 静的な Web サーバにホスト • アプリ全体がダウンロード(⼤きくなりがち) • DOM(ブラウザ UI)と Blazor ランタイム(仮想 DOM)がやりとりしUI 描画(差分更新)、ランタイム が Blazor アプリ(UI ロジック)とやりとりする • Hot Reload (デバッグなしで実⾏)
  • 11. Web Assembly(WASM) とは • Web ブラウザ上でバイナリコードを直接実⾏できる • 2019 年 12 ⽉ W3C 勧告、正式なウェブ標準に認定 • 様々な⾔語のバイナリコードを主要なブラウザのサンドボックス内で動作可能 • Web Assembly バイナリコードへのコンパイラなどのツールセットが必要 Edge Chrome Safari Firefox Web Assembly バイナリコード (W3C 標準技術) C++ WASM コンパイラ Rust WASM コンパイラ C WASM コンパイラ SQLite ソースコード(C) Rust ソースコード C++ ソースコード
  • 12. .NET 6 における Blazor WebAssembly 新機能 • 事前 (AOT) 実⾏コンパイル • カスタム要素 • ⼩規模なアプリサイズ • Native File Reference • Hot Reload • Component, .NET, HTML, CSS… …その他数⼗個の更新あり
  • 13. Blazor WebAssembly ⼩規模なアプリサイズ .NET 5 • Publish size: 1.7 MB .NET 6 • Publish size: 1.0 MB • ~40% size reduction
  • 14. Blazor WebAssembly のホスティング ASP.NET Blazor WebAssem bly APIs Globally distributed hosting Blazor WebAssem bly Serverless functions APIs App Services Azure Static Web Apps ASP.NET Globally distributed hosting Microservices Blazor WebAssembly APIs Blazor WebAssembly APIs
  • 15. Get started with Blazor • Go to https://blazor.net • Install the .NET SDK • .NET Conf 2021 https://www.dotnetconf.net/ • .NET Conf 2021 – videos/slides/demos https://github.com/dotnet-presentations/dotNETConf/tree/master/2021/MainEvent/Technical Visual Studio Visual Studio for Mac Visual Studio Code + C# extension
  • 17. ASP.NET Core Blazor プロジェクトの構造 https://docs.microsoft.com/ja-jp/aspnet/core/blazor/project-structure?view=aspnetcore-6.0 Blazor WebAssembly アプリの初期ファイルとディレクトリ構造 [Client] • Connected Service • Dependencies • Pages • Properties • Shared • wwwrooot • _imports.razor • App.razor • Program.cs [Server] • Connected Service • Dependencies • Controllers • Pages • Properties • appsettings.json • Program.cs [Shared] • Connected Service • Dependencies • WeatherForecast.cs
  • 18. ASP.NET Core Blazor のホスティング モデル https://docs.microsoft.com/ja-jp/aspnet/core/blazor/hosting-models?view=aspnetcore-6.0 • Blazor WebAssembly hosting model を使⽤すると、次のようになります。 • Blazor アプリ、その依存関係、.NET ランタイムが並⾏してブラウザーにダウンロードされます。 • アプリがブラウザー UI スレッド上で直接実⾏されます。 • 次の展開戦略がサポートされています。 • ASP.NET Core でのホストされた展開 • Blazor アプリは、ASP.NET Core アプリによって提供されます。 • "ホストされたデプロイ" により、 WebAssembly アプリが、Web サーバー上で実⾏されている ASP.NET Core アプリからブラウザーに提供されます。 • クライアント Blazor WebAssembly アプリは、サーバー アプリの他の静的な Web アセットと共に、サーバーアプリの /bin/Release/{TARGET FRAMEWORK}/publish/wwwroot フォルダーに発⾏されます。 • 2 つのアプリが⼀緒に展開されます。 ASP.NET Core アプリをホストできる Web サーバーが必要です。 ホストされている展開の場合、Visual Studio には WebAssembly アプリ プロジェクト テンプレートが含まれており (dotnet new コマンドを使⽤する場合は blazorwasm テンプレー ト)、 Hosted オプションが選択されています (dotnet new コマンドを使⽤する場合は -ho|--hosted)。 • スタンドアロン展開 • Blazor アプリは、Blazor アプリの提供に .NET が使⽤されていない静的ホスティング Web サーバーまたはサービス上に配置されます。 • "スタンドアロン デプロイ" により、 WebAssembly アプリが、クライアントによって直接要求される静的ファイルのセットとして提供されます。 任意の静 的ファイル サーバーで Blazor アプリを提供できます。 • スタンドアロンのデプロイアセットは、/bin/Release/{TARGET FRAMEWORK}/publish/wwwroot フォルダーに発⾏されます。 • Azure App Service • Blazor WebAssembly アプリは、Blazor 上でアプリをホストするために使⽤される Windows 上の Azure App Service にデプロイできます。 • スタンドアロンの Blazor WebAssembly アプリを Azure App Service for Linux にデプロイすることは、現在サポートされていません。 現時点で は、アプリをホストする Linux サーバー イメージは使⽤できません。 このシナリオを可能にするための取り組みが進⾏中です。 • Azure Static Web Apps • 詳細については、「Tutorial: Building a static web app with Blazor in Azure Static Web Apps」を参照してください。 • IIS
  • 19. EC デモアプリの画⾯遷移例 トップ 検索 Movies Books Video Games 選択 カート 決済・ログイン ユーザー登録
  • 20. EC Demo アプリの構成 1 Azure SQL Database Elastic Cloud 東⽇本リージョン マスターノード x 1 データノード x 2 ML ノード x 1 https://f79...c67.japaneast .azure.elastic- cloud.com:9243/ 全⽂検索クエリ 検索・更新 UI Azure サブスクリプション Azure App Service Elastic APM Endpoint に送信 Blazor Server APM .NET Agent Blazor WebAssembly CRUD Visual Studio 2022 for Mac Azure Data Studio
  • 21. EC Demo アプリの構成 2 Azure SQL Database Elastic Cloud 東⽇本リージョン マスターノード x 1 データノード x 2 ML ノード x 1 https://f79...c67.japaneast .azure.elastic- cloud.com:9243/ CRUD Azure サブスクリプション Visual Studio 2022 for Mac Azure App Service Elastic APM Endpoint に送信 Azure Data Studio ASP.NET 6 Web API Azure Static Web Apps Blazor WebAssembly 検索・更新 UI APM .NET Agent Blazor WebAssembly 全⽂検索クエリ
  • 22. ASP.NET Core Blazor のホスティング モデル https://docs.microsoft.com/ja-jp/aspnet/core/blazor/hosting-models?view=aspnetcore-6.0#blazor-webassembly ホスティング モデルの選択 Blazor サーバー Blazor WebAssembly 完全な .NET Core API の互換性 ✔ ❌ サーバー ソースへの直接アクセス ✔ ❌ ⼩さいペイロード サイズと ⾼速な初期読み込み時間 ✔ ❌ サーバー上でのアプリ コードの セキュリティ保護と⾮公開 ✔ ❌† ダウンロードしたアプリを オフラインで実⾏ ❌ ✔ 静的サイトのホスティング ❌ ✔ クライアントへの処理のオフロード ❌ ✔
  • 26. Product Model の追加 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorECommerceApp.Shared { public class Product { public int Id { get; set; } public string Title { get; set; }; public string Description { get; set; }; public string ImageUrl { get; set; }; public decimal Price { get; set; } } } --- @using BlazorECommerceApp.Shared ---
  • 28. ProductList.Razor の追加 1 <h3>ProductList</h3> --- @code { public static List<Product> Products = new List<Product> { new Product { Id = "1", Title = "The Hitchhiker's Guide to the Galaxy", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg", Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが⽣み出したコメディ SFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年のテレビシリーズ、1984年のテキストベー スのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。", Price. = 9.99m } new Product { Id = "2", Title = "Ready Player One", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg", Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年のディストピアを舞台 に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作者の財産を相続することになるというス トーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ(ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8 ⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、章のひとつで少し触れているウィル・ウィートンである[3][4]。 20 2012年には アメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞を受賞し[5] 、2011年にはプロメテウス賞を 受賞した[6]。”, Price. = 7.99m } new Product { Id = "3", Title = "Nineteen Eighty-Four”, ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg", Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語である。1949年6⽉8 ⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者であるオーウェルは、スターリン主義のロシ アとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。 より広く、この⼩説では政治における真実と事実の役割と、それらが操られる⽅法を検証し ている。" , Price = 6.99m } }
  • 29. ProductList.Razor の追加 2 <h3>ProductList</h3> <ul class="list-unstyled"> @foreach (var product in ProductService.Products) { <li class="media my-3"> <div class="media-img-wrapper mr-2"> <a href="/product/@product.Id"> <img class="media-img" src="@product.ImageUrl" alt="@product.Title" /> </a> </div> <div class="media-body"> <a href="/product/@product.Id"> <h4 class="mb-0">@product.Title</h4> </a> <p>@product.Description</p> <h5 class="price"> @GetPriceText(product) </h5> </div> </li> } </ul> ---
  • 30. Index.Razor の変更 @page "/" <ProductList /> https://localhost:7226/#
  • 33. ProductController.cs の追加 1 [Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private static List <Product> Products = new List <Product> { new Product { Id = "1", Title = "The Hitchhiker's Guide to the Galaxy", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg", Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが ⽣み出したコメディSFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年の テレビシリーズ、1984年のテキストベースのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。", Price. = 9.99m } new Product { Id = "2", Title = "Ready Player One", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg", Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年の ディストピアを舞台に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作 者の財産を相続することになるというストーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ (ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、 章のひとつで少し触れているウィル・ウィートンである[3][4]。2012年にはアメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞 を受賞し[5] 、2011年にはプロメテウス賞を受賞した[6]。”, Price. = 7.99m } new Product { Id = "3", Title = "Nineteen Eighty-Four”, ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg", Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語 である。1949年6⽉8⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者で あるオーウェルは、スターリン主義のロシアとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。より広く、この⼩説では政治 における真実と事実の役割と、それらが操られる⽅法を検証している。" , Price = 6.99m } } ---
  • 34. ProductController.cs の追加 2 --- [HttpGet] public async Task<ActionResult<<List<Product>>> GetProducts() { rerurn Ok(Product) var result = await _productService. GetProductsAsync(); return Ok(result); } https://localhost:7226/#
  • 35. ProductList.Razor の変更(クライアントからの呼び出し) --- @inject HttpClient Http <ul class="list-unstyled"> @foreach (var product in ProductService.Products) { <li class="media my-3"> <div class="media-img-wrapper mr-2"> <a href="/product/@product.Id"> <img class="media-img" src="@product.ImageUrl" alt="@product.Title" /> </a> </div> <div class="media-body"> <a href="/product/@product.Id"> <h4 class="mb-0">@product.Title</h4> </a> <p>@product.Description</p> <h5 class="price"> @GetPriceText(product) </h5> </div> </li> } </ul> --- code@ { private static List<Product> Products {get; set;} = new List<Product>(); protected override async TaskOnInitializedAsync() { Products = await Http.GetFromJsonAsync<List<Product>> ("api/product"); } }
  • 36. Entity Framework による Code First データベース作成
  • 37. Blazor アプリのデバッグその他の TIPS dotnet watch run public class xxx prop → snippets が出て予測してくれる
  • 38. swagger インストールその他 • https://localhost:(ポート番 号)/swagger/index.html --- // AddRazorPages の後 builer.Services.AddEndpointApiExploler(); builer.Services.AddSwaggerGen(); //var ap = buildder.Build();の後 app.UserSwaggerUI(); // app.UseHttpsRedirection();の前 app.UseSwagger(); // Swagger UI で Products の shema が表⽰されない場合 // Public Async Task を書き換え Task<Action> GetProduct() → Task<ActionResult<List<Product>>> GetProduct()
  • 39. .NET Core Entity Framework 6.0 インストール • Microsoft.EntityFrameworkCore • Microsoft.EntityFrameworkCore. Design • Microsoft.EntityFrameworkCore. SqlServer • Mac の場合は、唯⼀の選択肢︕ Windows の場合は、SQL Server Express Edition をインストールして使う ⼿もあり appsettings.json で ”ConnectionString” とうつと⾃動的に出てくる この⽂字列をコピペして修正すればOK ※ 注意点 EF で Code First で Database を⾃動⽣成した場合、巨⼤なインスタンスになっ ている(3⽇くらいで数千円レベル)。 instance のサイズだけはすぐに修正して⼩さいものBasic2TB等にする。 これなら⽉額数百円。
  • 40. Azure SQL Database 接続⽂字列追加 { "ConnectionStrings": { "DefaultConnection": "Server=tcp:xxx.database.windows.net,1433;Initial Catalog=BlazorECommerceApp;Persist Security Info=False;User ID=(UserID);Password=(Password); MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } dotnet ef migration add CreateInitial // Migrations フォルダーと Migration クラス作成 dotnet ef Update Database // Azure SQL データベースとテーブル作成
  • 41. Product Model の追加 • BlazorECommerceApp.Shared フォルダに、 Product クラスを作成 • Book.cs に右のコードを記載 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorECommerceApp.Shared { public class Product { public int Id { get; set; } [Required] public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string ImageUrl { get; set; } = string.Empty; public Category? Category { get; set; } public int CategoryId { get; set; } public bool Featured { get; set; } = false; public List<ProductVariant> Variants { get; set; } = new List<ProductVariant>(); public bool Visible { get; set; } = true; public bool Deleted { get; set; } = false; [NotMapped] public bool Editing { get; set; } = false; [NotMapped] public bool IsNew { get; set; } = false; } }
  • 42. DataContext 作成 • Class を追加 • DataContext.class • Serverプロジェクト側の Program.cs 修正 • global using Microsoft.EntityFramework.Core を⼊ れておくと楽 namespace BlazorECommerceApp.Server.Data { public class DataContext : DbContext { // DataContext を作るのに ctor とタイプするとできる // 全体的に IntelliCode が補完 public DataContext(DbContextOptions<DataContext> options) : base(options) { } } } • Server プロジェクト側の Program.cs • DataContext.cs global using Microsoft.EntityFrameworkCore;
  • 43. Entity Framework を使った最初の DB Migration //最初に名前を決めておく dotnet ef migrations add CreateInitial //成功したら Migration フォルダを開いて内容を確認 //データベース作成 dotnet ef database update
  • 44. データのシード(2回⽬のマイグレーション) --- Protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Product>().HasData( ---<ここに new Product 3エントリをコピペ>--- ); } dotnet ef migrations add ProductSeeding dotnet ef database update
  • 45. (参考)旧 ProductController.cs [Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private static List <Product> Products = new List <Product> { new Product { Id = "1", Title = "The Hitchhiker's Guide to the Galaxy", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/b/bd/H2G2_UK_front_cover.jpg", Description = "銀河ヒッチハイク・ガイド[注 1](HG2G、[1] HHGTTG、[2] H2G2、[3] tHGttGと表記することもある)は、ダグラス・アダムスが ⽣み出したコメディSFフランチャイズである。1978年にBBC Radio 4で放送されたラジオコメディが原作で、その後、舞台、⼩説、コミック、1981年の テレビシリーズ、1984年のテキストベースのコンピュータゲーム、2005年の⻑編映画など、様々な形式で翻案されている。", Price. = 9.99m } new Product { Id = "2", Title = "Ready Player One", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/a/a4/Ready_Player_One_cover.jpg", Description = “「レディ・プレイヤー・ワン」は2011年に発表されたSF⼩説で、アメリカ⼈作家アーネスト・クラインのデビュー作である。2045年の ディストピアを舞台に、主⼈公のウェイド・ワッツが世界規模のバーチャルリアリティゲームのイースターエッグを探し、その発⾒によってゲーム製作 者の財産を相続することになるというストーリーである。クラインは2010年6⽉、⼊札競争の末に本作の出版権をクラウン・パブリッシング・グループ (ランダムハウスの⼀部⾨)に売却した[1]。 本作は2011年8⽉16⽇に出版された[2]。同⽇にはオーディオブックも発売されており、ナレーションは、 章のひとつで少し触れているウィル・ウィートンである[3][4]。2012年にはアメリカ図書館協会のヤングアダルト図書館サービス部⾨からアレックス賞 を受賞し[5] 、2011年にはプロメテウス賞を受賞した[6]。”, Price. = 7.99m } new Product { Id = "3", Title = "Nineteen Eighty-Four”, ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c3/1984first.jpg", Description = “ Nineteen Eighty-Four(1984)は、イギリスの作家ジョージ・オーウェルが書いたディストピア社会SF⼩説であり、教訓的な物語 である。1949年6⽉8⽇にセッカー&ウォーバーグ社から出版され、オーウェルが⽣前に完成させた9冊⽬にして最後の著作となった。⺠主社会主義者で あるオーウェルは、スターリン主義のロシアとナチス・ドイツをモデルに、⼩説の中の全体主義政府を描いた[2][3][4]。より広く、この⼩説では政治 における真実と事実の役割と、それらが操られる⽅法を検証している。" , Price = 6.99m } } ---
  • 46. ProductController.cs 内のデータを削除 • Server Program.cs を開き global using Blazorxxx.Server.Data; を追加 • private readonly DataContext context; ⽣成されるので、これを修正 • しかしこれを⾃動的に実施したい //context → _context に変更 public ProductController(DataContext context) { _cotext = context; } • Server プロジェクト側の Program.cs global using Blazorxxx.Server.Data; ツール → オプションから テキストエディタ → C# → CodeStyle → Naming → Manage Naming Style Naming Style Title : _fieldName Capitalizatin : camel Case Name これを追加したらprivate or internal Style に追加 _fieldName、Suggestion を選択 エディタに戻って create field context を選択する
  • 47. [HttpGet] GetProduct() 変更 • ProductList • ProductController • DataContext • [HttpGet] GetProduct() 変更 var products = await _cotext.Products.ToListAsync(); return Ok(products)
  • 49. Blazor WebAssembly の追加・改修等 • ProductDetail.razor.css 追加 • ProductDetail.razor 編集 @page "/product/{id:int}" @inject IProductService ProductService @inject ICartService CartService
  • 51. Category を実装する using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorECommerceApp.Shared { public class Category { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; public bool Visible { get; set; } = true; public bool Deleted { get; set; } = false; [NotMapped] public bool Editing { get; set; } = false; [NotMapped] public bool IsNew { get; set; } = false; } }
  • 52. Category の Seeding と Migration(3回⽬) • • --- modelBuilder.Entity<Category>().HasData( new Category { Id = 1, Name = "Books", Url = "books" }, new Category { Id = 2, Name = "Movies", Url = "movies" }, new Category { Id = 3, Name = "Video Games", Url = "video-games" } ); --- • DataContext.cs
  • 53. Category サービスの Client 側 への実装 - 1 • • namespace Blazorxxxxxxxx.Client.Services.CategoryService { public class CategoryService : ICategoryService { private readonly HttpClient _http; public CategoryService(HttpClient http) { _http = http; } --- • CategoryServices.cs
  • 54. Category サービスの Client 側 への実装 - 2 • CategoryService • global using で⼀番上に追加 //builder.Services.AddScoped<IProductService,ProductSe rvice>();の下に追加 builder.Services.AddScoped<ICategoryService, CategoryService>(); //⼀番上に追加 global using Blazorxxxxxxxx.Client.Services.CategoryService; • Program.cs
  • 55. Category サービスの Client 側 への実装 - 3 • @using Blazorxxxxxxxx.Client.Services.ProductService • _Imports.razor
  • 56. iCategoryServices の実装 • namespace Blazorxxxxxxx.Client.Services.CategoryService { public interface ICategoryService { event Action OnChange; List<Category> Categories { get; set; } List<Category> AdminCategories { get; set; } Task GetCategories(); Task GetAdminCategories(); Task AddCategory(Category category); Task UpdateCategory(Category category); Task DeleteCategory(int categoryId); Category CreateNewCategory(); } } • iCategoryServices.cs
  • 57. NavMenu への Category の表⽰ • NavMenu.razor の編集 • @inject ICategoryService CategoryService を冒頭に追加 • @code の後半部分に追加 • NavMenuCssClass の追加 protected override async Task OnInitializedAsync() { await CategoryService.GetCategories(); } • NavMenu.razor • NavMenuCssClass <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <nav class="flex-column"> <div class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> Home </NavLink> </div> @foreach (var category in CategoryService.Categories) { <div class="nav-item px-3"> <NavLink class="nav-link" href="@category.Url"> @category.Name </NavLink> </div> } </nav> </div>
  • 58. Server の Category サービスから Product を取得 - 1 • • Task<ServiceResponse<Product>> GetProduct(int productId); • iProductService.cs public async Task GetProducts(string? categoryUrl = null) { var result = categoryUrl == null ? await _http.GetFromJsonAsync<ServiceResponse <List<Product>>>("api/product/featured") : await _http.GetFromJsonAsync<ServiceResponse <List<Product>>>($"api/product/category/{categoryUrl}"); if (result != null && result.Data != null) Products = result.Data; CurrentPage = 1; PageCount = 0; if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged.Invoke(); } • ProductService.cs
  • 59. Server の Category サービスから Product を取得 - 2 • • • https://locahost:(ポート番 号)/swagger/index.html --- [HttpGet("category/{categoryUrl}")] public async Task<ActionResult<ServiceResponse <List<Product>>>> GetProductsByCategory(string categoryUrl) { var result = await _productService. GetProductsByCategory(categoryUrl); return Ok(result); } --- • ProductController.cs
  • 60. Client の Category サービスから Product を取得 - 1 • • Task GetProducts を実装追加 • event ProductChanged を追加 • public async Task GetProducts(string? categoryUrl = null) { var result = categoryUrl == null ? //この2⾏がポイント await _http.GetFromJsonAsync<ServiceResponse <List<Product>>>("api/product/featured") : await _http.GetFromJsonAsync<ServiceResponse <List<Product>>>($"api/product/category/{categoryUrl}"); if (result != null && result.Data != null) Products = result.Data; CurrentPage = 1; PageCount = 0; if (Products.Count == 0) Message = "商品がみつかりません。"; //ここもポイント ProductsChanged.Invoke(); } • iProductService.cs • iProductService.cs Task GetProducts(string? categoryUrl = null); event Action ProductsChanged; • iProductService.cs
  • 61. Client の Category サービスから Product を取得 - 2 • @page "/" @page "/search/{searchText}/{page:int}" @page "/{categoryUrl}" @inject IProductService ProductService <PageTitle>マイショップ</PageTitle> @if (SearchText == null && CategoryUrl == null) { <FeaturedProducts /> } else { <ProductList /> } @code { [Parameter] public string? CategoryUrl { get; set; } = null; [Parameter] public string? SearchText { get; set; } = null; [Parameter] public int Page { get; set; } = 1; protected override async Task OnParametersSetAsync() { if (SearchText != null) { await ProductService.SearchProducts(SearchText, Page); } else { await ProductService.GetProducts(CategoryUrl); } } } • Index.razor
  • 62. Client の Category サービスから Product を取得 - 3 • • • • --- @code { protected override void OnInitialized() { ProductService.ProductsChanged += StateHasChanged; } • ProductList.razor --- public void Dispose() { ProductService.ProductsChanged -= StateHasChanged; }
  • 63. Shared に ProductVariant.cs を追加 • • • • ProductVariant.cs --- namespace BlazorECommerceApp.Shared { public class ProductVariant { [JsonIgnore] public Product? Product { get; set; } public int ProductId { get; set; } public ProductType? ProductType { get; set; } public int ProductTypeId { get; set; } [Column(TypeName = "decimal(18,2)")] public decimal Price { get; set; } [Column(TypeName = "decimal(18,2)")] public decimal OriginalPrice { get; set; } public bool Visible { get; set; } = true; public bool Deleted { get; set; } = false; [NotMapped] public bool Editing { get; set; } = false; [NotMapped] public bool IsNew { get; set; } = false; } }
  • 64. Composite Primary Key の追加と Seeding の実施(4回⽬) • • • • • ProductTypes • ProductVariants --- modelBuilder.Entity<ProductVariant>().HasData( new ProductVariant { ProductId = 1, ProductTypeId = 2, Price = 9.99m, OriginalPrice = 19.99m }, new ProductVariant { ProductId = 1, ProductTypeId = 3, Price = 7.99m }, new ProductVariant { ProductId = 1, ProductTypeId = 4, Price = 19.99m, OriginalPrice = 29.99m }, new ProductVariant { ProductId = 2, ProductTypeId = 2, Price = 7.99m, OriginalPrice = 14.99m }, new ProductVariant { ProductId = 3, ProductTypeId = 2, Price = 6.99m }, new ProductVariant { ProductId = 4, ProductTypeId = 5, Price = 3.99m }, new ProductVariant { ProductId = 4, ProductTypeId = 6, Price = 9.99m }, new ProductVariant { ProductId = 4, ProductTypeId = 7, Price = 19.99m }, new ProductVariant { ProductId = 5, ProductTypeId = 5, Price = 3.99m, }, new ProductVariant { ProductId = 6, ProductTypeId = 5, Price = 2.99m }, new ProductVariant { ProductId = 7, ProductTypeId = 8, Price = 19.99m, OriginalPrice = 29.99m }, new ProductVariant { ProductId = 7, ProductTypeId = 9, Price = 69.99m }, new ProductVariant { ProductId = 7, ProductTypeId = 10, Price = 49.99m, OriginalPrice = 59.99m }, new ProductVariant { ProductId = 8, ProductTypeId = 8, Price = 9.99m, OriginalPrice = 24.99m, }, new ProductVariant { ProductId = 9, ProductTypeId = 8, Price = 14.99m }, new ProductVariant { ProductId = 10, ProductTypeId = 1, Price = 159.99m, OriginalPrice = 299m }, new ProductVariant { ProductId = 11, ProductTypeId = 1, Price = 79.99m, OriginalPrice = 399m } ); } ---
  • 65. Product Variants と Types を Product Service に含める - 1 • • • タブは Network • フィルターは Fetch/XHR で実⾏ --- public async Task<ServiceResponse<Product>> GetProductAsync(int productId) { var response = new ServiceResponse<Product>(); Product product = null; if (_httpContextAccessor.HttpContext.User.IsInRole("Admin")) { product = await _context.Products .Include(p => p.Variants.Where(v => !v.Deleted)) .ThenInclude(v => v.ProductType) .FirstOrDefaultAsync(p => p.Id == productId && !p.Deleted); } else { --- public async Task<ServiceResponse<List<Product>>> GetProductsAsync() { var response = new ServiceResponse<List<Product>> { Data = await _context.Products .Where(p => p.Visible && !p.Deleted) .Include(p => p.Variants.Where(v => v.Visible && !v.Deleted)) .ToListAsync() --- public async Task<ServiceResponse <List<Product>>> GetProductsByCategory(string categoryUrl) { var response = new ServiceResponse<List<Product>> { Data = await _context.Products .Where(p => p.Category.Url.ToLower().Equals(categoryUrl.ToLower()) && p.Visible && !p.Deleted) .Include(p => p.Variants.Where(v => v.Visible && !v.Deleted)) .ToListAsync() ---
  • 66. Product Variants と Types を Product Service に含める - 2 • • Product は取れている • movies のところの下で Productを クリック し、variants の中に Product が列挙される ように出⼒されていることが確認できる • id 指定してないと ProductType が⼊ってい ないが、1と指定してリロードすると id に対応 した ProductType がちゃんと⼊っているのが ⾒える
  • 68. Product Search 機能の追加と実装 - 1 Server Service ProductService IProductService.cs --- //追加 Task SearchProducts(string searchText, int page); ---
  • 69. Product Search 機能の追加と実装 – 2 Server → Services → ProductService → ProductService.cs --- public async Task SearchProducts(string searchText, int page) { LastSearchText = searchText; var result = await _http .GetFromJsonAsync<ServiceResponse<ProductSearchResult>>($"api/product/search/{searchText}/{page }"); if (result != null && result.Data != null) { Products = result.Data.Products; CurrentPage = result.Data.CurrentPage; PageCount = result.Data.Pages; } if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged?.Invoke(); } ---
  • 70. Product Search 機能の追加と実装 – 3 Server → Services → ProductService → ProductService.cs --- //ついで public async Task<List<string>> GetProductSearchSuggestions(string searchText) { var result = await _http .GetFromJsonAsync<ServiceResponse<List<string>>> ($"api/product/searchsuggestions/{searchText}"); return result.Data; } //上記の通り実装
  • 71. Product Search 機能の追加と実装 – 4 Server → Services → ProductService → ProductService.cs --- public async Task<ServiceResponse<ProductSearchResult>> SearchProducts(string searchText, int page) { var pageResults = 2f; var pageCount = Math.Ceiling((await FindProductsBySearchText(searchText)).Count / pageResults); var products = await _context.Products .Where(p => p.Title.ToLower().Contains(searchText.ToLower()) || p.Description.ToLower().Contains(searchText.ToLower()) && p.Visible && !p.Deleted) .Include(p => p.Variants) .Skip((page - 1) * (int)pageResults) .Take((int)pageResults) .ToListAsync(); var response = new ServiceResponse<ProductSearchResult> { Data = new ProductSearchResult { Products = products, CurrentPage = page, Pages = (int)pageCount } }; return response; } //上記の通り実装 ---
  • 72. Product Search 機能の追加と実装 – 5 • デバッグ実⾏ • https://localhost:(port 番号)/swagger/index.html • ⼩説、等で実⾏。Response Body に1件ずつ全項⽬が表⽰される
  • 73. Search Suggestions の実装 - 1 Server → Services → ProductService → ProductService.cs --- public async Task<ServiceResponse<List<string>>> GetProductSearchSuggestions(string searchText) { var products = await FindProductsBySearchText(searchText); List<string> result = new List<string>(); foreach (var product in products) { if (product.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)) { result.Add(product.Title); } if (product.Description != null) { var punctuation = product.Description.Where(char.IsPunctuation) .Distinct().ToArray(); var words = product.Description.Split() .Select(s => s.Trim(punctuation)); foreach (var word in words) { if (word.Contains(searchText, StringComparison.OrdinalIgnoreCase) && !result.Contains(word)) { result.Add(word); } } } } return new ServiceResponse<List<string>> { Data = result }; } ---
  • 74. Search Suggestions の実装 - 2 Server → Controllers → ProductController.cs --- [HttpGet("searchsuggestions/{searchText}")] public async Task<ActionResult<ServiceResponse <List<Product>>>> GetProductSearchSuggestions(string searchText) { var result = await _productService.GetProductSearchSuggestions(searchText); return Ok(result); } ---
  • 75. Search Suggestions の実装 – 3 Server → Services → ProductService → ProductService.cs --- if (product.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)) { result.Add(product.Title); } if (product.Description != null) { var punctuation = product.Description.Where(char.IsPunctuation) .Distinct().ToArray(); var words = product.Description.Split() .Select(s => s.Trim(punctuation)); foreach (var word in words) { if (word.Contains(searchText, StringComparison.OrdinalIgnoreCase) && !result.Contains(word)) { result.Add(word); } } } --- • まず、句読点を取得し、句読点の助けを借りて、説明⽂のすべての単語を取得 • その後、単純に任意の単語が検索テキストを含むかどうかをチェックし、もしそうなら、結果に追加する
  • 76. Search Suggestions の実装 - 4 • デバッグ実⾏ • https://localhost:(port 番号)/swagger/index.html • ⼩説、等で実⾏。Response Body に出てくるものは Search ボックス内でサジェストされる(ここでは5件)
  • 77. Search Suggestions の実装 – Client 側 1 Client → Services → ProductService → IProductService.cs --- namespace BlazorECommerceApp.Client.Services.ProductService { public interface IProductService { --- Task SearchProducts(string searchText, int page); Task<List<string>> GetProductSearchSuggestions(string searchText); --- } } --- //を追加 • 「商品が⾒つかりませんでした」というようなメッセージを表⽰する • その後ユーザーにいくつかの情報を与えるために、サービスが開始される • リストの⽂字列を送信すると、商品検索候補を取得
  • 78. Search Suggestions の実装 – Client 側 2 Client → Services → ProductService → ProductService.cs --- namespace BlazorECommerceApp.Client.Services.ProductService { public interface IProductService { --- Task SearchProducts(string searchText, int page); Task<List<string>> GetProductSearchSuggestions(string searchText); --- } } --- //を追加 • 「商品が⾒つかりませんでした」というようなメッセージを表⽰する • その後ユーザーにいくつかの情報を与えるために、サービスが開始される • リストの⽂字列を送信すると、商品検索候補を取得
  • 79. Search Suggestions の実装 – Client 側 3-a Client → Services → ProductService → ProductService.cs --- namespace BlazorECommerceApp.Client.Services.ProductService { public class ProductService : IProductService //IProductService.cs インターフェイスをインプリする { private readonly HttpClient _http; public ProductService(HttpClient http) { _http = http; } ---
  • 80. Search Suggestions の実装 – Client 側 3-b Client → Services → ProductService → ProductService.cs --- public List<Product> Products { get; set; } = new List<Product>(); public string Message { get; set; } = "商品をロードしています..."; //メッセージを追加 public int CurrentPage { get; set; } = 1; public int PageCount { get; set; } = 0; public string LastSearchText { get; set; } = string.Empty; public List<Product> AdminProducts { get; set; } public event Action ProductsChanged; public async Task<Product> CreateProduct(Product product) { var result = await _http.PostAsJsonAsync("api/product", product); var newProduct = (await result.Content .ReadFromJsonAsync<ServiceResponse<Product>>()).Data; return newProduct; } ---
  • 81. Search Suggestions の実装 – Client 側 3-c Client → Services → ProductService → ProductService.cs --- public async Task DeleteProduct(Product product) { var result = await _http.DeleteAsync($"api/product/{product.Id}"); } public async Task GetAdminProducts() { var result = await _http .GetFromJsonAsync<ServiceResponse<List<Product>>>("api/product/admin"); AdminProducts = result.Data; CurrentPage = 1; PageCount = 0; if (AdminProducts.Count == 0) Message = "商品がみつかりません。"; }---
  • 82. Search Suggestions の実装 – Client 側 3-e Client → Services → ProductService → ProductService.cs --- public async Task<ServiceResponse<Product>> GetProduct(int productId) { var result = await _http.GetFromJsonAsync<ServiceResponse<Product>>($"api/product/{productId}"); return result; } public async Task GetProducts(string? categoryUrl = null) { var result = categoryUrl == null ? await _http.GetFromJsonAsync<ServiceResponse<List<Product>>> ("api/product/featured") : await _http.GetFromJsonAsync<ServiceResponse<List<Product>>> ($"api/product/category/{categoryUrl}"); if (result != null && result.Data != null) Products = result.Data; CurrentPage = 1; PageCount = 0; if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged.Invoke(); } ---
  • 83. Search Suggestions の実装 – Client 側 3-e Client → Services → ProductService → ProductService.cs --- public async Task<List<string>> GetProductSearchSuggestions(string searchText) { var result = await _http .GetFromJsonAsync<ServiceResponse<List<string>>>($"api/product/searchsuggestions/{searchText}"); return result.Data; } public async Task SearchProducts(string searchText, int page) { LastSearchText = searchText; var result = await _http .GetFromJsonAsync<ServiceResponse<ProductSearchResult>>($"api/product/search/{searchText}/{page}"); if (result != null && result.Data != null) { Products = result.Data.Products; CurrentPage = result.Data.CurrentPage; PageCount = result.Data.Pages; } if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged?.Invoke(); } public async Task<Product> UpdateProduct(Product product) { var result = await _http.PutAsJsonAsync($"api/product", product); var content = await result.Content.ReadFromJsonAsync<ServiceResponse<Product>>(); return content.Data; } } } ---
  • 84. URL を介した検索の実装 Client → Pages → index.razor --- @page "/" @page "/search/{searchText}/{page:int}" @page "/{categoryUrl}" --- @code { [Parameter] public string? CategoryUrl { get; set; } = null; [Parameter] public string? SearchText { get; set; } = null; [Parameter] public int Page { get; set; } = 1; protected override async Task OnParametersSetAsync() { if (SearchText != null) { await ProductService.SearchProducts(SearchText, Page); } else { await ProductService.GetProducts(CategoryUrl); } } } --- //を追加 • 単純にフォワードスラッシュ • 開始ページまたは検索 • 検索テキストまたはカテゴリ URL • 必要なのはこの新しいパラメータ、検索テキスト • コードブロック全体の実装
  • 85. Seach コンポーネント作成 - 1 Client → Shared → Search.razor --- //先に@code部分作成 @Inject NavigationManager NavigationManager @inject IProductService ProductService --- @code { private string searchText = string.Empty; private List<string> suggestions = new List<string>(); protected ElementReference searchInput; protected override async Task OnAfterRenderAsync (bool firstRender) { if (firstRender) { await searchInput.FocusAsync(); } } public void SearchProducts() { NavigationManager.NavigateTo($"search/{searchText}/1"); } --- • すでにあるものを注⼊する • Product Service • いくつかの呼び出し • Navigation Manager • ユーザーを特定のページに誘導したい • NavigateTo メソッドを使⽤
  • 86. Seach コンポーネント作成 - 2 Client → Shared → Search.razor --- //先に@code部分作成 @Inject NavigationManager NavigationManager @inject IProductService ProductService --- public async Task HandleSearch(KeyboardEventArgs args) { if (args.Key == null || args.Key.Equals("Enter")) { SearchProducts(); } else if (searchText.Length > 1) { suggestions = await ProductService. GetProductSearchSuggestions(searchText); } } • すでにあるものを注⼊する • Product Service • いくつかの呼び出し • Navigation Manager • ユーザーを特定のページに誘導したい • NavigateTo メソッドを使⽤
  • 87. Seach コンポーネント作成 - 3 Client → Shared → Search.razor --- //最後に HTML 部分 @Inject NavigationManager NavigationManager @inject IProductService ProductService --- <div class="input-group"> <input @bind-value="searchText" @bind-value:event="oninput" type="search" list="products" @onkeyup="HandleSearch" class="form-control" placeholder="検索..." @ref="searchInput" /> <datalist id="products"> @foreach (var suggestion in suggestions) { <option>@suggestion</option> } </datalist> <div class="input-group-append"> <button class="btn btn-primary" @onclick="SearchProducts"> <span class="oi oi-magnifying-glass"></span> </button> </div> </div> • すでにあるものを注⼊する • Product Service • いくつかの呼び出し • Navigation Manager • ユーザーを特定のページに誘導したい • NavigateTo メソッドを使⽤
  • 88. Seach コンポーネント作成 - 4 Client → Shared → MainLayout.razor --- MainLayout.razor @inherits LayoutComponentBase <div class="page"> <div class="sidebar"> <NavMenu /> </div> <main> <div class="top-row px-4"> <Search /> //ここに Search コンポーネントを⼊れる </div> <article class="content px-4"> @Body </article> </main> </div> --- • すでにあるものを注⼊する • Product Service • いくつかの呼び出し • Navigation Manager • ユーザーを特定のページに誘導したい • NavigateTo メソッドを使⽤
  • 90. レイアウトの変更 - 1 Client → Shared → MainLayout.razor --- <CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(ShopLayout)"> <NotAuthorized> <h3>Whoops! You're not allowed to see this page.</h3> <h5>Please <a href="login">login</a> or <a href="register">register </a> for a new account.</h5> </NotAuthorized> </AuthorizeRouteView> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <PageTitle>Not found</PageTitle> <LayoutView Layout="@typeof(ShopLayout)"> <p role="alert"> Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState> --- • MainLayout • → App.razor で定義 • MainLayout.razor をコピーすると css もコピーされる • ShopLayout.razor と • ShopLayout.razor.css を作成 • App.razor を編集 • ShopLayout をパラメーターに設定
  • 91. レイアウトの変更 - 2 Client → Shared → NavMenu.razor --- @inject ICategoryService CategoryService <div class="top-row ps-3 navbar navbar-dark"> <div class="container-fluid"> <a class="navbar-brand" href="">BlazorECommerceApp</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"> Home </NavLink> </div> @foreach (var category in CategoryService.Categories) { <div class="nav-item px-3"> <NavLink class="nav-link" href="@category.Url"> @category.Name </NavLink> </div> } </nav> </div> • ShopNavMenu.razor • ShopNavMenu.razor.css • 共に編集し最終形にする • これによって上のナビゲーションメニュー ボタンができ、左のメニューが消える • css は⾯倒だがこの機会にある程度 詳しくなると、他のプラットフォームでも 使いこなせる • css に慣れるためにも Hot Reload を活⽤してください︕楽しくなります
  • 92. レイアウトの変更 - 3 Client → Shared → MainLayout.razor --- @inherits LayoutComponentBase <div class="page"> <div class="sidebar"> <NavMenu /> </div> <main> <div class="top-row px-4"> <Search /> </div> <article class="content px-4"> @Body </article> </main> </div> --- • ShopNavMenu.razor • ShopNavMenu.razor.css • 共に編集し最終形にする • これによって上のナビゲーションメニュー ボタンができ、左のメニューが消える • css は⾯倒だがこの機会にある程度 詳しくなると、他のプラットフォームでも 使いこなせる • css に慣れるためにも Hot Reload を活⽤してください︕楽しくなります
  • 93. レイアウトの変更 - 4 Client → Shared → ShopNavMenu.razor --- @inject ICategoryService CategoryService @implements Idisposable <div class="top-row ps-3 navbar navbar-dark navbar-toggler-wrapper"> <div class="container-fluid"> <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-nav"> <div class="nav-item px-2"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> Home </NavLink> </div> @foreach (var category in CategoryService.Categories) { <div class="nav-item px-2"> <NavLink class="nav-link" href="@category.Url"> @category.Name </NavLink> </div> } </nav> </div> --- • MainLayout • → App.razor で定義 • MainLayout.razor をコピーすると css もコピーされる • ShopLayout.razor と • ShopLayout.razor.css を作成 • App.razor を編集 • ShopLayout をパラメーターに設定
  • 94. レイアウトの変更 - 5 Client → Shared → ShopNavMenu.razor --- @inject ICategoryService CategoryService @implements IDisposable --- @code { private bool collapseNavMenu = true; private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } protected override async Task OnInitializedAsync() { await CategoryService.GetCategories(); CategoryService.OnChange += StateHasChanged; } public void Dispose() { CategoryService.OnChange -= StateHasChanged; } } --- • MainLayout • → App.razor で定義 • MainLayout.razor をコピーすると css もコピーされる • ShopLayout.razor と • ShopLayout.razor.css を作成 • App.razor を編集 • ShopLayout をパラメーターに設定
  • 95. HomeButton.razor - 1 Client → Shared → HomuButton.razor --- @inject NavigationManager NavigationManager <button @onclick="GoToHome" class="btn btn-outline-primary home-button"> マイショップ </button> @code { private void GoToHome() { NavigationManager.NavigateTo(""); } } --- • Home Button の配置 • razor の作成
  • 96. HomeButton.razor - 2 Client → Shared → HomuButton.razor.css --- HomeButton.razor.css .home-button { white-space: nowrap; margin-right: 10px; transform: rotate(-5deg); } --- • Home Button の配置 • css の追加
  • 97. HomeButton.razor - 3 Client → Shared → ShopLayout.razor --- @inherits LayoutComponentBase <div class="page"> <main> <div class="top-row px-2"> //これを追加 <HomeButton /> <Search /> </div> <div class="nav-menu"> <ShopNavMenu /> </div> <article class="content px-2"> @Body </article> </main> </div> --- • Home Button の配置 • ShopLayout.razor の修正
  • 98. 注⽬の商品 - 1 Shared → Product.cs --- //新しいプロパティを⼊れる --- namespace BlazorECommerceApp.Shared { public class Product { public int Id { get; set; } [Required] public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string ImageUrl { get; set; } = string.Empty; public Category? Category { get; set; } public int CategoryId { get; set; } //これを⼊れる public bool Featured { get; set; } = false; public List<ProductVariant> Variants { get; set; } = new List<ProductVariant>(); --- } } --- • Featured Products として3つを top ページにリコメンドして表⽰する
  • 99. 注⽬の商品 - 2 DataContext.cs --- //新しいプロパティを追加する(3つのみ) --- Seed を変更する(5回⽬の Migration) --- new Product { Id = 5, CategoryId = 2, Title = "Back to the Future", Description = "「バック・トゥ・ザ・フューチャー」は、ロバート・ゼメキス監督による1985年のアメ リカのSF映画である。ゼメキスとボブ・ゲイルの脚本で、マイケル・J・フォックス、クリスト ファー・ロイド、リア・トンプソン、クリスピン・グローバー、トーマス・F・ウィルソンらが出演し ています。1985年を舞台に、マーティ・マクフライ(フォックス)は、友⼈の⾵変わりな科 学者、エメット博⼠(ロイド)が作ったタイムトラベル可能なデロリアンに乗って、偶然に も1955年に戻されることになります。ブラウン(ロイド)。過去に閉じ込められたマーティは、 うっかり未来の両親の出会いを邪魔してしまい、⾃分の存在意義を脅かされてしまう。", ImageUrl = "https://upload.wikimedia.org/wikipedia/en/d/d2/ Back_to_the_Future.jpg", //ここを追加。3つのみ Featured = true }, --- • Featured Products として3つを top ページにリコメンドして表⽰する
  • 100. 注⽬の商品 - 3 • Package Manager Console で Migration 実施 cd ./BlazorECommerceApp cd ./Server dot net ef Migrations add FeaturedProducts
  • 101. 注⽬の商品 - 4 • Migrations フォルダ → FeaturedProducts を開いて内容 を確認 • カラムが追加されることを確認 • フラグが⽴つプロダクトの Id を確認 using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace BlazorEcommerce.Server.Migrations { public partial class FeaturedProducts : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn<bool>( name: "Featured", table: "Products", type: "bit", nullable: false, defaultValue: false); migrationBuilder.UpdateData( table: "Products", keyColumn: "Id", keyValue: 1, column: "Featured", value: true); migrationBuilder.UpdateData( table: "Products", keyColumn: "Id", keyValue: 5, column: "Featured", value: true); migrationBuilder.UpdateData( table: "Products", keyColumn: "Id", keyValue: 9, column: "Featured", value: true); } ---
  • 102. 注⽬の商品 - 5 • Package Manager Console で DB 作成 • Azure Data Studio で確認 • dbo.Products • Featured 列が増えている • フラグが⽴っている dotnet ef database update
  • 103. 注⽬の商品のローディング - 1 Server → Services → ProductService → iProductService.cs --- namespace BlazorEcommerceApp.Server.Services.ProductService { public interface IProductService { --- Task<ServiceResponse<ProductSearchResult>> SearchProducts (string searchText, int page); Task<ServiceResponse<List<string>>> GetProductSearchSuggestions(string searchText); //これを追加する Task<ServiceResponse<List<Product>>> GetFeaturedProducts(); --- } } ---
  • 104. 注⽬の商品のローディング - 2 Server → Services → ProductService → ProductService.cs --- //インターフェイスを実装 public async Task<ServiceResponse<List<Product>>> GetFeaturedProducts() { var response = new ServiceResponse<List<Product>> { Data = await _context.Products .Where(p => p.Featured) .Include(p => p.Variants) .ToListAsync() }; return response; } ---
  • 105. 注⽬の商品のローディング – 3 Server → Controllers → ProductController.cs //下記を追加 --- [HttpGet("featured")] public async Task<ActionResult<ServiceResponse<List<Product>>>> GetFeaturedProducts() { var result = await _productService.GetFeaturedProducts(); return Ok(result); } ---
  • 106. 注⽬の商品のローディング – 4 Client → Shared → FeaturedProducts.razor.cs --- @inject IProductService ProductService @implements Idisposable --- @code { protected override void OnInitialized() { ProductService.ProductsChanged += StateHasChanged; } public void Dispose() { ProductService.ProductsChanged -= StateHasChanged; } } --- • 新しいコンポーネントを作ってフィーチャード プロダクトを表⽰する • 先に @code 部分から
  • 107. 注⽬の商品のローディング – 5 Client → Shared → FeaturedProducts.razor.cs --- @inject IProductService ProductService @implements Idisposable --- <center><h2>今⽇の⼈気商品</h2></center> @if (ProductService.Products == null || ProductService.Products.Count == 0) { <span>@ProductService.Message</span> } else { <div class="container"> @foreach (var product in ProductService.Products) { @if (product.Featured) { <div class="featured-product"> <div> <a href="product/@product.Id"> <img src="@product.ImageUrl"> </a> </div> <h4><a href="product/@product.Id">@product.Title</a></h4> @if (product.Variants != null && product.Variants.Count > 0) { <h5 class="price"> $@product.Variants[0].Price </h5> } </div> } } </div> }--- • 新しいコンポーネントを作ってフィーチャード プロダクトを表⽰する • @code に続いて View 部分
  • 108. 注⽬の商品のローディング – 6 Client → Shared → FeaturedProducts.razor.cs → FeaturedProducts.razor.css --- .container { display: flex; flex-direction: row; overflow-x: auto; justify-content: center; } img { max-width: 200px; max-height: 200px; border-radius: 6px; transition: transform .2s; margin-bottom: 10px; } img:hover { transform: scale(1.1) rotate(5deg); } //ここを追加 .featured-product { margin: 10px; text-align: center; padding: 10px; border: 1px solid lightgray; border-radius: 10px; max-width: 200px; } @media (max-width: 1023.98px) { .container { justify-content: flex-start; } } • FeaturedProducts.razor.css 作成 • Chrome Dev Tool のモバイルビューなど にも切り替えながら検証する • Hot Reload は css にこそ有効
  • 109. 注⽬の商品のローディング – 7 Client → Pages → Index.razor --- @page "/" @page "/search/{searchText}/{page:int}" @page "/{categoryUrl}" @inject IProductService ProductService --- <PageTitle>マイショップ</PageTitle> @if (SearchText == null && CategoryUrl == null) { <FeaturedProducts /> //ここを追加 } else { <ProductList /> } @code { [Parameter] public string? CategoryUrl { get; set; } = null; [Parameter] public string? SearchText { get; set; } = null; [Parameter] public int Page { get; set; } = 1; protected override async Task OnParametersSetAsync() { if (SearchText != null) { await ProductService.SearchProducts(SearchText, Page); } else { await ProductService.GetProducts(CategoryUrl); //ここで Go to Implementation } } • Index.razor を修正
  • 110. 注⽬の商品のローディング – 8 Client → Services → ProductServices → ProductService.cs - GetProducts --- --- public async Task GetProducts(string? categoryUrl = null) { var result = categoryUrl == null ? await _http.GetFromJsonAsync<ServiceResponse <List<Product>>> ("api/product/featured") : await _http.GetFromJsonAsync<ServiceResponse <List<Product>>> ($"api/product/category/{categoryUrl}"); if (result != null && result.Data != null) Products = result.Data; CurrentPage = 1; PageCount = 0; if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged.Invoke(); } --- } • Client.Services.ProductServices ProductService.cs の GetProducts を修正
  • 111. 検索結果の ページネーション - 1 Shared → Products → ProductSerchResult.cs --- using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorEcommerceApp.Shared { public class ProductSearchResult { public List<Product> Products { get; set; } = new List<Product>(); public int Pages { get; set; } public int CurrentPage { get; set; } } } --- • データベースに多くの製品が登録されている 場合、分割して表⽰させたい • 1ページに2つの商品を表⽰し、2ページ⽬、 3ページ⽬......と進む • タイトルと説明⽂だけ表⽰させる • 商品からデータ転送オブジェクトを作成し、 DTO は商品タイトルと説明⽂だけを返す • ProductSerchResult.cs という DTO オブジェクト • この DTO で、製品のリストを取得し、ペー ジ数を取得し、情報として現在のページを 取得
  • 112. 検索結果の ページネーション - 2 Server → Services → ProductServices → IProductService.cs --- Server Services ProductServices IProductService.cs namespace BlazorEcommerceApp.Server.Services.ProductService { public interface IProductService { --- //ここを追加 Task<ServiceResponse<ProductSearchResult>> SearchProducts(string searchText, int page); --- } }--- • ProductSerchResult.cs という DTO オブジェクト • この DTO で、製品のリストを取得し、ペー ジ数を取得し、情報として現在のページを 取得
  • 113. 検索結果の ページネーション - 3 Server → Services → ProductServices → ProductService.cs --- public async Task<ServiceResponse<ProductSearchResult>> SearchProducts(string searchText, int page) //リターン値を ProductSearchResult にしてパラメーターに page も追加 { //下記の両者を定義しておく var pageResults = 2f; var pageCount = Math.Ceiling((await FindProductsBySearchText(searchText)) .Count / pageResults); var products = await _context.Products .Where(p => p.Title.ToLower().Contains(searchText.ToLower()) ||p.Description.ToLower().Contains(searchText.ToLower()) && p.Visible && !p.Deleted) .Include(p => p.Variants) .Skip((page - 1) * (int)pageResults) .Take((int)pageResults) .ToListAsync(); //ここもProductSearchResultに変更 var response = new ServiceResponse<ProductSearchResult> { Data = new ProductSearchResult { Products = products, CurrentPage = page, Pages = (int)pageCount } }; return response; } --- • Server 上のページネーション
  • 114. 検索結果の ページネーション - 4 Server → Controllers → ProductContoroller.cs --- //page 追加 [HttpGet("search/{searchText}/{page}")] public async Task<ActionResult<ServiceResponse<ProductSearchResult>>> //page 追加、デフォルト値=1 SearchProducts(string searchText, int page = 1) { var result = await _productService.SearchProducts(searchText, page); return Ok(result); } ------ • コントローラにも変更を加える必要あり • Product コントローラの Search メソッドに、 もうひとつパラメータを追加(Page) • デフォルトで1に設定 • アプリケーションを起動 • Swagger ページを開く • 検索テキストを⼊⼒してテスト
  • 115. 検索結果の ページネーション - 5 Client → Services → ProductService → IProductService.cs --- namespace BlazorEcommerceApp.Client.Services.ProductService { public interface IProductService { -- Task<ServiceResponse<Product>> GetProduct(int productId); Task SearchProducts(string searchText, int page); --- --- • クライアントの変更を実装 Client → Services → ProductService → IProductService.cs --- namespace BlazorEcommerceApp.Client.Services.ProductService { --- public List<Product> Products { get; set; } = new List<Product>(); //下記3⾏を追加 public string Message { get; set; } = "商品をロードしています..."; public int CurrentPage { get; set; } = 1; public int PageCount { get; set; } = 0; ---
  • 116. 検索結果の ページネーション - 6 Client → Services → ProductService → ProductService.cs --- public async Task GetProducts(string? categoryUrl = null) { --- CurrentPage = 1; PageCount = 0; if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged.Invoke();} --- • Client 側 GetProducts を修正 • Client 側 SearchProducts を修正 Client → Services → ProductService → ProductService.cs --- public async Task SearchProducts(string searchText, int page) //page パラメータを追加 { LastSearchText = searchText; var result = await _http .GetFromJsonAsync<ServiceResponse<ProductSearchResult>> //List<Products> をProductSearchResult に変更 ($"api/product/search/{searchText}/{page}"); //page 部分を追加 if (result != null && result.Data != null) { Products = result.Data.Products; CurrentPage = result.Data.CurrentPage; PageCount = result.Data.Pages; } if (Products.Count == 0) Message = "商品がみつかりません。"; ProductsChanged?.Invoke(); } ---
  • 117. 検索結果の ページネーション - 7 Client → Shared → Search.razor --- public void SearchProducts() { --- //1 をデフォルト値として追加 NavigationManager.NavigateTo($"search/{searchText}/1"); --- • ページネーションのコンポーネントへの追加 • Search.razor、 Index.razor を修正 Client → Pages → Index.razor --- //int を追加 @page "/search/{searchText}/{page:int}" --- // パラメータ追加 [Parameter] public int Page { get; set; } = 1; --- //修正 --- protected override async Task OnParametersSetAsync() { if (SearchText != null) { //page 追加 await ProductService.SearchProducts(SearchText, Page); } --- ---
  • 118. 検索結果の ページネーション - 8 Client → Shared → Product.razor --- //下記を追加 for (var i = 1; i <= ProductService.PageCount; i++) { <a class="btn @(i == ProductService.CurrentPage ? "btn-info" : "btn-outline-info") page-selection" href="/search/@ProductService.LastSearchText/@i">@i</a> } --- • ボタンの追加 • これでページネーションは完成 Client → Shared → Product.razor.css --- //追加 --- .page-selection { margin-right: 15px; margin-bottom: 30px; } //追加 --- .page-selection { margin-right: 15px; margin-bottom: 30px; } ---
  • 120. ショッピングカート - 1 Client → Program.cs --- //下記を追加 --- using Blazored.LocalStorage; --- builder.Services.AddBlazoredLocalStorage(); --- • ローカルストレージを使う • Client プロジェクトに NuGet パッケージ 追加 • Blazer Local Storage Client → Imports.razor --- //追加 --- @using Blazored.LocalStorage --- ---
  • 121. ショッピングカート - 2 Client → Shared → CartCounter.razor --- @inject ICartService CartService @inject ISyncLocalStorageService LocalStorage @implements IDisposable <a href="cart" class="btn btn-info"> <i class="oi oi-cart"></i> <span class="badge">@GetCartItemsCount()</span> </a> @code { private int GetCartItemsCount() { var count = LocalStorage.GetItem<int>("cartItemsCount"); return count; } protected override void OnInitialized() { CartService.OnChange += StateHasChanged; } public void Dispose() { CartService.OnChange -= StateHasChanged; } } --- • カートを追加 • デバッグ実⾏して画⾯のカート部分を確認
  • 122. ショッピングカート - 3 Client → CartItem.cs --- using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorEcommerceApp.Shared { public class CartItem { public int ProductId { get; set; } public int ProductTypeId { get; set; } } }--- • カートを追加 • デバッグ実⾏して画⾯のカート部分を確認
  • 123. クライアント側の CartService 実装 - 1 Client → Program.cs --- builder.Services.AddScoped<ICategoryService, CategoryService>(); --- • Client → Service → ICartService.cs を追加 • Client → Service → CartService.cs を追加 Client → _Imports.razor --- @using BlazorEcommerceApp.Client.Services.CartService ---
  • 124. クライアント側の CartService 実装 - 2 Client → Client → Service → ICartService.cs --- namespace BlazorEcommerceApp.Client.Services.CartService { public interface ICartService { event Action OnChange; Task AddToCart(CartItem cartItem); Task<List<CartProductResponse>> GetCartProducts(); Task RemoveProductFromCart(int productId, int productTypeId); Task UpdateQuantity(CartProductResponse product); Task StoreCartItems(bool emptyLocalCart); Task GetCartItemsCount(); } } --- • Client → Service → ICartService.cs を追加
  • 125. クライアント側の CartService 実装 - 3 Client → Client → Service → CartService.cs --- using Blazored.LocalStorage; namespace BlazorEcommerceApp.Client.Services.CartService { public class CartService : ICartService { private readonly ILocalStorageService _localStorage; private readonly HttpClient _http; private readonly IAuthService _authService; public CartService(ILocalStorageService localStorage, HttpClient http, IAuthService authService) { _localStorage = localStorage; _http = http; _authService = authService; } public event Action OnChange; public async Task AddToCart(CartItem cartItem) { if (await _authService.IsUserAuthenticated()) { await _http.PostAsJsonAsync("api/cart/add", cartItem); } else { • Client → Service → CartService.cs を追加 var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart"); if (cart == null) { cart = new List<CartItem>(); } var sameItem = cart.Find(x => x.ProductId == cartItem.ProductId && x.ProductTypeId == cartItem.ProductTypeId); if (sameItem == null) { cart.Add(cartItem); } else { sameItem.Quantity += cartItem.Quantity; } await _localStorage.SetItemAsync("cart", cart); } await GetCartItemsCount(); } public async Task GetCartItemsCount() { if (await _authService.IsUserAuthenticated()) { var result = await _http.GetFromJsonAsync<ServiceResponse<int>>("api/cart/count"); var count = result.Data; await _localStorage.SetItemAsync<int> ("cartItemsCount", count); } else { var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart"); await _localStorage.SetItemAsync<int> ("cartItemsCount", cart != null ? cart.Count : 0); } OnChange.Invoke(); } ---
  • 126. クライアント側の CartService 実装 - 4 • ProductDetail に Add to Cart ボタン を追加 Client Pages ProductDetails.razor // 先に View 側に追加 --- @inject ICartService CartService --- --- <button class="btn btn-primary" @onclick="AddToCart"> <i class="oi oi-cart"></i>&nbsp;&nbsp;&nbsp;Add to Cart </button> --- --- // 次いで@code側に追加 --- private async Task AddToCart() { var productVariant = GetSelectedVariant(); var cartItem = new CartItem { ProductId = productVariant.ProductId, ProductTypeId = productVariant.ProductTypeId }; await CartService.AddToCart(cartItem); } ---
  • 127. クライアント側の CartService 実装 - 5 • デバッグ実⾏ • Chrome Developer Tools 起動 • アイテムを⼀つ選択 • アプリケーションタブに切り替え • ローカルストレージ表⽰ • 当該アイテムを Add to Cart で追加 • 値を確認する
  • 128. クライアント側の CartService 実装 - 6 • CartCounter 数字のインクリメント • @Code 部分を先に実装 Client → Shared → CartCounter.razor // 先に @code 部分を実装 --- @inject ICartService CartService @inject ISyncLocalStorageService LocalStorage @implements IDisposable --- @code { private int GetCartItemsCount() { var count = LocalStorage.GetItem<int>("cartItemsCount"); return count; } protected override void OnInitialized() { CartService.OnChange += StateHasChanged; } public void Dispose() { CartService.OnChange -= StateHasChanged; } } ---
  • 129. クライアント側の CartService 実装 - 7 • CartCounter 数字のインクリメント • 続いて view 部分を実装 • CartService.cs → OnChange.Invoke() を追加 Client → Shared → CartCounter.razor // View 部分を実装 --- <a href="cart" class="btn btn-info"> <i class="oi oi-cart"></i> <span class="badge">@GetCartItemsCount()</span> </a> --- Client → Service → CartService → CartService.cs --- OnChange.Invoke(); ---
  • 130. CartItem のサーバー側 Products への送付- 1 • Shared の CartProductResponse.cs Shared → CartProductResponse.cs --- using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorEcommerceApp.Shared { public class CartProductResponse { public int ProductId { get; set; } public string Title { get; set; } = string.Empty; public int ProductTypeId { get; set; } public string ProductType { get; set; } = string.Empty; public string ImageUrl { get; set; } = string.Empty; public decimal Price { get; set; } public int Quantity { get; set; } } } ---
  • 131. CartItem のサーバー側 Products への送付- 2 • Server → Services → CartService フォルダ作成 • Server → Services → ICartService • Server → Services → CartService を追加 Server → Program.cs //下記を追加 --- builder.Services.AddScoped<ICartService, CartService>(); --- global using BlazorEcommerceApp.Server.Services.CartService; ---
  • 132. CartItem のサーバー側 Products への送付- 3 • Server → Services → CartServiceProgram.cs 実装 Server → Services → CartServiceProgram.cs --- using System.Security.Claims; namespace BlazorEcommerceApp.Server.Services.CartService { public class CartService : ICartService { private readonly DataContext _context; public CartService(DataContext context, IAuthService authService) { _context = context; } public async Task<ServiceResponse<List<CartProductResponse>>> GetCartProducts(List<CartItem> cartItems) { var result = new ServiceResponse<List<CartProductResponse>> { Data = new List<CartProductResponse>() }; foreach (var item in cartItems) { var product = await _context.Products .Where(p => p.Id == item.ProductId) .FirstOrDefaultAsync(); if (product == null) { continue; } var productVariant = await _context.ProductVariants .Where(v => v.ProductId == item.ProductId && v.ProductTypeId == item.ProductTypeId) .Include(v => v.ProductType) .FirstOrDefaultAsync(); if (productVariant == null) { continue; } var cartProduct = new CartProductResponse { ProductId = product.Id, Title = product.Title, ImageUrl = product.ImageUrl, Price = productVariant.Price, ProductType = productVariant.ProductType.Name, ProductTypeId = productVariant.ProductTypeId, Quantity = item.Quantity }; result.Data.Add(cartProduct); } return result; } ---
  • 133. CartItem のサーバー側 Products への送付- 4 • Server → Controller→ CartController.cs 実装 Server → Controller → CartController.cs // using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; namespace BlazorEcommerceApp.Server.Controllers { [Route("api/[controller]")] [ApiController] public class CartController : ControllerBase { private readonly ICartService _cartService; public CartController(ICartService cartService) { _cartService = cartService; } [HttpPost("products")] public async Task<ActionResult<ServiceResponse<List<CartProductResponse>>>> GetCartProducts(List<CartItem> cartItems) { var result = await _cartService.GetCartProducts(cartItems); return Ok(result); } --- ---
  • 134. クライアント側の CartProduct 取得 • Client → Services → ICartService.cs 実装 • Client → Services → CartService.cs 実装 Client → Services → ICartService.cs --- namespace BlazorEcommerceApp.Client.Services.CartService { public interface ICartService { event Action OnChange; Task AddToCart(CartItem cartItem); //ここを追加 Task<List<CartProductResponse>> GetCartProducts(); Task RemoveProductFromCart(int productId, int productTypeId); } } --- Client → Services → CartService.cs --- // このメソッドで取得する public async Task<List<CartProductResponse>> GetCartProducts() { if (await _authService.IsUserAuthenticated()) { var response = await _http.GetFromJsonAsync <ServiceResponse<List<CartProductResponse>>> ("api/cart"); return response.Data; } else { var cartItems = await _localStorage.GetItemAsync<List<CartItem>>("cart"); if (cartItems == null) return new List<CartProductResponse>(); var response = await _http.PostAsJsonAsync ("api/cart/products", cartItems); var cartProducts = await response.Content.ReadFromJsonAsync <ServiceResponse<List<CartProductResponse>>>(); return cartProducts.Data; } } ---
  • 135. Cart ページの実装 - 1 • Client → Pages → Cart.razor • 先に @code 部分を実装する Client → Pages → Cart.razor --- // 先に@codeを実装する @page "/cart" @inject ICartService CartService @inject IOrderService OrderService @inject IAuthService AuthService @inject NavigationManager NavigationManager --- @code { List<CartProductResponse> cartProducts = null; string message = "Loading cart..."; bool isAuthenticated = false; protected override async Task OnInitializedAsync() { isAuthenticated = await AuthService.IsUserAuthenticated(); await LoadCart(); } private async Task RemoveProductFromCart(int productId, int productTypeId) { await CartService.RemoveProductFromCart(productId, productTypeId); await LoadCart(); } private async Task LoadCart() { await CartService.GetCartItemsCount(); cartProducts = await CartService.GetCartProducts(); if (cartProducts == null || cartProducts.Count == 0) { message = "Your cart is empty."; } } private async Task UpdateQuantity(ChangeEventArgs e, CartProductResponse product) { product.Quantity = int.Parse(e.Value.ToString()); if (product.Quantity < 1) product.Quantity = 1; await CartService.UpdateQuantity(product); } private async Task PlaceOrder() { string url = await OrderService.PlaceOrder(); NavigationManager.NavigateTo(url); } } ---
  • 136. Cart ページの実装 - 2 • Client → Pages → Cart.razor • 続いて View 部分を実装する Client → Pages → Cart.razor --- <PageTitle>Shopping Cart</PageTitle> <h3>ショッピングカート</h3> @if (cartProducts == null || cartProducts.Count == 0) { <span>@message</span> } else { <div> @foreach (var product in cartProducts) { <div class="container"> <div class="image-wrapper"> <img src="@product.ImageUrl" class="image" /> </div> <div class="name"> <h5><a href="/product/@product.ProductId">@product.Title</a></h5> <span>@product.ProductType</span><br /> <input type="number" value="@product.Quantity" @onchange="@((ChangeEventArgs e) => UpdateQuantity(e, product))" class="form-control input-quantity" min="1" /> <button class="btn-delete" @onclick="@(() => RemoveProductFromCart (product.ProductId, product.ProductTypeId))"> Delete </button> </div> <div class="cart-product-price"> $@(product.Price * product.Quantity)</div> </div> } <div class="cart-product-price"> Total (@cartProducts.Count): $@cartProducts.Sum (product => @product.Price * product.Quantity) </div> </div> @if (isAuthenticated) { <div> <h5>Delivery Address</h5> <AddressForm /> </div> } <button @onclick="PlaceOrder" class="btn alert-success float-end mt-1">Checkout</button> } ---
  • 137. Cart ページの実装 - 3 • Client → Pages → Cart.razor.css 実装 Client → Pages → Cart.razor.css --- .container { display: flex; padding: 6px; } .image-wrapper { width: 150px; text-align: center; } .image { max-height: 150px; max-width: 150px; padding: 6px; } .name { flex-grow: 1; padding: 6px; } .cart-product-price { font-weight: 600; text-align: right; } .btn-delete { background: none; border: none; padding: 0px; color: red; font-size: 12px; } .btn-delete:hover { text-decoration: underline; } .input-quantity { width: 70px; } ---
  • 138. Cart から Item を削除 - 1 • Client → Services → CartService ICartService.cs 追加 • Client → Services → CartService ICartService.cs 修正 • RemoveProductFromCart 追加 Client → Services → CartService → ICartService.cs --- //追加 Task RemoveProductFromCart(int productId, int productTypeId); --- Client → Services → CartService → CartService.cs --- //修正 RemoveProductFromCart 追加 public async Task RemoveProductFromCart(int productId, int productTypeId) { if (await _authService.IsUserAuthenticated()) { await _http.DeleteAsync($"api/cart/{productId}/{productTypeId}"); } else { var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart"); if (cart == null) { return; } var cartItem = cart.Find(x => x.ProductId == productId && x.ProductTypeId == productTypeId); if (cartItem != null) { cart.Remove(cartItem); await _localStorage.SetItemAsync("cart", cart); } } } ---
  • 139. Cart から Item を削除 - 2 • Client → Pages → Cart.razor • View 部分修正 • @Code 部分修正 Client → Pages → Cart.razor --- //View 部分修正 <button class="btn-delete" @onclick="@(() => RemoveProductFromCart(product.ProductId, product.ProductTypeId))"> Delete </button>--- Client → Pages → Cart.razor --- // @Code 部分修正(追加) private async Task RemoveProductFromCart (int productId, int productTypeId) { await CartService.RemoveProductFromCart(productId, productTypeId); await LoadCart(); } --- private async Task LoadCart() { await CartService.GetCartItemsCount(); cartProducts = await CartService.GetCartProducts(); if (cartProducts == null || cartProducts.Count == 0) { message = "Your cart is empty."; } } ---
  • 140. Cart から Item を削除 - 3 • Client → Pages → Cart.razor.css への追加 Client → Pages → Cart.razor.css --- // 追加 --- .btn-delete { background: none; border: none; padding: 0px; color: red; font-size: 12px; } --- ---
  • 141. Cart から Item を削除 - 4 • デバッグ実⾏ • カートを表⽰ • Chrome Dev Tools アプリケーションタブ に移動 • ローカルストレージを表⽰ • delete ボタン押下してテスト
  • 142. Cart モデルに数量を追加 - 1 • Shared → CartProductResponse.cs を修正 Shared → CartProductResponse.cs --- // 追加 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorEcommerceApp.Shared { public class CartProductResponse { public int ProductId { get; set; } public string Title { get; set; } = string.Empty; public int ProductTypeId { get; set; } public string ProductType { get; set; } = string.Empty; public string ImageUrl { get; set; } = string.Empty; public decimal Price { get; set; } public int Quantity { get; set; } } } ---
  • 143. Cart モデルに数量を追加 - 2a • Server → Services → CartService → CartService.cs を修正 Server → Services → CartService → CartService.cs --- // 追加 --- var cartProduct = new CartProductResponse { ProductId = product.Id, Title = product.Title, ImageUrl = product.ImageUrl, Price = productVariant.Price, ProductType = productVariant.ProductType.Name, ProductTypeId = productVariant.ProductTypeId, Quantity = item.Quantity }; ---
  • 144. Cart モデルに数量を追加 - 2b • Server → Services → CartService → CartService.cs を修正 Server → Services → CartService → CartService.cs --- public async Task<ServiceResponse<bool>> AddToCart(CartItem cartItem) { cartItem.UserId = _authService.GetUserId(); var sameItem = await _context.CartItems .FirstOrDefaultAsync(ci => ci.ProductId == cartItem.ProductId && ci.ProductTypeId == cartItem.ProductTypeId && ci.UserId == cartItem.UserId); if (sameItem == null) { _context.CartItems.Add(cartItem); } else { //この箇所を追加する sameItem.Quantity += cartItem.Quantity; } await _context.SaveChangesAsync(); return new ServiceResponse<bool> { Data = true }; } ---
  • 145. Cart モデルに数量を追加 - 3a • Client → Services → CartService → ICartService.cs を修正 Client → Services → CartService → ICartService.cs --- namespace BlazorEcommerceApp.Client.Services.CartService { public interface ICartService { --- Task UpdateQuantity(CartProductResponse product); Task StoreCartItems(bool emptyLocalCart); //ここを追加 Task GetCartItemsCount(); } } ---
  • 146. Cart モデルに数量を追加 - 3b • Client → Services → CartService → CartService.cs を修正 Client → Services → CartService → CartService.cs --- public async Task UpdateQuantity(CartProductResponse product) { if (await _authService.IsUserAuthenticated()) { var request = new CartItem { ProductId = product.ProductId, Quantity = product.Quantity, ProductTypeId = product.ProductTypeId }; await _http.PutAsJsonAsync("api/cart/update-quantity", request); } else { var cart = await _localStorage.GetItemAsync<List<CartItem>>("cart"); if (cart == null) { return; } var cartItem = cart.Find(x => x.ProductId == product.ProductId && x.ProductTypeId == product.ProductTypeId); if (cartItem != null) { cartItem.Quantity = product.Quantity; await _localStorage.SetItemAsync("cart", cart); } } } ---
  • 147. Cart モデルに数量を追加 - 4a • 数値⼊⼒フィールドで数量を更新する Client → Pages → Cart.razor //追加 --- <div class="name"> <h5><a href="/product/@product.ProductId">@product.Title</a> </h5> <span>@product.ProductType</span><br /> <input type="number" value="@product.Quantity" @onchange="@((ChangeEventArgs e) => UpdateQuantity(e, product))" class="form-control input-quantity" min="1" /> ---
  • 148. Cart モデルに数量を追加 - 4b • UpdateQuantity 追加する Client → Pages → Cart.razor //追加 --- private async Task UpdateQuantity (ChangeEventArgs e, CartProductResponse product) { product.Quantity = int.Parse(e.Value.ToString()); if (product.Quantity < 1) product.Quantity = 1; await CartService.UpdateQuantity(product); } ---
  • 149. Cart モデルに数量を追加 - 4c • UpdateQuantity 追加する Client → Pages → Cart.razor //追加 --- private async Task UpdateQuantity (ChangeEventArgs e, CartProductResponse product) { product.Quantity = int.Parse(e.Value.ToString()); if (product.Quantity < 1) product.Quantity = 1; await CartService.UpdateQuantity(product); } ---
  • 150. Cart モデルに数量を追加 - 4c • デバッグ実⾏ • カートを表⽰ • Chrome Dev Tools アプリケーションタブ に移動 • ローカルストレージを表⽰ • update ボタン押下してテスト
  • 152. 認証 全体の流れ • 新しいページの追加 • 最初のユーザーを登録 • パスワードのハッシュを作成し、パスワードを解決 • JSON Web Token • Authorized View の利⽤
  • 153. UserRegister Model の作成 • ユーザー登録には 新しいモデルが必要 • モデルの名前は UserRegister • Shared Project を右クリックし、ここに 新 Class を追加 • Public Class shared → UserRegister using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BlazorEcommerceApp.Shared { public class UserRegister { [Required, EmailAddress] public string Email { get; set; } = string.Empty; [Required, StringLength(100, MinimumLength = 6)] public string Password { get; set; } = string.Empty; [Compare("Password", ErrorMessage = "The passwords do not match.")] public string ConfirmPassword { get; set; } = string.Empty; } }