[C#] 打包 nuget

NuGet 打包與引用指南 概述 本文檔詳細說明了如何創建 NuGet 包並將其添加到專案中的步驟流程。NuGet 是 .NET 生態系統中的包管理器,可以簡化函式庫的分發和引用過程。 打包 .NET 專案為 NuGet 包 生成 NuGet 包 (.nupkg) 要將您的 .NET 專案打包成 NuGet 包,請在專案根目錄中執行以下命令: dotnet pack -c Release 這個命令會編譯專案並創建發佈配置的 NuGet 包。生成的 .nupkg 檔案通常位於 bin/Release 目錄中。 參數說明: -c Release:以 Release 配置模式進行打包,這會優化程式碼並移除除錯資訊 查找生成的 NuGet 包 打包完成後,可以使用以下命令查找所有生成的 .nupkg 檔案: ls **/*.nupkg 這個命令會遞迴地搜索當前目錄及其子目錄中的所有 .nupkg 檔案並列出它們。 使用本地 NuGet 包 複製 NuGet 包到本地倉庫 要將生成的 NuGet 包複製到本地 NuGet 倉庫中,可以使用以下命令: cp **/*.nupkg /Users/rainhu/advantech/mynuget/ 這會將所有找到的 .nupkg 檔案複製到指定的本地目錄 /Users/rainhu/advantech/mynuget/。 配置本地 NuGet 來源 要使用本地 NuGet 包,您需要將本地目錄添加為 NuGet 來源。在您的專案中,可以通過以下方式配置: ...

April 8, 2025 · 2 分鐘 · Rain Hu

[.NET] User Secret CLI

.NET User Secrets 使用指南 安裝與設定 dotnet tool install --global dotnet-user-secrets 常用指令 # 初始化 user secrets (在專案資料夾中執行) dotnet user-secrets init # 設定 secret dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;" # 設定多層級值 dotnet user-secrets set "Logging:LogLevel:Default" "Information" # 查看所有 secrets dotnet user-secrets list # 移除特定 secret dotnet user-secrets remove "ConnectionStrings:DefaultConnection" # 清除所有 secrets dotnet user-secrets clear 在程式碼中使用 ConnectionString 範例 // Program.cs 或 Startup.cs var builder = WebApplication.CreateBuilder(args); // 自動讀取 user secrets (開發環境) if (builder.Environment.IsDevelopment()) { builder.Configuration.AddUserSecrets<Program>(); } // 在需要的地方使用 ConnectionString var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); // 範例: 註冊 DbContext builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));

March 23, 2025 · 1 分鐘 · Rain Hu

[IT] React + .Net

React + .Net 一、環境設置 Setup 1. 行前準備 Prerequisites 安裝 Node.js 和 npm node -v npm -v 安裝 .NET SDK dotnet --version 用 vs code 下載 ES7+ React/Redux/React-Native snippets 因為 rafce 很好用, 相當於, typescript 則是 tsrafce import React from 'react' const index = () => { return ( <div>index</div> ) } export default index 2. 創建 React 安裝 react npm install create-react-app 創建新的 react app (typescript) --template typescript 可指定使用 typescript npx create-react-app {project} --template typescript cd {project} 必要時可能要初始化一個新的 react 項目,確保版本是匹配的。 yarnpkg add --exact react-dom react-scripts 3. 基本結構 函數式元件(Functional Component): const Card: React.FC<Props> = ({ companyName, ticker, price }: Props): JSX.Element => { ... } Card 是一個函數式元件,使用 TypeScript 和 React。 React.FC<Props> 指定這個元件是接受 Props 作為參數的 React 函數式元件 ({ companyName, ticker, price }) 是從 Props 解構的屬性,這些屬性將會被傳入元件 JSX 標籤: return ( <div className='card'>...</div> ) 這是元件返回的 JSX,它描述了元件應該如何渲染。 4. State import React, { useState } from 'react' type Props = {} const Button: React.FC<Props> = (props: Props): JSX.Element => { const [count, setCount] = useState<number>(0); const onClick = (e: any) => { setCount(count + 1); console.log(e); } return ( <div> <button onClick={(e) => onClick(e)}>Click me</button> <p>You clicked {count} times</p> </div> ) } export default Button 引入 React 和 useState Hook: import React, { useState } from 'react' 從 React 包中引入 useState Hook,用於函數式元件中添加狀態。 使用 useState Hook 定義狀態 const [count, setCount] = useState<number>(0); 定義一個名為 count 的狀態變量,初始值為 0。 setCount 是用來更新 count 的函數。 useState<number>(0) 指定 count 的類型為 number。 定義 onClick 事件處理函數: const onClick = (e: any) => { setCount(count + 1); console.log(e); } onClick 是一個事件處理函數,接受一個事件參數 e。 每當按鈕被點擊時, count 會加 1,並且會在控制台輸出事件 e。 any 也可被寫成 MounthEvent 或 SyntheticEvent。

June 11, 2024 · 2 分鐘 · Rain Hu

[IT] ApiController Atrribute

ApiController Atrribute 當我在 API 專案中建立新的 controller 時,它會自帶一個 [ApiController] 屬性的控制器類別,而這個標籤的作用為何呢? [ApiController] public class TestController : ControllerBase { } 1. 自動 HTTP 400 回應 它會自動產生一個行為過濾器(action filter),當 ModelState.IsValid 為 false 時,自動回傳 400 Response。 2. 綁定來源參數推斷 可以更改模型綁定的約定,例如,[FromBody] 是針對複雜類型參數推斷的。 3. Multi/form-data 請求推理 對於標示 [FromForm] 的參數,推斷 Content-Type 為 multipart/form-data。 4. 屬性路由要求 強制要求所有操作都必須通過屬性路由。 ...

June 9, 2024 · 1 分鐘 · Rain Hu

[IT] EntityFramework Commands

前置作業 需安裝以下 Packages Microsoft.EntityFrameworkCore Microsoft.entityFrameworkCore.Design Microsoft.EntityFrameworkCore.Tools 設置好 []DbContext 範例 public class AppDbContext : DbContext { public DbSet<Reminder> Reminders { get; set; } = null!; public DbSet<User> Users { get; set; } = null!; public AppDbContext() { } protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseNpgsql("Host=localhost;Port=5432;Username=********;Password=********;Database=testdb"); } } ...

