Power Apps x .NET ~ Transforming Business Applications with Fusion DevelopmentShotaro Suzuki
タイトル:Power Apps x .NET ~ フュージョン開発によるビジネス アプリケーションの変革
概要:企業、政府自治体に限らず、Fusion Team = 市民開発者とプロの開発者がコラボしてアプリを作っていこうという気運が高まってきています。
今回は Power Apps、 .NET 6、OpenAPI 対応 Web API、Azure API Management 等の組み合わせでアプリを作ってみます。
https://dotnetlab.connpass.com/event/254374/
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
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)
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
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
全⽂検索クエリ
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
---
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
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; }
}
}---
• カートを追加
• デバッグ実⾏して画⾯のカート部分を確認
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);
}
---
---
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;
}
}
164. サーバーへのユーザー登録の実施 - 1
• サーバーへのユーザー
登録
Server → Services → AuthService → AuthService.cs
---
public async Task<ServiceResponse<int>> Register(User user, string password)
{
if (await UserExists(user.Email))
{
return new ServiceResponse<int>
{
Success = false,
Message = "このユーザーは既に存在しています。"
};
}
CreatePasswordHash(password, out byte[] passwordHash, out byte[]
passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
_context.Users.Add(user);
await _context.SaveChangesAsync();
return new ServiceResponse<int>
{ Data = user.Id, Message = "登録が成功しました!!" };
}
---
165. サーバーへのユーザー登録の実施 - 2
• 変更をテーブルに保存
• テストするには、もちろん
Auth Controller が
必要
Server → Services → AuthService → AuthService.cs
---
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[]
passwordSalt)
{
using (var hmac = new HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac
.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
---
166. AuthControllerを追加する - 1
• Server →
Controllers →
AuthController →
API コントローラー新規
作成
• control フォルダに別の
空の API コントローラを
作成
Server → Controllers → AuthController.cs
---
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace BlazorEcommerceApp.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
---
---
167. AuthControllerを追加する - 2
• アプリを起動して、
swagger にアクセス
• localhost://ポート番号
/swagger/index.html
• user already exists
• Azure Data
Explorer で確認
211. クライアントの [Authorize] 属性の活⽤
• Client → wwwroot
→ _Imports.razor
• テスト
Chrome Dev Tools
Application タブ
LocalStorage ペイン
Client → Shared → UserButton.razor
---
//追加
---
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Authorization
---
===
Client → Pages → Profile.razor
===
//追加
@attribute [Authorize]
===
Client → wwwroot → App.razor
===
//追加
---
<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>
---
---
212. UserChangePassword モデルを追加する
• Shared →
UserChange
Password.cs
Shared → UserChangePassword.cs
---
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 UserChangePassword
{
[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;
}
}
---
221. まとめ
l 前回までの復習
l Blazor 概要
l 今回作成する Web アプリケーションの概要
l Blazor WebAssembly プロジェクト作成
l Web API コントローラー追加、モデル追加
l Entity Framework による Code First データベース作成
l 商品サービス、商品リスト、カテゴリーサービス等必要なサービス、CRUD 処理等の実装
l 検索サービスの追加と検索コンポーネントの実装、カートサービス、UI/UX の変更
l 認証・ユーザー登録、その他の機能の実装
222. リソース
l セッションでご紹介した EC アプリ .NET 5版ですが、参考にさせて戴きました。
l https://github.com/patrickgod/BlazorEcommercePreviewYT
227. 今回のデモアプリのイメージ
Azure
SQL Database
Elastic Cloud
東⽇本リージョン
マスターノード x 1
データノード x 2
ML ノード x 1
https://f79...c67.japaneast
.azure.elastic-
cloud.com:9243/
全⽂検索クエリ
CRUD
検索・更新 UI
APM .NET Agent
Blazor
WebAssembly
Azure サブスクリプション
Visual
Studio
2022
Azure
App Service
Elastic APM
Endpoint に送信
Azure
Data Explorer
ASP.NET 6
Web API
AntDesign
234. まとめ
l .NET 6 における Blazor Update
l ASP.NET Core Web API を構築
l Blazor WebAssembly でフロントエンドアプリを構築
l Elastic APM によるアプリケーションの監視
235. .NET MAUI Blazor App - モバイル、デスクトップ、
Web ハイブリッドアプリを開発
https://qiita.com/shosuz/items/4218af93343e5cc999ec
236. ASP.NET Core Blazor WebAssembly と Web API と Entity Framework
Core で SQL Server のデータを取得したり追加したり更新したり削除したりする
[.NET 6 版]
https://qiita.com/tamtamyarn/items/876a5cd4b9ec9cdc1044