March 31, 2024 · 2 分鐘 · Rain Hu

[IT] 用 C# 建置 Clean Architecture 專案

Configure C# Solution step by step 開啟 Terminal 新增解決方案 dontet new sln -o MySln 移至方案目錄 cd MySln 根據解決方案 hierarchy 建構專案 dotnet new webapi -o MySln.Api dotnet new classlib -o MySln.Application dotnet new classlib -o MySln.Domain dotnet new classlib -o MySln.Infrastructure dotnet new classlib -o MySln.Contracts 將所有專案加進解決方案 for proj in $(ls -r **/*.csproj) dotnet sln add $proj 建立專案之間的 dependency dotnet add MySln.Api reference MySln.Application MySln.Infrastructure MySln.Contracts dotnet add MySln.Infrastructure reference MySln.Application dotnet add MySln.Application reference MySln.Domain

March 9, 2024 · 1 分鐘 · Rain Hu

[IT] 使用 Clean Architecture + DDD 建置 Restful API

程式碼 https://github.com/intervalrain/webapi_ca/ 正文 建置 Solution 首先先參考 Clean Architecture 最經典的同心圓,來確定我們需要將我們的解決方案做哪些分層: 我將使用 Restful API 做為我們 I/O (Presentation Layer) 並且我需要配備身份驗證的機制 (Presentation Layer) 我使用 PostgresDB 作為我的 (Infrastructure Layer) 我的核心商業邏輯 (Application / Domain Layer) 創建專案 dotnet new sln -o Mysln 進入專案所在的資料夾 cd Mysln 根據預先的分層建立專案資料夾,並且使用 dotnet 指令建立相對應的專案類型。 Api –> WebAPI Infrastructure –> classlib Contracts –> classlib Application –> classlib Domain –> classlib dotnet new webapi -o Mysln.Api dotnet new classlib -o Mysln.Contracts dotnet new classlib -o Mysln.Infrastructure dotnet new classlib -o Mysln.Application dotnet new classlib -o Mysln.Domain 接著我們需要把產生的專案資料夾,加入到我們的 Solution。 dotnet sln add Mysln.Api dotnet sln add Mysln.Application dotnet sln add Mysln.Contracts dotnet sln add Mysln.Domain dotnet sln add Mysln.Infrastructure 接下來按照 Clean Architecture 的依賴原則來設定 dependency,依我的專案來說依賴方向如下。 graph TD; Api-->Contracts; Api-->Application; Infrastructure-->Application Application-->Domain Api-.->Infrastructure dotnet add Mysln.Api reference Mysln.Contracts Mysln.Application dotnet add Mysln.Infrastructure reference Mysln.Application dotnet add Mysln.Application reference Mysln.Domain dotnet add Mysln.Api reference Mysln.Infrastructure 至此,已經完成了基本的 hierarchy 建置,接下來要為 Restful Client 做準備。 Login Authentication 作為驗證的需要,我們需要以下三種驗證檔案,包含兩個 Request 與一個 Response public record RegisterRequest( string FirstName, string LastName, string Email, string Password ); public record LoginRequest( string Email, string Password ); public record AuthenticationResponse( Guid Id, string FirstName, string LastName, string Email, string token ); 到 Controller 去設置註冊與登入的兩個路由,並且將之後的服務介面預先注入到其中。 [ApiController] [Route("auth")] public class AuthenticationController : ControllerBase { [HttpPost("register")] public IActionResult Register(RegisterRequest request) { return Ok(request); } [HttpPost("login")] public IActionResult Login(LoginRequest request) { return Ok(request); } } 接著我們創建 Application 中的服務,注意到因為 Application 不依賴於 Contracts,故我們這邊需要創建自己的 DataModel public record AuthenticationResult ( Guid Id, string FirstName, string LastName, string Email, string Token ); 接著我們定義出 Application 的 Service。 public interface IAuthenticationService { AuthenticationResult Register(string firstName, string lastName, string email, string password); AuthenticationResult Login(string email, string password); } 定義好我們的 service interface 之後,就可以到 Presentation 中將我們的 service 注入到 presentation 之中。 [ApiController] [Route("auth")] public class AuthenticationController : ControllerBase { private readonly IAuthenticationService _authenticationService; public AuthenticationController(IAuthenticationService authenticationService) { _authenticationService = authenticationService; } [HttpPost("register")] public IActionResult Register(RegisterRequest request) { var authResult = _authenticationService.Register( request.FirstName, request.LastName, request.Email, request.Password); var response = new AuthenticationResponse( authResult.Id, authResult.FirstName, authResult.LastName, authResult.Email, authResult.Token); return Ok(response); } [HttpPost("login")] public IActionResult Login(LoginRequest request) { var authResult = _authenticationService.Login( request.Email, request.Password); var response = new AuthenticationResponse( authResult.Id, authResult.FirstName, authResult.LastName, authResult.Email, authResult.Token); return Ok(response); } } 我們已經定義好我們的 service 後,便可以到 presentation 的 Program(或是其它入口點,如 Startup.cs 或 MauiProgram.cs),做 service 的依賴注入。 using BuberDinner.Application.Services.Authentication; var builder = WebApplication.CreateBuilder(args); { builder.Services.AddScoped<IAuthenticationService, AuthenticationService>(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); } var app = builder.Build(); { app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.MapControllers(); app.Run(); } 最後,我們先實作一個暫時的 Service,來確認 Api 是可以作業的。 public class AuthenticationService : IAuthenticationService { public AuthenticationResult Register(string firstName, string lastName, string email, string password) { return new AuthenticationResult( Guid.NewGuid(), firstName, lastName, email, "token" ); } public AuthenticationResult Login(string email, string password) { return new AuthenticationResult( Guid.NewGuid(), "firstName", "lastName", email, "token" ); } } 執行 dotnet run --project .\Mysln.Api\ 在 Swagger 中測試我們實作的 register 與 login API,如果正常工作,會回傳 StatusCode: 200。 Dependency Injection 我們想要每一層都可以自己管理自己的注入,此時我們需要引入 Microsoft.Extensions.DependencyInjection。 接下來實作 Application 的 DependencyInjection。 public static class DependencyInjection { public static IServiceCollection AddApllication(this IServiceCollection services) { services.AddScope<IAuthenticationService, AuthenticationService>(); return services; } } 接下來實作 Infrastructure 的 DependencyInjection。(暫時還沒有注入 repository) public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services) { // 未來要注入 repositories return services; } } 接下來我們可以改寫 Program.cs。 using BuberDinner.Application; using BuberDinner.Infrastructure; var builder = WebApplication.CreateBuilder(args); { builder.Services .AddApplication() .AddInfrastructure(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); } var app = builder.Build(); { app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.MapControllers(); app.Run(); } 實作 JWT Token Generator 首先先在 Application Layer 創建一個 interface 來做依賴反轉 public interface IJwtTokenGenerator { string GenerateToken(Guid userId, string firstName, string lastName); } 接著我們到 Infrastructure Layer 來實作我們的 JwtTokenGenerator。 首先我們需要 System.IdentityModel.Tokens.Jwt 這個 Package。 接著我們實作 JwtTokenGenerator。 public class JwtTokenGenerator : IJwtTokenGenerator { public string GenerateToken(Guid userId, string firstName, string lastName) { var signingCredentials = new SigningCredentials( new SymmetricSecurityKey( Encoding.UTF8.GetBytes("super-secret-key")), SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()), new Claim(JwtRegisteredClaimNames.GivenName, firstName), new Claim(JwtRegisteredClaimNames.FamilyName, lastName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var securityToken = new JwtSecurityToken( issuer: "Mysln", expires: DateTime.Now.AddDays(1), claims: claims, signingCredentials: signingCredentials); return new JwtSecurityTokenHandler().WriteToken(securityToken); } } 接著我們將之注入到服務中,即大功告成了。 public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services) { services.AddSingleton<IJwtTokenGenerator, JwtTokenGenerator>(); return services; } } 使用 Options Pattern 注入 JWT Settings 接下來我們要使用 Options Pattern 將 JWT Settings 注入到 JwtTokenGenerator 中。 首先我們先到 Mysln.Api 的 appsettings.json 中將 options 設置完成。 { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "JwtSettings": { "Secret": "super-secret-key", "ExpiryMinutes": 60, "Issuer": "Mysln", "Audience:": "Mysln" } } 由於我們要使用 Options Pattern,我們需要改寫我們的 Program.cs,並且將 ConfigurationManager 注入到 Infrastructure 的 DependencyInjection。 為此我們需要引入套件 Microsoft.Extensions.Configuration 與 Microsoft.Extensions.Options.ConfigurationExtensions。 並且我們需要創建一個 Model。 public class JwtSettings { public const string SectionName = "JwtSettings"; public string Secret { get; init; } = null!; public int ExpiryMinutes { get; init; } public string Issuer { get; init; } = null!; public string Audience { get; init; } = null!; } Program.cs 需改寫成: builder.Services .AddApplication() .AddInfrastructure(builder.Configuration); 將 DependencyInjection 改寫成: public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, ConfigurationManager configuration) { services.Configure<JwtSettings>(configuration.GetSection(JwtSettings.SectionName)); services.AddSingleton<IJwtTokenGenerator, JwtTokenGenerator>(); services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); return services; } } 接下來,我們可以把 JwtTokenGenerator 改寫成: public class JwtTokenGenerator : IJwtTokenGenerator { private readonly JwtSettings _jwtSettings; private readonly IDateTimeProvider _dateTimeProvider; public JwtTokenGenerator(IDateTimeProvider dateTimeProvider, IOptions<JwtSettings> jwtOptions) { _dateTimeProvider = dateTimeProvider; _jwtSettings = jwtOptions.Value; } public string GenerateToken(Guid userId, string firstName, string lastName) { var signingCredentials = new SigningCredentials( new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_jwtSettings.Secret)), SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()), new Claim(JwtRegisteredClaimNames.GivenName, firstName), new Claim(JwtRegisteredClaimNames.FamilyName, lastName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var securityToken = new JwtSecurityToken( issuer: _jwtSettings.Issuer, audience: _jwtSettings.Audience, expires: _dateTimeProvider.UtcNow.AddMinutes(_jwtSettings.ExpiryMinutes), claims: claims, signingCredentials: signingCredentials); return new JwtSecurityTokenHandler().WriteToken(securityToken); } } 以上就大功告成了。 使用 dotnet user-secrets 指令 如果不想要將 Options 中的 secret 儲存在程式(appsettings.json)裡面,可以利用 dotnet user-secrets 將 secret 儲存於環境變數裡面。 透過執行以下的指令來初始化專案的 UserSecretsId dotnet user-secrets init --project Mysln.Api 接著將 UserSecretsId 綁定到我們專案的 JwtSettings:Secret。 dotnet user-secrets set --project Mysln.Api "JwtSettings:Secret" 日後可以經由以下指令查詢。 dotnet user-secrets list --project Mysln.Api Domain Model 先建立一個簡單的 Domain Model(Entity) public class User { public Guid Id { get; set; } = Guid.NewGuid(); public string FirstName { get; set; } = null!; public string LastName { get; set; } = null!; public string Email { get; set; } = null!; public string Password { get; set; } = null!; } Repository Pattern 在 Application Layer 建立 IRepository public interface IUserRepository { User? GetUserByEmail(string email); void Add(User user); } 將 IRepository 注入 Application 的 Service 並用查改存推改寫 Service public class AuthenticationService : IAuthenticationService { private readonly IJwtTokenGenerator _jwtTokenGenerator; private readonly IUserRepository _userRepository; public AuthenticationService(IJwtTokenGenerator jwtTokenGenerator, IUserRepository userRepository) { _jwtTokenGenerator = jwtTokenGenerator; _userRepository = userRepository; } public AuthenticationResult Register(string firstName, string lastName, string email, string password) { // 查 if (_userRepository.GetUserByEmail(email) is not null) { throw new Exception("User with given email already exists."); } // 改 var user = new User { FirstName = firstName, LastName = lastName, Email = email, Password = password }; // 存 _userRepository.Add(user); // 推 var token = _jwtTokenGenerator.GenerateToken(user.Id, firstName, lastName); return new AuthenticationResult(user.Id, firstName, lastName, email, token); } public AuthenticationResult Login(string email, string password) { // 查 if (_userRepository.GetUserByEmail(email) is not User user) { throw new Exception("User with given email does not exist."); } if (user.Password != password) { throw new Exception("Invalid password."); } // 改 var token = _jwtTokenGenerator.GenerateToken(user.Id, user.FirstName, user.LastName); return new AuthenticationResult(user.Id, user.FirstName, user.LastName, email, token); } } 接著我們在 Infrastructure Layer 實作我們的 repository,我們暫時先不接資料庫,所以先做一個 InMemory 版本的 repository 來做測試。 public class UserRepository : IUserRepository { private readonly List<User> _users = new(); public void Add(User user) { _users.Add(user); } public User? GetUserByEmail(string email) { return _users.SingleOrDefault(u => u.Email.Equals(email)); } } 實作完需要透過 DependencyInjection 注入到我們的 Service Container 內。 public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, ConfigurationManager configuration) { services.Configure<JwtSettings>(configuration.GetSection(JwtSettings.SectionName)); services.AddSingleton<IJwtTokenGenerator, JwtTokenGenerator>(); services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); services.AddSingleton<IUserRepository, UserRepository>(); return services; } } 至此,我們已經完成了一個簡單的身份認證的 API。

February 26, 2024 · 6 分鐘 · Rain Hu

[IT] .NET Maui

.NET Maui .NET Maui 是一個跨平台的桌面與手機應用開發框架,它支援 iOS、Android、macOS、Windows。 不同於 Xamarin,.NET Maui 只需要一個專案便可以導向不同的平台。 架構 一個 .NET Maui 專案底下,預設會有幾個資料夾與檔案,其關係如下圖: /Platforms 底下的各個資料夾為不同平台的入口,不同的平台各有一個 Program.cs。 各個 Program.cs 內又會透過注入該 namespace 底下的 AppDelegate ,將入口指向 MauiProgram 的 CreateMauiApp(),就此將不同平台路由到 MauiProgram.cs 這個統一的入口。 public class Program { static void Main(string[] args) { UIApplication.Main(args, null, typeof(AppDelegate)); } } [Register("AppDelegate")] public class AppDelegate : MauiUIApplicationDelegate { protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); } 關係如下圖: graph TD; iOS-->MauiProgram.cs; Android-->MauiProgram.cs; Windows-->MauiProgram.cs; macOS-->MauiProgram.cs; MauiProgram.cs-->App; App-->AppShell; AppShell-.->Page1; AppShell-.->Page2; AppShell-.->Page3; AppShell-.->Page4; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); return builder.Build(); } } public partial class App : Application { public App() { InitializeComponent(); MainPage = new AppShell(); } } 從上面兩段程式 MauiProgram.cs 與 App.xaml.cs 可以看出,這個統一的進入點會開啟 App,並將 AppShell 作為 MainPage 開放。 Router 在此可以發現 AppShell 作為一個 Controller 的功能,用來引導頁面的路由。 我們可以透過 RegisterRoute 來注冊要顯示的頁面。 public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(Page1), typeof(Page1)); Routing.RegisterRoute(nameof(Page2), typeof(Page2)); Routing.RegisterRoute(nameof(Page3), typeof(Page3)); } } xaml 上述的頁面可以透過新增 xaml 檔來建立: 以下為一個 xaml 檔的 sample x:class="MoneyTrack.AppShell" 表示該檔案的路徑為 MoneyTrack.AppShell mlns 關鍵字很像是 using: xmlns:views="clr-namespace:MoneyTrack.Views": 代表將 MoneyTrack.Views 這個路徑命名成 views。 ShellContent 內代表是首頁要導引至的頁面,如下例會導向 view:MoneyTackPage <?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="MoneyTrack.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:MoneyTrack" xmlns:views="clr-namespace:MoneyTrack.Views" Shell.FlyoutBehavior="Disabled" Title="MoneyTrack"> <ShellContent Title="Home" ContentTemplate="{DataTemplate views:MoneyTackPage}" Route="MoneyTackPage" /> </Shell> <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MoneyTrack.Views.ContactsPage" Title="Contacts"> <VerticalStackLayout Spacing="5"> <Label Text="Welcome to .NET MAUI!" VerticalOptions="Center" HorizontalOptions="Center" /> <Button x:Name="btn1" Clicked="btn1_Clicked" Text="click1"></Button> <Button x:Name="btn2" Clicked="btn2_Clicked" Text="click2"></Button> </VerticalStackLayout> </ContentPage> Shell 接著我們可以透過 Shell 來控制面版上要顯示的頁面: public void btnPage1_Clicked() { Shell.Current.GoToAsync(nameof(Page1)); } GoToAsync Shell 本身是一個 View,也是一個 Layout GoToAsync 可以用來切換頁面 void btnPage1_Clicked(object sender, EvertArgs e) { Shell.Current.GoToAsync($"{nameof(Page1)}"); // 前往 Page1 } void btnCancel_Clicked(object sender, EvertArgs e) { Shell.Current.GoToAsync($"//{nameof(MainPage)}"); // 回到 MainPage } void btnCancel_Clicked(object sender, EvertArgs e) { Shell.Current.GoToAsync($".."); // 回到上一頁 } QueryProperty 透過 QueryPropertyAttribute 可以達到如 http method 裡的 get 的方法。 以下範例等同於實現 page1?Id=1,在路由到 page1 的同時,將 property: Id 賦值。 [QueryProperty(nameof(PageId), "Id")] public partial class Page1 : ContentPage { public EditContactPage() { InitializeComponent(); } public string ContactId { set { lblName.Text = Id; } } }

January 28, 2024 · 2 分鐘 · Rain Hu

[IT] CORS 跨原始來源要求

TL;DR CORS (Cross-Origin Resource Sharing) 是一個支援安全跨源請求和資料傳輸的機制,用於在瀏覽器和伺服器之間進行跨源請求。 這是一個 W3C 標準,可讓伺服器放寬相同原始來源原則。 不是安全性功能,CORS 會放寬安全性。 允許 CORS 並不會增強 API 的安全性。CORS 的運作方式 允許伺服器明確允許某些跨原始來源要求,同時拒絕其他要求。 比舊版技術 (例如:JSONP) 更安全且更有彈性。 何謂相同原始來源 如果兩個 URL 具有相同的配置、主機和連接埠,則其原始來源相同 (RFC 6454)。 這兩個 URL 具有相同的原始來源: https://example.com/foo.html https://example.com/bar.html 這些 URL 的原始來源與前兩個 URL 不同: https://example.net:不同的網域 https://www.example.com/foo.html:不同的子網域 http://example.com/foo.html:不同的配置 https://example.com:9000/foo.html:不同的連接埠 如何啟動 CORS 有三種方式可以啟用 CORS: 在中介軟體中,使用具名原則或預設原則。 使用端點路由。 使用 [EnableCors] 屬性。 搭配具名原則使用 [EnableCors] 屬性能夠以最精細的程度來控制對於支援 CORS 之端點的限制。 ...

January 19, 2024 · 3 分鐘 · Rain Hu

[IT] LINQ: IQueryable Provider

可重複使用的 IQueryable 基類 IQueryable 簡介 在 C# 最新版本中的 IQueryable 已經不再是一個介面,而是分為兩個部分: IQueryable 與 IQueryProvider。在開始實作之前,我們必須先了解一下這兩個介面。 public interface IQuerable : IEnumerable { Type ElementType { get; } Expression Expression { get; } IQueryProvider Provider { get; } } public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable { } IQueryable 有三個唯讀屬性: ElementType 代表了元素的類型 (或等於 IQueryable<T> 中的 T) Expression 代表了查詢對應的表達式。這是 IQueryable 存在的核心要素。在 IQueryable 的內部,實際上是一個表示查詢的表達式,它將查詢表示為 LINQ 查詢運算子/方法調用的樹狀結構。如果進一步看,你會發現,IQueryable 或是 Queryable 都只是在提供一個自動構建表達式樹節點 (expression tree nodes) 的機制。當我們對 IQeuryable 使用 Where 方法時,它只是回傳一個新的 IQueryable,並且在進行調用的樹頂添加一個方法表達式樹節點。 Provider 作為真正的「提供者」,它負責原先所有 IQueryable 的執行方法。 IQueryProvider 簡介 public interface IQueryProvider { IQueryable CreateQuery(Expression expression); IQueryable<TElement> CreateQuery<TElement>(Expression expression); object Execute(Expression expression); TResult Execute<TResult>(Expression expression); } 當我們進一步觀察 IQueryProvider,會發現它事實上只有兩個操作:CreateQuery、Execute,只是各有一個泛型與非泛型的方法。一般我們會使用泛型的方法,因為它可以避免使用反射來建構實例,從而提高性能。 ...

September 21, 2023 · 19 分鐘 · Rain Hu