docs: 补全全部缺失的 XML 文档注释(中文)

- 为全部 5 个项目(Avalonia-API、Avalonia-Common、Avalonia-EFCore、
  Avalonia-PC、Avalonia-Services)中缺失注释的类、方法、属性、字段、
  接口成员等补全中文 XML 文档注释
- 共修改约 37 个文件,补全约 220+ 处注释
- 修复 ServiceEndpointCollection.cs 中 MapDelete<TService> 语法错误
- 修复 PcAuthService.cs 中 const prefix 位置错乱导致编译失败的问题
- 扫描结果:缺失项 0
- 构建结果:4/4 项目编译通过
This commit is contained in:
luoqian 2026-05-18 11:35:13 +08:00
parent 446502b0e9
commit fc6f9f6bc3
43 changed files with 884 additions and 233 deletions

View File

@ -1,6 +1,7 @@
{
"chat.tools.terminal.autoApprove": {
"ForEach-Object": true,
"dotnet list": true
"dotnet list": true,
"dotnet build": true
}
}

View File

@ -8,6 +8,10 @@ using System.Text.Json;
namespace Avalonia_API.Authentication
{
/// <summary>
/// API 鉴权端点服务,实现 <see cref="IApiAuthEndpointService"/>
/// 处理登录、刷新 Token 和登出操作,使用 JWT 与 Refresh Token 机制。
/// </summary>
public sealed class ApiAuthEndpointService(
AppDataContext db,
JwtTokenService jwtTokenService,
@ -18,6 +22,12 @@ namespace Avalonia_API.Authentication
PropertyNameCaseInsensitive = true,
};
/// <summary>
/// 处理用户登录请求。根据账号(邮箱或用户名)查找或创建用户,
/// 生成 JWT Access Token 和 Refresh Token 并返回。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体、请求头等信息。</param>
/// <returns>包含 AccessToken、RefreshToken 及过期时间的认证响应。</returns>
public async Task<object?> LoginAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiLoginRequest>(ctx.Body);
@ -56,6 +66,12 @@ namespace Avalonia_API.Authentication
roles), "登录成功");
}
/// <summary>
/// 使用 Refresh Token 轮换新的 Access Token 和 Refresh Token。
/// 旧的 Refresh Token 会被撤销并替换。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
/// <returns>新的 Token 对;若 Refresh Token 无效则返回 401 错误。</returns>
public async Task<object?> RefreshAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiRefreshTokenRequest>(ctx.Body);
@ -88,6 +104,11 @@ namespace Avalonia_API.Authentication
roles), "刷新成功");
}
/// <summary>
/// 处理用户登出请求,撤销指定的 Refresh Token。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
/// <returns>登出成功的响应。</returns>
public async Task<object?> LogoutAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiLogoutRequest>(ctx.Body);
@ -95,6 +116,12 @@ namespace Avalonia_API.Authentication
return ResponseHelper.Succeed("退出成功");
}
/// <summary>
/// 将 JSON 请求体反序列化为指定类型。
/// </summary>
/// <typeparam name="T">目标类型。</typeparam>
/// <param name="body">JSON 请求体字符串,可为空。</param>
/// <returns>反序列化后的对象;若 body 为空则返回默认值。</returns>
private static T? Deserialize<T>(string? body)
{
return string.IsNullOrWhiteSpace(body)
@ -102,6 +129,11 @@ namespace Avalonia_API.Authentication
: JsonSerializer.Deserialize<T>(body, JsonOptions);
}
/// <summary>
/// 从上下文的 Items 中提取 ASP.NET Core HttpContext并获取客户端远程 IP 地址。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>客户端 IP 地址字符串;若无法获取则返回 null。</returns>
private static string? GetRemoteIpAddress(ServiceEndpointContext ctx)
{
return ctx.Items.TryGetValue("HttpContext", out var value) && value is HttpContext httpContext
@ -109,6 +141,11 @@ namespace Avalonia_API.Authentication
: null;
}
/// <summary>
/// 规范化角色数组:去空白、去重(忽略大小写),为空时默认返回 Admin 角色。
/// </summary>
/// <param name="roles">原始角色数组,可为 null。</param>
/// <returns>规范化后的角色数组。</returns>
private static string[] NormalizeRoles(string[]? roles)
{
var normalized = roles?

View File

@ -1,15 +1,33 @@
namespace Avalonia_API.Authentication
{
/// <summary>
/// JWT 鉴权配置选项,从 appsettings.json 的 Jwt 节绑定。
/// </summary>
public sealed class JwtOptions
{
/// <summary>
/// 获取或设置 Token 签发者。
/// </summary>
public string Issuer { get; set; } = "Avalonia-API";
/// <summary>
/// 获取或设置 Token 受众。
/// </summary>
public string Audience { get; set; } = "Avalonia-Client";
/// <summary>
/// 获取或设置签名密钥(至少 32 字节)。
/// </summary>
public string SigningKey { get; set; } = "change-this-development-signing-key-at-least-32-bytes";
/// <summary>
/// 获取或设置 Access Token 有效期(分钟),默认 60 分钟。
/// </summary>
public int AccessTokenMinutes { get; set; } = 60;
/// <summary>
/// 获取或设置 Refresh Token 有效期(天),默认 30 天。
/// </summary>
public int RefreshTokenDays { get; set; } = 30;
}
}

View File

@ -7,10 +7,22 @@ using System.Text;
namespace Avalonia_API.Authentication
{
/// <summary>
/// JWT Token 服务,负责创建包含用户声明和角色的 Access Token。
/// </summary>
public sealed class JwtTokenService(IOptions<JwtOptions> options)
{
/// <summary>
/// JWT 配置选项。
/// </summary>
private readonly JwtOptions _options = options.Value;
/// <summary>
/// 创建包含用户声明和角色的 JWT Access Token。
/// </summary>
/// <param name="user">用户实体。</param>
/// <param name="roles">角色集合。</param>
/// <returns>包含 Token 字符串和过期时间的元组。</returns>
public (string Token, DateTime ExpiresAt) CreateAccessToken(UserEntity user, IReadOnlyCollection<string> roles)
{
var expiresAt = DateTime.UtcNow.AddMinutes(_options.AccessTokenMinutes);

View File

@ -7,10 +7,25 @@ using System.Text;
namespace Avalonia_API.Authentication
{
/// <summary>
/// Refresh Token 服务,负责创建、查找、撤销和轮换 Refresh Token
/// Token 原文经 SHA256 哈希后存入数据库以保证安全性。
/// </summary>
public sealed class RefreshTokenService(AppDataContext db, IOptions<JwtOptions> options)
{
/// <summary>
/// JWT 配置选项。
/// </summary>
private readonly JwtOptions _options = options.Value;
/// <summary>
/// 创建一个新的 Refresh Token生成随机 Token 原文并存储其哈希到数据库。
/// </summary>
/// <param name="userId">关联的用户 ID。</param>
/// <param name="device">创建设备标识(如 User-Agent。</param>
/// <param name="ipAddress">客户端 IP 地址。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>包含 Token 原文和实体记录的元组。</returns>
public async Task<(string Token, ApiRefreshTokenEntity Entity)> CreateAsync(
int userId,
string? device,
@ -32,6 +47,13 @@ namespace Avalonia_API.Authentication
return (token, entity);
}
/// <summary>
/// 查找有效的 Refresh Token 实体。Token 原文会被哈希后查询数据库,
/// 仅返回未过期且未被撤销的 Token。
/// </summary>
/// <param name="token">Refresh Token 原文。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>有效的 Token 实体;若无效或不存在则返回 null。</returns>
public async Task<ApiRefreshTokenEntity?> FindActiveAsync(string? token, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(token))
@ -44,6 +66,11 @@ namespace Avalonia_API.Authentication
return entity?.IsActive == true ? entity : null;
}
/// <summary>
/// 撤销指定的 Refresh Token将其 RevokedAt 设为当前时间。
/// </summary>
/// <param name="token">要撤销的 Refresh Token 原文。</param>
/// <param name="cancellationToken">取消令牌。</param>
public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default)
{
var entity = await FindActiveAsync(token, cancellationToken);
@ -56,6 +83,14 @@ namespace Avalonia_API.Authentication
await db.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// 轮换 Refresh Token撤销旧的并创建新的将新 Token 的哈希关联到旧记录。
/// </summary>
/// <param name="token">旧的 Refresh Token 原文。</param>
/// <param name="device">当前设备标识。</param>
/// <param name="ipAddress">当前客户端 IP 地址。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>新的 Token 对;若旧 Token 无效则返回 null。</returns>
public async Task<(string Token, ApiRefreshTokenEntity Entity)?> RotateAsync(
string? token,
string? device,
@ -75,6 +110,11 @@ namespace Avalonia_API.Authentication
return next;
}
/// <summary>
/// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。
/// </summary>
/// <param name="token">Token 原文。</param>
/// <returns>SHA256 哈希后的十六进制字符串。</returns>
private static string HashToken(string token)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));

View File

@ -10,6 +10,9 @@ using System.Text;
namespace Avalonia_API.Configuration
{
/// <summary>
/// API 项目服务配置扩展类,负责注册数据库、鉴权、业务服务和统一端点。
/// </summary>
public static class ServicesConfiguration
{
/// <summary>

View File

@ -95,6 +95,13 @@ namespace Avalonia_API.Extensions
return routeBuilder;
}
/// <summary>
/// 根据端点的 HTTP 方法GET/POST/PUT/DELETE将其映射到 ASP.NET Core 路由。
/// </summary>
/// <param name="group">路由组。</param>
/// <param name="endpoint">统一端点定义。</param>
/// <param name="serviceProvider">服务提供程序。</param>
/// <returns>路由处理器构建器,用于叠加过滤器等配置。</returns>
private static RouteHandlerBuilder MapEndpoint(
IEndpointRouteBuilder group,
ServiceEndpoint endpoint,
@ -112,6 +119,12 @@ namespace Avalonia_API.Extensions
};
}
/// <summary>
/// 创建适配 ASP.NET Core 的委托处理器,将统一处理器包装为 ASP.NET Core 可识别的委托。
/// </summary>
/// <param name="unifiedHandler">统一端点处理器。</param>
/// <param name="serviceProvider">服务提供程序。</param>
/// <returns>ASP.NET Core 兼容的委托。</returns>
private static Delegate CreateAspNetCoreHandler(
Func<ServiceEndpointContext, Task<object?>> unifiedHandler,
IServiceProvider serviceProvider)
@ -135,6 +148,12 @@ namespace Avalonia_API.Extensions
};
}
/// <summary>
/// 从 ASP.NET Core 的 HttpContext 构建统一的 ServiceEndpointContext
/// 提取路径、方法、请求头、查询参数和请求体。
/// </summary>
/// <param name="httpContext">ASP.NET Core 的 HttpContext。</param>
/// <returns>构建好的统一端点上下文。</returns>
private static async Task<ServiceEndpointContext> BuildContextFromHttpContext(HttpContext httpContext)
{
var ctx = new ServiceEndpointContext
@ -166,6 +185,14 @@ namespace Avalonia_API.Extensions
return ctx;
}
/// <summary>
/// 将统一过滤器转换为 ASP.NET Core 端点过滤器,
/// 在调用统一过滤器前后桥接上下文和状态。
/// </summary>
/// <param name="unifiedFilter">统一过滤器。</param>
/// <param name="aspContext">ASP.NET Core 过滤器调用上下文。</param>
/// <param name="aspNext">ASP.NET Core 过滤器管道中的下一个委托。</param>
/// <returns>过滤器执行结果,可能包含短路响应体。</returns>
private static async ValueTask<object?> ConvertFilterAsync(
UnifiedFilter unifiedFilter,
AspNetCoreFilterContext aspContext,

View File

@ -115,27 +115,56 @@ namespace Avalonia_Common.Core
/// </summary>
public class PagedResponse<T>
{
/// <summary>
/// 获取或设置操作是否成功。
/// </summary>
[JsonPropertyName("success")]
public bool Success { get; set; } = true;
/// <summary>
/// 获取或设置业务状态码,默认 200。
/// </summary>
[JsonPropertyName("code")]
public int Code { get; set; } = 200;
/// <summary>
/// 获取或设置分页数据项列表。
/// </summary>
[JsonPropertyName("items")]
public List<T> Items { get; set; } = new();
/// <summary>
/// 获取或设置数据总条数。
/// </summary>
[JsonPropertyName("total")]
public int Total { get; set; }
/// <summary>
/// 获取或设置当前页码,从 1 开始。
/// </summary>
[JsonPropertyName("page")]
public int Page { get; set; } = 1;
/// <summary>
/// 获取或设置每页条数,默认 20。
/// </summary>
[JsonPropertyName("pageSize")]
public int PageSize { get; set; } = 20;
/// <summary>
/// 获取总页数(根据 Total 和 PageSize 自动计算)。
/// </summary>
[JsonPropertyName("totalPages")]
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0;
/// <summary>
/// 从数据列表和分页参数创建分页响应。
/// </summary>
/// <param name="items">当前页数据项。</param>
/// <param name="total">数据总条数。</param>
/// <param name="page">当前页码。</param>
/// <param name="pageSize">每页条数。</param>
/// <returns>分页响应实例。</returns>
public static PagedResponse<T> From(List<T> items, int total, int page, int pageSize)
{
return new PagedResponse<T>

View File

@ -87,6 +87,9 @@ namespace Avalonia_Common.Infrastructure
/// </summary>
public static class AppLog
{
/// <summary>
/// 保存全局日志记录器实例。
/// </summary>
private static ILogger? _logger;
/// <summary>
@ -98,26 +101,66 @@ namespace Avalonia_Common.Infrastructure
Log.Logger = logger;
}
/// <summary>
/// 获取全局日志记录器。若未初始化则回退到 Serilog.Log.Logger。
/// </summary>
public static ILogger Logger => _logger ?? Log.Logger;
/// <summary>
/// 写入 Debug 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Debug(string messageTemplate, params object?[] propertyValues)
=> Logger.Debug(messageTemplate, propertyValues);
/// <summary>
/// 写入 Information 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Information(string messageTemplate, params object?[] propertyValues)
=> Logger.Information(messageTemplate, propertyValues);
/// <summary>
/// 写入 Warning 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Warning(string messageTemplate, params object?[] propertyValues)
=> Logger.Warning(messageTemplate, propertyValues);
/// <summary>
/// 写入 Error 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Error(string messageTemplate, params object?[] propertyValues)
=> Logger.Error(messageTemplate, propertyValues);
/// <summary>
/// 写入 Error 级别日志,并附带异常信息。
/// </summary>
/// <param name="exception">异常对象。</param>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Error(Exception exception, string messageTemplate, params object?[] propertyValues)
=> Logger.Error(exception, messageTemplate, propertyValues);
/// <summary>
/// 写入 Fatal 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Fatal(string messageTemplate, params object?[] propertyValues)
=> Logger.Fatal(messageTemplate, propertyValues);
/// <summary>
/// 写入 Fatal 级别日志,并附带异常信息。
/// </summary>
/// <param name="exception">异常对象。</param>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Fatal(Exception exception, string messageTemplate, params object?[] propertyValues)
=> Logger.Fatal(exception, messageTemplate, propertyValues);
}

View File

@ -19,6 +19,10 @@ namespace Avalonia_EFCore.Database
/// <summary>API refresh token 数据</summary>
public DbSet<ApiRefreshTokenEntity> ApiRefreshTokens => Set<ApiRefreshTokenEntity>();
/// <summary>
/// 配置实体映射,包括主键、索引和属性约束。
/// </summary>
/// <param name="modelBuilder">模型构建器。</param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

View File

@ -2,8 +2,16 @@ using Microsoft.EntityFrameworkCore.Design;
namespace Avalonia_EFCore.Database
{
/// <summary>
/// 设计时 DbContext 工厂,用于 EF Core 迁移工具生成迁移代码。
/// </summary>
public class AppDataContextFactory : IDesignTimeDbContextFactory<AppDataContext>
{
/// <summary>
/// 创建用于设计时的 AppDataContext 实例,默认使用 SQLite 提供程序。
/// </summary>
/// <param name="args">命令行参数。</param>
/// <returns>配置好的数据上下文实例。</returns>
public AppDataContext CreateDbContext(string[] args)
{
DatabaseProviderRegistry.RegisterDefaults();

View File

@ -12,8 +12,15 @@ namespace Avalonia_EFCore.Database
/// </summary>
public abstract class AppDbContext(DatabaseConfiguration dbConfig) : DbContext
{
/// <summary>
/// 数据库配置。
/// </summary>
private readonly DatabaseConfiguration _dbConfig = dbConfig;
/// <summary>
/// 配置数据库提供程序和连接选项。
/// </summary>
/// <param name="optionsBuilder">选项构建器。</param>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured) return;
@ -65,12 +72,21 @@ namespace Avalonia_EFCore.Database
return base.SaveChanges(acceptAllChangesOnSuccess);
}
/// <summary>
/// 异步保存更改,自动设置时间戳。
/// </summary>
/// <param name="acceptAllChangesOnSuccess">是否在成功时接受所有更改。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>受影响的行数。</returns>
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
SetTimestamps();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
/// <summary>
/// 自动设置新增或修改实体的 CreatedAt 和 UpdatedAt 时间戳。
/// </summary>
private void SetTimestamps()
{
var entries = ChangeTracker.Entries()

View File

@ -17,10 +17,25 @@ namespace Avalonia_EFCore.Database
/// </summary>
public class DatabaseManager<TContext> where TContext : AppDbContext
{
/// <summary>
/// 数据库上下文实例。
/// </summary>
private readonly TContext _context;
/// <summary>
/// 数据库配置。
/// </summary>
private readonly DatabaseConfiguration _config;
/// <summary>
/// DI 服务提供程序(可选,用于种子数据中解析服务)。
/// </summary>
private readonly IServiceProvider? _serviceProvider;
/// <summary>
/// 初始化数据库管理器。
/// </summary>
/// <param name="context">数据库上下文。</param>
/// <param name="config">数据库配置。</param>
/// <param name="serviceProvider">可选的 DI 容器。</param>
public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null)
{
_context = context;
@ -128,6 +143,10 @@ namespace Avalonia_EFCore.Database
}
}
/// <summary>
/// 获取当前应用程序的版本号,优先读取 AssemblyInformationalVersion回退到 AssemblyVersion。
/// </summary>
/// <returns>应用程序版本字符串。</returns>
private static string GetApplicationVersion()
{
var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly;
@ -181,10 +200,25 @@ namespace Avalonia_EFCore.Database
/// </summary>
public class DatabaseVersionInfo
{
/// <summary>
/// 获取或设置数据库提供程序名称。
/// </summary>
public string Provider { get; set; } = string.Empty;
/// <summary>
/// 获取或设置已应用的迁移列表。
/// </summary>
public List<string> AppliedMigrations { get; set; } = new();
/// <summary>
/// 获取或设置待应用的迁移列表。
/// </summary>
public List<string> PendingMigrations { get; set; } = new();
/// <summary>
/// 获取或设置是否为最新版本。
/// </summary>
public bool IsLatest { get; set; }
/// <summary>
/// 获取或设置数据库是否可连接。
/// </summary>
public bool CanConnect { get; set; }
}
}

View File

@ -15,6 +15,9 @@ namespace Avalonia_EFCore.Database
/// </summary>
public delegate void ProviderConfigurator(DbContextOptionsBuilder optionsBuilder, string connectionString, int timeout);
/// <summary>
/// 保存已注册的数据库提供程序及其配置委托。
/// </summary>
private static readonly Dictionary<DatabaseProvider, ProviderConfigurator> _providers = new();
/// <summary>

View File

@ -11,39 +11,69 @@ namespace Avalonia_EFCore.Models
[Table("api-refresh-token")]
public class ApiRefreshTokenEntity
{
/// <summary>
/// 获取或设置主键 ID自增
/// </summary>
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
/// <summary>
/// 获取或设置关联的用户 ID。
/// </summary>
[Column("user-id")]
public int UserId { get; set; }
/// <summary>
/// 获取或设置 Token 的 SHA256 哈希值,用于安全存储和查询。
/// </summary>
[Column("token-hash")]
[MaxLength(128)]
public string TokenHash { get; set; } = string.Empty;
/// <summary>
/// 获取或设置 Token 创建时间。
/// </summary>
[Column("created-at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 获取或设置 Token 过期时间。
/// </summary>
[Column("expires-at")]
public DateTime ExpiresAt { get; set; }
/// <summary>
/// 获取或设置 Token 撤销时间null 表示尚未撤销。
/// </summary>
[Column("revoked-at")]
public DateTime? RevokedAt { get; set; }
/// <summary>
/// 获取或设置替换此 Token 的新 Token 哈希值(轮换时设置)。
/// </summary>
[Column("replaced-by-token-hash")]
[MaxLength(128)]
public string? ReplacedByTokenHash { get; set; }
/// <summary>
/// 获取或设置创建设备标识(如 User-Agent
/// </summary>
[Column("device")]
[MaxLength(200)]
public string? Device { get; set; }
/// <summary>
/// 获取或设置创建时的客户端 IP 地址。
/// </summary>
[Column("ip-address")]
[MaxLength(64)]
public string? IpAddress { get; set; }
/// <summary>
/// 获取 Token 是否有效(未被撤销且未过期)。
/// </summary>
public bool IsActive => RevokedAt is null && ExpiresAt > DateTime.UtcNow;
}
}

View File

@ -11,31 +11,49 @@ namespace Avalonia_EFCore.Models
[Table("user")]
public class UserEntity
{
/// <summary>
/// 获取或设置用户主键 ID自增
/// </summary>
[Key]
[Comment("用户主键")]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
/// <summary>
/// 获取或设置用户名称。
/// </summary>
[Comment("用户名称")]
[Column("name")]
[MaxLength(100)]
public string? Name { get; set; }
/// <summary>
/// 获取或设置用户邮箱。
/// </summary>
[Comment("用户邮箱")]
[Column("email")]
[MaxLength(200)]
public string? Email { get; set; }
/// <summary>
/// 获取或设置用户电话号码。
/// </summary>
[Comment("电话号码")]
[Column("phone-number")]
[MaxLength(50)]
public string? PhoneNumber { get; set; }
/// <summary>
/// 获取或设置用户创建时间。
/// </summary>
[Comment("创建时间")]
[Column("created-at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 获取或设置用户最后更新时间。
/// </summary>
[Comment("更新时间")]
[Column("updated-at")]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;

View File

@ -1,13 +1,28 @@
namespace Avalonia_EFCore.Models
{
/// <summary>
/// 天气预报数据模型(内存/DTO 用,非数据库实体)。
/// </summary>
public class WeatherForecast
{
/// <summary>
/// 获取或设置预报日期。
/// </summary>
public DateOnly Date { get; set; }
/// <summary>
/// 获取或设置摄氏温度。
/// </summary>
public int TemperatureC { get; set; }
/// <summary>
/// 获取华氏温度(根据摄氏温度自动计算)。
/// </summary>
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
/// <summary>
/// 获取或设置天气摘要。
/// </summary>
public string? Summary { get; set; }
}
}

View File

@ -11,29 +11,47 @@ namespace Avalonia_EFCore.Models
[Table("weather-forecast")]
public class WeatherForecastEntity
{
/// <summary>
/// 获取或设置天气预报主键 ID自增
/// </summary>
[Key]
[Comment("天气预报主键")]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
/// <summary>
/// 获取或设置预报日期。
/// </summary>
[Comment("预报日期")]
[Column("date")]
public DateOnly Date { get; set; }
/// <summary>
/// 获取或设置摄氏温度。
/// </summary>
[Comment("摄氏温度")]
[Column("temperature-c")]
public int TemperatureC { get; set; }
/// <summary>
/// 获取或设置天气摘要。
/// </summary>
[Comment("天气摘要")]
[Column("summary")]
[MaxLength(200)]
public string? Summary { get; set; }
/// <summary>
/// 获取或设置记录创建时间。
/// </summary>
[Comment("创建时间")]
[Column("created-at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 获取或设置记录最后更新时间。
/// </summary>
[Comment("更新时间")]
[Column("updated-at")]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;

View File

@ -7,13 +7,22 @@ using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_PC
{
/// <summary>
/// Avalonia 应用程序入口类,负责初始化 XAML 资源和设置主窗口。
/// </summary>
public partial class App : Application
{
/// <summary>
/// 加载 Avalonia XAML 资源。
/// </summary>
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
/// <summary>
/// 框架初始化完成后设置主窗口和数据上下文。
/// </summary>
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)

View File

@ -10,6 +10,12 @@ namespace Avalonia_PC.Authentication
/// </summary>
public sealed class DefaultPcThirdPartyAuthorizationClient : IPcThirdPartyAuthorizationClient
{
/// <summary>
/// 验证第三方授权码是否有效。默认实现将 "invalid" 视为授权丢失,其余视为有效。
/// </summary>
/// <param name="authorizationCode">第三方授权码。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
public Task<ThirdPartyAuthCheckResult> ValidateAuthorizationCodeAsync(
string authorizationCode,
CancellationToken cancellationToken = default)
@ -23,6 +29,12 @@ namespace Avalonia_PC.Authentication
return Task.FromResult(ThirdPartyAuthCheckResult.Valid);
}
/// <summary>
/// 刷新第三方授权。默认实现总是返回 TemporaryFailure表示暂时无法刷新。
/// </summary>
/// <param name="authorizationReference">授权引用标识。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
public Task<ThirdPartyAuthCheckResult> RefreshAuthorizationAsync(
string authorizationReference,
CancellationToken cancellationToken = default)

View File

@ -8,6 +8,10 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Authentication
{
/// <summary>
/// PC 端鉴权端点服务,实现 <see cref="IPcAuthEndpointService"/>
/// 处理授权码登录、Token 刷新和登出操作。
/// </summary>
public sealed class PcAuthEndpointService(PcGlobalTokenService tokenService) : IPcAuthEndpointService
{
private static readonly JsonSerializerOptions JsonOptions = new()
@ -15,6 +19,7 @@ namespace Avalonia_PC.Authentication
PropertyNameCaseInsensitive = true,
};
/// <inheritdoc />
public async Task<object?> AuthorizeAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcAuthorizeRequest>(ctx.Body);
@ -28,6 +33,7 @@ namespace Avalonia_PC.Authentication
return ResponseHelper.Ok(token, "授权成功");
}
/// <inheritdoc />
public async Task<object?> RefreshAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcRefreshRequest>(ctx.Body);
@ -42,6 +48,7 @@ namespace Avalonia_PC.Authentication
return ResponseHelper.Ok(refreshed, "刷新成功");
}
/// <inheritdoc />
public Task<object?> LogoutAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcLogoutRequest>(ctx.Body);
@ -50,6 +57,12 @@ namespace Avalonia_PC.Authentication
return Task.FromResult<object?>(ResponseHelper.Succeed("退出成功"));
}
/// <summary>
/// 将 JSON 请求体反序列化为指定类型。
/// </summary>
/// <typeparam name="T">目标类型。</typeparam>
/// <param name="body">JSON 请求体字符串,可为空。</param>
/// <returns>反序列化后的对象;若 body 为空则返回默认值。</returns>
private static T? Deserialize<T>(string? body)
{
return string.IsNullOrWhiteSpace(body)
@ -57,6 +70,11 @@ namespace Avalonia_PC.Authentication
: JsonSerializer.Deserialize<T>(body, JsonOptions);
}
/// <summary>
/// 从 Authorization 头中提取 Bearer Token。
/// </summary>
/// <param name="authorization">Authorization 头的值。</param>
/// <returns>提取的 Token 字符串;若无法提取则返回 null。</returns>
private static string? ExtractBearerToken(string? authorization)
{
if (string.IsNullOrWhiteSpace(authorization))
@ -64,6 +82,9 @@ namespace Avalonia_PC.Authentication
return null;
}
/// <summary>
/// Bearer Token 的前缀常量。
/// </summary>
const string prefix = "Bearer ";
return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? authorization[prefix.Length..].Trim()

View File

@ -6,8 +6,12 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Authentication
{
/// <summary>
/// PC 端鉴权服务,基于全局 Token 验证用户身份,实现 <see cref="IAuthService"/>。
/// </summary>
public sealed class PcAuthService(PcGlobalTokenService tokenService) : IAuthService
{
/// <inheritdoc />
public async Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context)
{
var token = ExtractBearerToken(context.GetHeader("Authorization"));
@ -29,11 +33,17 @@ namespace Avalonia_PC.Authentication
return new ClaimsPrincipal(identity);
}
/// <inheritdoc />
public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy)
{
return Task.FromResult(user.Identity?.IsAuthenticated == true);
}
/// <summary>
/// 从 Authorization 头中提取 Bearer Token。
/// </summary>
/// <param name="authorization">Authorization 头的值。</param>
/// <returns>提取的 Token 字符串;若无法提取则返回 null。</returns>
private static string? ExtractBearerToken(string? authorization)
{
if (string.IsNullOrWhiteSpace(authorization))
@ -41,7 +51,7 @@ namespace Avalonia_PC.Authentication
return null;
}
const string prefix = "Bearer ";
string prefix = "Bearer ";
return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? authorization[prefix.Length..].Trim()
: authorization.Trim();

View File

@ -7,16 +7,45 @@ using System.Threading.Tasks;
namespace Authentication
{
/// <summary>
/// PC 端全局 Token 服务,管理全局唯一的访问 Token。
/// 支持授权码登录、Token 刷新、有效性验证和登出,
/// 在第三方授权暂时失败时使用缩短有效期的临时 Token。
/// </summary>
public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient)
{
/// <summary>
/// 超级管理员角色集合。
/// </summary>
private static readonly string[] SuperRoles = ["SuperAdmin", "Admin"];
/// <summary>
/// 线程同步锁。
/// </summary>
private readonly object _syncRoot = new();
/// <summary>
/// 当前 Token 状态。
/// </summary>
private PcTokenState? _current;
/// <summary>
/// 正常 Token 有效期8 小时)。
/// </summary>
private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8);
/// <summary>
/// 第三方暂时失败时的 Token 有效期20 分钟)。
/// </summary>
private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20);
/// <summary>
/// 第三方暂时失败的最长容忍窗口24 小时),超出后清除 Token。
/// </summary>
private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24);
/// <summary>
/// 使用授权码进行登录授权,验证成功后颁发全局 Token。
/// </summary>
/// <param name="authorizationCode">第三方授权码。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>Token 响应;若授权码无效则返回 null。</returns>
public async Task<PcTokenResponse?> AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(authorizationCode))
@ -33,6 +62,13 @@ namespace Authentication
return IssueToken(authorizationCode, NormalLifetime, resetTemporaryFailureWindow: true);
}
/// <summary>
/// 刷新当前 Token向第三方验证授权引用是否仍然有效。
/// 根据第三方返回结果决定是续期、降级为临时 Token 还是清除。
/// </summary>
/// <param name="token">当前 Token。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>新的 Token 响应;若授权丢失则返回 null。</returns>
public async Task<PcTokenResponse?> RefreshAsync(string? token, CancellationToken cancellationToken = default)
{
PcTokenState? current;
@ -56,6 +92,12 @@ namespace Authentication
};
}
/// <summary>
/// 验证 Token 是否有效,若已过期则尝试自动刷新。
/// </summary>
/// <param name="token">要验证的 Token。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>Token 是否有效。</returns>
public async Task<bool> ValidateAsync(string? token, CancellationToken cancellationToken = default)
{
PcTokenState? current;
@ -76,6 +118,10 @@ namespace Authentication
return await RefreshAsync(token, cancellationToken) is not null;
}
/// <summary>
/// 登出并清除当前 Token。
/// </summary>
/// <param name="token">要清除的 Token。</param>
public void Logout(string? token)
{
lock (_syncRoot)
@ -87,6 +133,13 @@ namespace Authentication
}
}
/// <summary>
/// 颁发新的全局 Token。
/// </summary>
/// <param name="authorizationReference">授权引用标识。</param>
/// <param name="lifetime">Token 有效期。</param>
/// <param name="resetTemporaryFailureWindow">是否重置暂时失败窗口。</param>
/// <returns>包含 Token 和过期时间的响应。</returns>
private PcTokenResponse IssueToken(string authorizationReference, TimeSpan lifetime, bool resetTemporaryFailureWindow)
{
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
@ -105,6 +158,11 @@ namespace Authentication
return new PcTokenResponse(token, state.ExpiresAt, SuperRoles);
}
/// <summary>
/// 在第三方暂时失败时刷新 Token。若超出最大容忍窗口则清除 Token。
/// </summary>
/// <param name="current">当前 Token 状态。</param>
/// <returns>新的临时 Token 响应;若超出容忍窗口则返回 null。</returns>
private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current)
{
var startedAt = current.TemporaryFailureStartedAt ?? DateTime.UtcNow;
@ -116,6 +174,10 @@ namespace Authentication
return IssueToken(current.AuthorizationReference, TemporaryFailureLifetime, resetTemporaryFailureWindow: false);
}
/// <summary>
/// 清除当前 Token 并返回 null。
/// </summary>
/// <returns>始终返回 null。</returns>
private PcTokenResponse? ClearAndReturnNull()
{
lock (_syncRoot)
@ -126,6 +188,11 @@ namespace Authentication
return null;
}
/// <summary>
/// 检查给定 Token 是否与当前持有的 Token 匹配。
/// </summary>
/// <param name="token">要检查的 Token。</param>
/// <returns>是否匹配。</returns>
private bool IsCurrentToken(string? token)
{
return !string.IsNullOrWhiteSpace(token) &&
@ -133,12 +200,24 @@ namespace Authentication
string.Equals(_current.TokenHash, HashToken(token), StringComparison.Ordinal);
}
/// <summary>
/// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。
/// </summary>
/// <param name="token">Token 原文。</param>
/// <returns>SHA256 哈希后的十六进制字符串。</returns>
private static string HashToken(string token)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));
return Convert.ToHexString(bytes);
}
/// <summary>
/// 保存当前全局 Token 的内部状态。
/// </summary>
/// <param name="TokenHash">Token 的 SHA256 哈希值。</param>
/// <param name="AuthorizationReference">授权引用标识。</param>
/// <param name="ExpiresAt">过期时间。</param>
/// <param name="TemporaryFailureStartedAt">第三方暂时失败的起始时间。</param>
private sealed record PcTokenState(
string TokenHash,
string AuthorizationReference,

View File

@ -14,10 +14,20 @@ using System;
namespace Avalonia_PC
{
/// <summary>
/// 桌面应用程序入口类,负责配置 DI 容器、初始化数据库和启动 Avalonia 框架。
/// </summary>
internal sealed class Program
{
/// <summary>
/// 获取全局 DI 服务提供程序。
/// </summary>
public static IServiceProvider Services { get; private set; } = null!;
/// <summary>
/// 应用程序主入口点。
/// </summary>
/// <param name="args">命令行参数。</param>
[STAThread]
public static void Main(string[] args)
{
@ -40,6 +50,9 @@ namespace Avalonia_PC
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
/// <summary>
/// 配置 DI 容器,注册数据库、业务服务、鉴权服务和统一端点。
/// </summary>
private static void ConfigureServices()
{
var services = new ServiceCollection();
@ -71,7 +84,10 @@ namespace Avalonia_PC
Services = services.BuildServiceProvider();
}
// Avalonia configuration, don't remove; also used by visual designer.
/// <summary>
/// 构建 Avalonia 应用程序(供可视化设计器使用,请勿删除)。
/// </summary>
/// <returns>Avalonia 应用构建器。</returns>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()

View File

@ -12,8 +12,18 @@ namespace Avalonia_PC
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
/// <summary>
/// 视图定位器,根据 ViewModel 类型自动查找对应的 View
/// 实现 IDataTemplate 以支持 Avalonia 的数据模板机制。
/// </summary>
public class ViewLocator : IDataTemplate
{
/// <summary>
/// 根据 ViewModel 实例构建对应的 View 控件。
/// 约定:将 ViewModels 命名空间中的 ViewModel 替换为 Views 命名空间中的同名 View。
/// </summary>
/// <param name="param">ViewModel 实例。</param>
/// <returns>对应的 View 控件;若未找到则返回 TextBlock 显示错误信息。</returns>
public Control? Build(object? param)
{
if (param is null)
@ -30,6 +40,11 @@ namespace Avalonia_PC
return new TextBlock { Text = "Not Found: " + name };
}
/// <summary>
/// 判断数据对象是否为 ViewModel 类型(以 "ViewModel" 结尾)。
/// </summary>
/// <param name="data">要判断的数据对象。</param>
/// <returns>是否为 ViewModel。</returns>
public bool Match(object? data)
{
return data is ViewModelBase;

View File

@ -1,7 +1,13 @@
namespace Avalonia_PC.ViewModels
{
/// <summary>
/// 主窗口的 ViewModel提供问候语等绑定属性。
/// </summary>
public partial class MainWindowViewModel : ViewModelBase
{
/// <summary>
/// 获取问候语文本。
/// </summary>
public string Greeting { get; } = "Welcome to Avalonia!";
}
}

View File

@ -2,6 +2,10 @@
namespace Avalonia_PC.ViewModels
{
/// <summary>
/// ViewModel 基类,继承自 CommunityToolkit.Mvvm 的 ObservableObject
/// 提供属性变更通知功能。
/// </summary>
public abstract class ViewModelBase : ObservableObject
{
}

View File

@ -1,5 +1,8 @@
namespace Avalonia_PC.Views
{
/// <summary>
/// MainWindow 的分部类,定义注入 WebView2 的 JavaScript Bridge 脚本。
/// </summary>
public partial class MainWindow
{
private const string BridgeScript = """
@ -106,6 +109,9 @@ if (!window.__appBridgeInstalled) {
});
};
/// <summary>
/// WebView2 Bridge 中的 XMLHttpRequest 替代实现,将 app:// 请求拦截并转为 C# 调用。
/// </summary>
class BridgeXMLHttpRequest {
constructor() {
this._native = new NativeXMLHttpRequest();

View File

@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Views
{
/// <summary>
/// MainWindow 的分部类,负责路由注册和统一端点适配。
/// </summary>
public partial class MainWindow
{
/// <summary>
@ -22,6 +25,9 @@ namespace Avalonia_PC.Views
/// </summary>
private IServiceProvider _services = null!;
/// <summary>
/// 从 DI 获取统一端点集合并构建桌面适配器。
/// </summary>
private void RegisterRoutes()
{
// 从 DI 获取已构建的端点集合

View File

@ -12,23 +12,56 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Views
{
/// <summary>
/// 主窗口,承载 WebView2 控件并管理前后端 Bridge 通信。
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// 自定义协议方案名称。
/// </summary>
private const string AppScheme = "app";
/// <summary>
/// 在线模式下的前端启动 URL。
/// </summary>
private const string? OnlineStartupUrl = "http://localhost:51240";
//private const string? OnlineStartupUrl = null;
/// <summary>
/// 离线模式下的前端本地文件路径,为空则使用在线模式。
/// </summary>
private const string? LocalStartupPath = null;
private static readonly JsonSerializerOptions BridgeJsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>
/// WebView2 原生控件实例。
/// </summary>
private NativeWebView? _webView;
/// <summary>
/// 标记 WebView 事件是否已绑定。
/// </summary>
private bool _eventsAttached;
/// <summary>
/// WebView 适配器对象。
/// </summary>
private object? _webViewAdapter;
/// <summary>
/// 本地 HTTP 服务器实例(离线模式)。
/// </summary>
private HttpListener? _localHttpServer;
/// <summary>
/// 本地 HTTP 服务器的取消令牌源。
/// </summary>
private CancellationTokenSource? _localHttpServerCts;
/// <summary>
/// 本地 HTTP 服务器的基础 URL。
/// </summary>
private string? _localHttpBaseUrl;
/// <summary>
/// 本地 HTTP 服务器的根目录路径。
/// </summary>
private string? _localHttpRoot;
#region WebView
@ -610,18 +643,39 @@ namespace Avalonia_PC.Views
#region DTO /
/// <summary>
/// Bridge 通信响应 DTO用于序列化返回给前端的数据。
/// </summary>
private sealed class AppResponse
{
/// <summary>
/// 获取或设置响应类型标识。
/// </summary>
public string Kind { get; set; } = string.Empty;
/// <summary>
/// 获取或设置请求 ID对应前端请求
/// </summary>
public string? Id { get; set; }
/// <summary>
/// 获取或设置 HTTP 状态码。
/// </summary>
public int StatusCode { get; set; }
/// <summary>
/// 获取或设置状态描述文本。
/// </summary>
public string StatusMessage { get; set; } = string.Empty;
/// <summary>
/// 获取或设置响应体 JSON 字符串。
/// </summary>
public string Body { get; set; } = string.Empty;
/// <summary>
/// 获取或设置响应头字典。
/// </summary>
public Dictionary<string, string> Headers { get; set; } = new();
}

View File

@ -10,9 +10,13 @@ namespace Avalonia_Services.Core
/// </summary>
public sealed class GlobalExceptionFilter : IEndpointFilter
{
/// <summary>
/// 是否在错误响应中包含异常详情。
/// </summary>
private readonly bool _includeDetails;
/// <summary>
/// 初始化全局异常过滤器。
/// </summary>
/// <param name="includeDetails">是否在响应中包含异常详情(开发环境建议 true生产环境 false</param>
public GlobalExceptionFilter(bool includeDetails = false)
@ -20,6 +24,11 @@ namespace Avalonia_Services.Core
_includeDetails = includeDetails;
}
/// <summary>
/// 执行过滤器逻辑:包裹下一个委托,捕获所有未处理异常并转换为统一错误响应。
/// </summary>
/// <param name="context">请求上下文。</param>
/// <param name="next">管道中的下一个委托。</param>
public async Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next)
{
try
@ -73,6 +82,11 @@ namespace Avalonia_Services.Core
}
}
/// <summary>
/// 记录异常日志,优先使用 Serilog不可用时回退到 Console。
/// </summary>
/// <param name="context">请求上下文。</param>
/// <param name="ex">异常对象。</param>
private static void LogException(ServiceEndpointContext context, Exception ex)
{
try

View File

@ -23,13 +23,13 @@ namespace Avalonia_Services.Core
/// </summary>
public sealed class AnonymousAuthService : IAuthService
{
/// <inheritdoc />
public Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context)
{
// 匿名用户,始终通过
var identity = new ClaimsIdentity("anonymous");
return Task.FromResult<ClaimsPrincipal?>(new ClaimsPrincipal(identity));
}
/// <inheritdoc />
public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy)
{

View File

@ -25,13 +25,21 @@ namespace Avalonia_Services.Core
/// </summary>
internal sealed class AnonymousEndpointFilter : IEndpointFilter
{
/// <summary>
/// 匿名过滤器的委托实现。
/// </summary>
private readonly Func<ServiceEndpointContext, EndpointFilterDelegate, Task> _filter;
/// <summary>
/// 使用匿名函数创建过滤器。
/// </summary>
/// <param name="filter">过滤器委托。</param>
public AnonymousEndpointFilter(Func<ServiceEndpointContext, EndpointFilterDelegate, Task> filter)
{
_filter = filter;
}
/// <inheritdoc />
public Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next)
{
return _filter(context, next);

View File

@ -5,11 +5,17 @@ using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_Services.Core
{
/// <summary>
/// 端点挂载的宿主目标。
/// </summary>
[Flags]
public enum EndpointHostTarget
{
/// <summary>挂载到 Avalonia-APIASP.NET Core Web API。</summary>
Api = 1,
/// <summary>挂载到 Avalonia-PC桌面 WebView。</summary>
Pc = 2,
/// <summary>同时挂载到 API 和 PC。</summary>
All = Api | Pc,
}
@ -69,6 +75,15 @@ namespace Avalonia_Services.Core
return this;
}
/// <summary>
/// 设置端点的 OpenAPI 元数据(标签、摘要、描述、请求/响应类型)。
/// </summary>
/// <param name="tag">OpenAPI 分组标签。</param>
/// <param name="summary">简要摘要。</param>
/// <param name="description">详细描述。</param>
/// <param name="requestType">请求体类型。</param>
/// <param name="responseType">成功响应类型。</param>
/// <returns>当前端点实例Fluent API。</returns>
public ServiceEndpoint WithOpenApi(
string tag,
string summary,
@ -122,6 +137,11 @@ namespace Avalonia_Services.Core
return this;
}
/// <summary>
/// 判断端点是否支持指定的宿主目标。
/// </summary>
/// <param name="host">要检查的宿主目标。</param>
/// <returns>是否支持。</returns>
public bool SupportsHost(EndpointHostTarget host)
{
return (HostTarget & host) != 0;
@ -136,6 +156,11 @@ namespace Avalonia_Services.Core
/// <summary>所有已注册的端点</summary>
public List<ServiceEndpoint> Endpoints { get; } = new();
/// <summary>
/// 获取指定宿主目标的所有端点。
/// </summary>
/// <param name="host">宿主目标。</param>
/// <returns>匹配的端点集合。</returns>
public IEnumerable<ServiceEndpoint> ForHost(EndpointHostTarget host)
{
return Endpoints.Where(endpoint => endpoint.SupportsHost(host));
@ -152,6 +177,13 @@ namespace Avalonia_Services.Core
return AddEndpoint(pattern, "GET", handler);
}
/// <summary>
/// 注册一个带服务依赖注入的 GET 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapGet<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
@ -168,6 +200,13 @@ namespace Avalonia_Services.Core
return AddEndpoint(pattern, "POST", handler);
}
/// <summary>
/// 注册一个带服务依赖注入的 POST 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapPost<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
@ -184,6 +223,13 @@ namespace Avalonia_Services.Core
return AddEndpoint(pattern, "PUT", handler);
}
/// <summary>
/// 注册一个带服务依赖注入的 PUT 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapPut<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
@ -200,6 +246,13 @@ namespace Avalonia_Services.Core
return AddEndpoint(pattern, "DELETE", handler);
}
/// <summary>
/// 注册一个带服务依赖注入的 DELETE 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapDelete<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
@ -226,6 +279,13 @@ namespace Avalonia_Services.Core
return this;
}
/// <summary>
/// 内部方法,创建端点并添加到集合。
/// </summary>
/// <param name="pattern">路由路径。</param>
/// <param name="method">HTTP 方法。</param>
/// <param name="handler">端点处理器。</param>
/// <returns>已创建的端点实例。</returns>
private ServiceEndpoint AddEndpoint(string pattern, string method, Func<ServiceEndpointContext, Task<object?>> handler)
{
var endpoint = new ServiceEndpoint
@ -238,6 +298,12 @@ namespace Avalonia_Services.Core
return endpoint;
}
/// <summary>
/// 创建自动从 DI 解析服务实例并调用处理器的委托包装。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>包装后的处理器委托。</returns>
private static Func<ServiceEndpointContext, Task<object?>> CreateServiceHandler<TService>(
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull

View File

@ -89,6 +89,11 @@ namespace Avalonia_Services.Endpoints
return ResponseHelper.Ok(forecasts, "获取天气预报成功(内存生成)");
}
/// <summary>
/// 从数据库获取用户信息(演示数据库查询),若无数据则返回演示用户。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>用户信息。</returns>
private static async Task<object?> GetUserFromDatabaseAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
@ -109,6 +114,11 @@ namespace Avalonia_Services.Endpoints
return ResponseHelper.Ok(user);
}
/// <summary>
/// 处理前端发送的数据POST 演示),将数据存入数据库或转为大写返回。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>处理结果。</returns>
private static async Task<object?> ProcessDataAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;

View File

@ -8,6 +8,10 @@ namespace Avalonia_Services.Endpoints
/// </summary>
public static class AuthEndpoints
{
/// <summary>
/// 配置 API 端鉴权端点(登录、刷新、登出)。
/// </summary>
/// <param name="builder">端点构建器。</param>
public static void ConfigureApi(ServiceEndpointBuilder builder)
{
builder.ConfigureEndpoints(endpoints =>
@ -29,6 +33,10 @@ namespace Avalonia_Services.Endpoints
});
}
/// <summary>
/// 配置 PC 端鉴权端点(授权码登录、刷新、登出)。
/// </summary>
/// <param name="builder">端点构建器。</param>
public static void ConfigurePc(ServiceEndpointBuilder builder)
{
builder.ConfigureEndpoints(endpoints =>

View File

@ -8,8 +8,17 @@ namespace Avalonia_Services.Extensions
/// </summary>
public class DesktopEndpointAdapter
{
/// <summary>
/// 统一端点集合。
/// </summary>
private readonly ServiceEndpointCollection _endpoints;
/// <summary>
/// 鉴权服务。
/// </summary>
private readonly IAuthService _authService;
/// <summary>
/// DI 服务提供程序。
/// </summary>
private readonly IServiceProvider _serviceProvider;
/// <summary>
@ -17,12 +26,33 @@ namespace Avalonia_Services.Extensions
/// </summary>
public class RouteResult
{
/// <summary>
/// 获取是否匹配到路由。
/// </summary>
public bool IsMatched { get; init; }
/// <summary>
/// 获取 HTTP 状态码。
/// </summary>
public int StatusCode { get; init; } = 200;
public string StatusMessage { get; init; } = "OK";
/// <summary>
/// 获取状态描述文本。
/// </summary>
public string StatusMessage { get; init; } = "";
/// <summary>
/// 获取响应数据。
/// </summary>
public object? Data { get; init; }
/// <summary>
/// 获取响应头字典。
/// </summary>
public Dictionary<string, string> ResponseHeaders { get; init; } = new();
/// <summary>
/// 创建成功响应结果。
/// </summary>
/// <param name="data">响应数据。</param>
/// <param name="ctx">端点上下文。</param>
/// <returns>路由结果。</returns>
public static RouteResult Success(object? data, ServiceEndpointContext ctx)
{
return new RouteResult
@ -35,6 +65,10 @@ namespace Avalonia_Services.Extensions
};
}
/// <summary>
/// 创建 404 未找到响应。
/// </summary>
/// <returns>表示未匹配的路由结果。</returns>
public static RouteResult NotFound() => new()
{
IsMatched = false,
@ -43,6 +77,12 @@ namespace Avalonia_Services.Extensions
};
}
/// <summary>
/// 初始化桌面端点适配器。
/// </summary>
/// <param name="endpoints">端点集合。</param>
/// <param name="authService">鉴权服务。</param>
/// <param name="serviceProvider">DI 服务提供程序。</param>
public DesktopEndpointAdapter(
ServiceEndpointCollection endpoints,
IAuthService authService,

View File

@ -1,11 +1,33 @@
namespace Avalonia_Services.Services.AuthService
{
/// <summary>
/// API 登录请求。
/// </summary>
/// <param name="Account">账号(邮箱或用户名)。</param>
/// <param name="Password">密码。</param>
/// <param name="Roles">请求的角色列表。</param>
public sealed record ApiLoginRequest(string? Account, string? Password, string[]? Roles = null);
/// <summary>
/// API Refresh Token 请求。
/// </summary>
/// <param name="RefreshToken">刷新令牌。</param>
public sealed record ApiRefreshTokenRequest(string? RefreshToken);
/// <summary>
/// API 登出请求。
/// </summary>
/// <param name="RefreshToken">要撤销的刷新令牌。</param>
public sealed record ApiLogoutRequest(string? RefreshToken);
/// <summary>
/// 认证 Token 响应,包含 Access Token 和 Refresh Token 及其过期时间。
/// </summary>
/// <param name="AccessToken">访问令牌。</param>
/// <param name="RefreshToken">刷新令牌。</param>
/// <param name="AccessTokenExpiresAt">访问令牌过期时间。</param>
/// <param name="RefreshTokenExpiresAt">刷新令牌过期时间。</param>
/// <param name="Roles">用户角色列表。</param>
public sealed record AuthTokenResponse(
string AccessToken,
string RefreshToken,
@ -13,25 +35,64 @@ namespace Avalonia_Services.Services.AuthService
DateTime RefreshTokenExpiresAt,
string[] Roles);
/// <summary>
/// PC 端授权码登录请求。
/// </summary>
/// <param name="AuthorizationCode">第三方授权码。</param>
public sealed record PcAuthorizeRequest(string? AuthorizationCode);
/// <summary>
/// PC 端 Token 刷新请求。
/// </summary>
/// <param name="Token">当前 Token。</param>
public sealed record PcRefreshRequest(string? Token);
/// <summary>
/// PC 端登出请求。
/// </summary>
/// <param name="Token">要清除的 Token。</param>
public sealed record PcLogoutRequest(string? Token);
/// <summary>
/// PC 端 Token 响应。
/// </summary>
/// <param name="Token">访问令牌。</param>
/// <param name="ExpiresAt">过期时间。</param>
/// <param name="Roles">用户角色列表。</param>
public sealed record PcTokenResponse(string Token, DateTime ExpiresAt, string[] Roles);
/// <summary>
/// 第三方授权检查结果。
/// </summary>
public enum ThirdPartyAuthCheckResult
{
/// <summary>授权有效。</summary>
Valid,
/// <summary>授权已丢失。</summary>
AuthorizationLost,
/// <summary>暂时性失败。</summary>
TemporaryFailure,
}
/// <summary>
/// 第三方授权客户端接口,用于验证和刷新第三方授权。
/// </summary>
public interface IPcThirdPartyAuthorizationClient
{
/// <summary>
/// 验证第三方授权码是否有效。
/// </summary>
/// <param name="authorizationCode">第三方授权码。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
Task<ThirdPartyAuthCheckResult> ValidateAuthorizationCodeAsync(string authorizationCode, CancellationToken cancellationToken = default);
/// <summary>
/// 刷新第三方授权。
/// </summary>
/// <param name="authorizationReference">授权引用标识。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
Task<ThirdPartyAuthCheckResult> RefreshAuthorizationAsync(string authorizationReference, CancellationToken cancellationToken = default);
}
}

View File

@ -3,21 +3,57 @@ using System.Threading.Tasks;
namespace Avalonia_Services.Services.AuthService
{
/// <summary>
/// API 鉴权端点服务接口,定义登录、刷新 Token 和登出操作。
/// </summary>
public interface IApiAuthEndpointService
{
/// <summary>
/// 处理用户登录请求。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>包含 Token 的认证响应。</returns>
Task<object?> LoginAsync(ServiceEndpointContext ctx);
/// <summary>
/// 使用 Refresh Token 刷新 Access Token。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>新的 Token 对。</returns>
Task<object?> RefreshAsync(ServiceEndpointContext ctx);
/// <summary>
/// 处理用户登出请求。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>登出结果。</returns>
Task<object?> LogoutAsync(ServiceEndpointContext ctx);
}
/// <summary>
/// PC 端鉴权端点服务接口定义授权码登录、Token 刷新和登出操作。
/// </summary>
public interface IPcAuthEndpointService
{
/// <summary>
/// 使用授权码进行登录授权。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>包含 Token 的认证响应。</returns>
Task<object?> AuthorizeAsync(ServiceEndpointContext ctx);
/// <summary>
/// 刷新当前 Token。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>新的 Token 响应。</returns>
Task<object?> RefreshAsync(ServiceEndpointContext ctx);
/// <summary>
/// 处理用户登出请求。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>登出结果。</returns>
Task<object?> LogoutAsync(ServiceEndpointContext ctx);
}
}

View File

@ -2,6 +2,9 @@ using Avalonia_EFCore.Models;
namespace Avalonia_Services.Services
{
/// <summary>
/// 天气预报服务,随机生成未来 5 天的天气预报数据。
/// </summary>
public class WeatherForecastService
{
private static readonly string[] Summaries =
@ -9,6 +12,10 @@ namespace Avalonia_Services.Services
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
/// <summary>
/// 生成未来 5 天的随机天气预报数据。
/// </summary>
/// <returns>天气预报数据集合。</returns>
public IEnumerable<WeatherForecast> GetWeatherForecasts()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast

View File

@ -40,11 +40,11 @@ $memberRegexes = @(
},
@{
Kind = "Property"
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|readonly|required|unsafe)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|readonly|required|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
},
@{
Kind = "InterfaceProperty"
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
},
@{
Kind = "Constructor"
@ -52,15 +52,15 @@ $memberRegexes = @(
},
@{
Kind = "Method"
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|async|extern|new|unsafe|partial)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+(?:operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\('
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|async|extern|new|unsafe|partial)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+(?:operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\('
},
@{
Kind = "InterfaceMethod"
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*(?:<[^>]+>)?\s*\([^;{}]*\)\s*;'
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:<[^>]+>)?\s*\([^;{}]*\)\s*;'
},
@{
Kind = "Field"
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|readonly|const|volatile|new|unsafe)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^;]+)?\s*;'
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|readonly|const|volatile|new|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^;]+)?\s*;'
}
)
@ -230,17 +230,15 @@ function Get-MemberName {
}
}
"Method" {
$openParenIndex = $Declaration.IndexOf("(")
$prefix = if ($openParenIndex -ge 0) { $Declaration.Substring(0, $openParenIndex) } else { $Declaration }
if ($prefix -match '(?<name>operator\s*[^\s]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?$') {
return $Matches["name"]
$matches = [regex]::Matches($Declaration, '\s(?<name>operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(')
if ($matches.Count -gt 0) {
return $matches[$matches.Count - 1].Groups["name"].Value
}
}
"InterfaceMethod" {
$openParenIndex = $Declaration.IndexOf("(")
$prefix = if ($openParenIndex -ge 0) { $Declaration.Substring(0, $openParenIndex) } else { $Declaration }
if ($prefix -match '(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?$') {
return $Matches["name"]
$matches = [regex]::Matches($Declaration, '\s(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(')
if ($matches.Count -gt 0) {
return $matches[$matches.Count - 1].Groups["name"].Value
}
}
default {

View File

@ -0,0 +1,23 @@
[
{
"File": "Avalonia-API\\Authentication\\JwtTokenService.cs",
"Line": 20,
"Kind": "Method",
"Name": "CreateAccessToken",
"Declaration": "public (string Token, DateTime ExpiresAt) CreateAccessToken(UserEntity user, IReadOnlyCollection\u003cstring\u003e roles) {"
},
{
"File": "Avalonia-API\\Authentication\\RefreshTokenService.cs",
"Line": 21,
"Kind": "Method",
"Name": "CreateAsync",
"Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)\u003e CreateAsync( int userId, string? device, string? ipAddress, CancellationToken cancellationToken = default) {"
},
{
"File": "Avalonia-API\\Authentication\\RefreshTokenService.cs",
"Line": 78,
"Kind": "Method",
"Name": "RotateAsync",
"Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)?\u003e RotateAsync( string? token, string? device, string? ipAddress, CancellationToken cancellationToken = default) {"
}
]

View File

@ -1,215 +1 @@

File Line Kind Name Declaration
---- ---- ---- ---- -----------
Avalonia-API\Authentication\ApiAuthEndpointService.cs 11 Type ApiAuthEndpointService public sealed class ApiAuthEndpointService( AppDataContext db, JwtTokenService jwtTokenService, RefreshTokenServ...
Avalonia-API\Authentication\ApiAuthEndpointService.cs 21 Method LoginAsync public async Task<object?> LoginAsync(ServiceEndpointContext ctx) {
Avalonia-API\Authentication\ApiAuthEndpointService.cs 59 Method RefreshAsync public async Task<object?> RefreshAsync(ServiceEndpointContext ctx) {
Avalonia-API\Authentication\ApiAuthEndpointService.cs 91 Method LogoutAsync public async Task<object?> LogoutAsync(ServiceEndpointContext ctx) {
Avalonia-API\Authentication\ApiAuthEndpointService.cs 98 Method Deserialize private static T? Deserialize<T>(string? body) {
Avalonia-API\Authentication\ApiAuthEndpointService.cs 105 Method GetRemoteIpAddress private static string? GetRemoteIpAddress(ServiceEndpointContext ctx) {
Avalonia-API\Authentication\ApiAuthEndpointService.cs 112 Method NormalizeRoles private static string[] NormalizeRoles(string[]? roles) {
Avalonia-API\Authentication\JwtOptions.cs 3 Type JwtOptions public sealed class JwtOptions {
Avalonia-API\Authentication\JwtOptions.cs 5 Property Issuer public string Issuer { get; set; } = "";
Avalonia-API\Authentication\JwtOptions.cs 7 Property Audience public string Audience { get; set; } = "";
Avalonia-API\Authentication\JwtOptions.cs 9 Property SigningKey public string SigningKey { get; set; } = "";
Avalonia-API\Authentication\JwtOptions.cs 11 Property AccessTokenMinutes public int AccessTokenMinutes { get; set; } = 60;
Avalonia-API\Authentication\JwtOptions.cs 13 Property RefreshTokenDays public int RefreshTokenDays { get; set; } = 30;
Avalonia-API\Authentication\JwtTokenService.cs 10 Type JwtTokenService public sealed class JwtTokenService(IOptions<JwtOptions> options) {
Avalonia-API\Authentication\JwtTokenService.cs 12 Field _options private readonly JwtOptions _options = options.Value;
Avalonia-API\Authentication\RefreshTokenService.cs 10 Type RefreshTokenService public sealed class RefreshTokenService(AppDataContext db, IOptions<JwtOptions> options) {
Avalonia-API\Authentication\RefreshTokenService.cs 12 Field _options private readonly JwtOptions _options = options.Value;
Avalonia-API\Authentication\RefreshTokenService.cs 35 Method FindActiveAsync public async Task<ApiRefreshTokenEntity?> FindActiveAsync(string? token, CancellationToken cancellationToken = d...
Avalonia-API\Authentication\RefreshTokenService.cs 47 Method RevokeAsync public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default) {
Avalonia-API\Authentication\RefreshTokenService.cs 78 Method HashToken private static string HashToken(string token) {
Avalonia-API\Configuration\ServicesConfiguration.cs 13 Type ServicesConfiguration public static class ServicesConfiguration {
Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 98 Method MapEndpoint private static RouteHandlerBuilder MapEndpoint( IEndpointRouteBuilder group, ServiceEndpoint endpoint, IServiceP...
Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 115 Method CreateAspNetCoreHandler private static Delegate CreateAspNetCoreHandler( Func<ServiceEndpointContext, Task<object?>> unifiedHandler, ISe...
Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 138 Method BuildContextFromHttpContext private static async Task<ServiceEndpointContext> BuildContextFromHttpContext(HttpContext httpContext) {
Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 169 Method ConvertFilterAsync private static async ValueTask<object?> ConvertFilterAsync( UnifiedFilter unifiedFilter, AspNetCoreFilterContext...
Avalonia-Common\Core\ApiResponse.cs 119 Property Success public bool Success { get; set; } = true;
Avalonia-Common\Core\ApiResponse.cs 122 Property Code public int Code { get; set; } = 200;
Avalonia-Common\Core\ApiResponse.cs 125 Property Items public List<T> Items { get; set; } = new();
Avalonia-Common\Core\ApiResponse.cs 128 Property Total public int Total { get; set; }
Avalonia-Common\Core\ApiResponse.cs 131 Property Page public int Page { get; set; } = 1;
Avalonia-Common\Core\ApiResponse.cs 134 Property PageSize public int PageSize { get; set; } = 20;
Avalonia-Common\Core\ApiResponse.cs 137 Field TotalPages public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0;
Avalonia-Common\Core\ApiResponse.cs 139 Method From public static PagedResponse<T> From(List<T> items, int total, int page, int pageSize) {
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 90 Field _logger private static ILogger? _logger;
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 101 Field Logger public static ILogger Logger => _logger ?? Log.Logger;
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 103 Method Debug public static void Debug(string messageTemplate, params object?[] propertyValues) => Logger.Debug(messageTemplat...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 106 Method Information public static void Information(string messageTemplate, params object?[] propertyValues) => Logger.Information(me...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 109 Method Warning public static void Warning(string messageTemplate, params object?[] propertyValues) => Logger.Warning(messageTem...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 112 Method Error public static void Error(string messageTemplate, params object?[] propertyValues) => Logger.Error(messageTemplat...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 115 Method Error public static void Error(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 118 Method Fatal public static void Fatal(string messageTemplate, params object?[] propertyValues) => Logger.Fatal(messageTemplat...
Avalonia-Common\Infrastructure\LoggingConfiguration.cs 121 Method Fatal public static void Fatal(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger...
Avalonia-EFCore\Database\AppDataContext.cs 22 Method OnModelCreating protected override void OnModelCreating(ModelBuilder modelBuilder) {
Avalonia-EFCore\Database\AppDataContextFactory.cs 5 Type AppDataContextFactory public class AppDataContextFactory : IDesignTimeDbContextFactory<AppDataContext> {
Avalonia-EFCore\Database\AppDataContextFactory.cs 7 Method CreateDbContext public AppDataContext CreateDbContext(string[] args) {
Avalonia-EFCore\Database\AppDbContext.cs 15 Field _dbConfig private readonly DatabaseConfiguration _dbConfig = dbConfig;
Avalonia-EFCore\Database\AppDbContext.cs 17 Method OnConfiguring protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
Avalonia-EFCore\Database\AppDbContext.cs 68 Method SaveChangesAsync public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken =...
Avalonia-EFCore\Database\AppDbContext.cs 74 Method SetTimestamps private void SetTimestamps() {
Avalonia-EFCore\Database\DatabaseManager.cs 20 Field _context private readonly TContext _context;
Avalonia-EFCore\Database\DatabaseManager.cs 21 Field _config private readonly DatabaseConfiguration _config;
Avalonia-EFCore\Database\DatabaseManager.cs 22 Field _serviceProvider private readonly IServiceProvider? _serviceProvider;
Avalonia-EFCore\Database\DatabaseManager.cs 24 Constructor DatabaseManager public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null) {
Avalonia-EFCore\Database\DatabaseManager.cs 131 Method GetApplicationVersion private static string GetApplicationVersion() {
Avalonia-EFCore\Database\DatabaseManager.cs 184 Property Provider public string Provider { get; set; } = string.Empty;
Avalonia-EFCore\Database\DatabaseManager.cs 185 Property AppliedMigrations public List<string> AppliedMigrations { get; set; } = new();
Avalonia-EFCore\Database\DatabaseManager.cs 186 Property PendingMigrations public List<string> PendingMigrations { get; set; } = new();
Avalonia-EFCore\Database\DatabaseManager.cs 187 Property IsLatest public bool IsLatest { get; set; }
Avalonia-EFCore\Database\DatabaseManager.cs 188 Property CanConnect public bool CanConnect { get; set; }
Avalonia-EFCore\Database\DatabaseProviderRegistry.cs 18 Field _providers private static readonly Dictionary<DatabaseProvider, ProviderConfigurator> _providers = new();
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 17 Property Id public long Id { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 20 Property UserId public int UserId { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 24 Property TokenHash public string TokenHash { get; set; } = string.Empty;
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 27 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 30 Property ExpiresAt public DateTime ExpiresAt { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 33 Property RevokedAt public DateTime? RevokedAt { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 37 Property ReplacedByTokenHash public string? ReplacedByTokenHash { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 41 Property Device public string? Device { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 45 Property IpAddress public string? IpAddress { get; set; }
Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 47 Field IsActive public bool IsActive => RevokedAt is null && ExpiresAt > DateTime.UtcNow;
Avalonia-EFCore\Models\UserEntity.cs 18 Property Id public int Id { get; set; }
Avalonia-EFCore\Models\UserEntity.cs 23 Property Name public string? Name { get; set; }
Avalonia-EFCore\Models\UserEntity.cs 28 Property Email public string? Email { get; set; }
Avalonia-EFCore\Models\UserEntity.cs 33 Property PhoneNumber public string? PhoneNumber { get; set; }
Avalonia-EFCore\Models\UserEntity.cs 37 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
Avalonia-EFCore\Models\UserEntity.cs 41 Property UpdatedAt public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
Avalonia-EFCore\Models\WeatherForecast.cs 3 Type WeatherForecast public class WeatherForecast {
Avalonia-EFCore\Models\WeatherForecast.cs 5 Property Date public DateOnly Date { get; set; }
Avalonia-EFCore\Models\WeatherForecast.cs 7 Property TemperatureC public int TemperatureC { get; set; }
Avalonia-EFCore\Models\WeatherForecast.cs 9 Field TemperatureF public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
Avalonia-EFCore\Models\WeatherForecast.cs 11 Property Summary public string? Summary { get; set; }
Avalonia-EFCore\Models\WeatherForecastEntity.cs 18 Property Id public int Id { get; set; }
Avalonia-EFCore\Models\WeatherForecastEntity.cs 22 Property Date public DateOnly Date { get; set; }
Avalonia-EFCore\Models\WeatherForecastEntity.cs 26 Property TemperatureC public int TemperatureC { get; set; }
Avalonia-EFCore\Models\WeatherForecastEntity.cs 31 Property Summary public string? Summary { get; set; }
Avalonia-EFCore\Models\WeatherForecastEntity.cs 35 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
Avalonia-EFCore\Models\WeatherForecastEntity.cs 39 Property UpdatedAt public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
Avalonia-PC\App.axaml.cs 10 Type App public partial class App : Application {
Avalonia-PC\App.axaml.cs 12 Method Initialize public override void Initialize() {
Avalonia-PC\App.axaml.cs 17 Method OnFrameworkInitializationCompleted public override void OnFrameworkInitializationCompleted() {
Avalonia-PC\Authentication\DefaultPcThirdPartyAuthorizationClient.cs 13 Method ValidateAuthorizationCodeAsync public Task<ThirdPartyAuthCheckResult> ValidateAuthorizationCodeAsync( string authorizationCode, CancellationTok...
Avalonia-PC\Authentication\DefaultPcThirdPartyAuthorizationClient.cs 26 Method RefreshAuthorizationAsync public Task<ThirdPartyAuthCheckResult> RefreshAuthorizationAsync( string authorizationReference, CancellationTok...
Avalonia-PC\Authentication\PcAuthEndpointService.cs 11 Type PcAuthEndpointService public sealed class PcAuthEndpointService(PcGlobalTokenService tokenService) : IPcAuthEndpointService {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 18 Method AuthorizeAsync public async Task<object?> AuthorizeAsync(ServiceEndpointContext ctx) {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 31 Method RefreshAsync public async Task<object?> RefreshAsync(ServiceEndpointContext ctx) {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 45 Method LogoutAsync public Task<object?> LogoutAsync(ServiceEndpointContext ctx) {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 53 Method Deserialize private static T? Deserialize<T>(string? body) {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 60 Method ExtractBearerToken private static string? ExtractBearerToken(string? authorization) {
Avalonia-PC\Authentication\PcAuthEndpointService.cs 67 Field prefix const string prefix = "";
Avalonia-PC\Authentication\PcAuthService.cs 9 Type PcAuthService public sealed class PcAuthService(PcGlobalTokenService tokenService) : IAuthService {
Avalonia-PC\Authentication\PcAuthService.cs 11 Method AuthenticateAsync public async Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context) {
Avalonia-PC\Authentication\PcAuthService.cs 32 Method AuthorizeAsync public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy) {
Avalonia-PC\Authentication\PcAuthService.cs 37 Method ExtractBearerToken private static string? ExtractBearerToken(string? authorization) {
Avalonia-PC\Authentication\PcAuthService.cs 44 Field prefix const string prefix = "";
Avalonia-PC\Authentication\PcGlobalTokenService.cs 10 Type PcGlobalTokenService public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 12 Field SuperRoles private static readonly string[] SuperRoles = ["", ""];
Avalonia-PC\Authentication\PcGlobalTokenService.cs 13 Field _syncRoot private readonly object _syncRoot = new();
Avalonia-PC\Authentication\PcGlobalTokenService.cs 14 Field _current private PcTokenState? _current;
Avalonia-PC\Authentication\PcGlobalTokenService.cs 16 Field NormalLifetime private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8);
Avalonia-PC\Authentication\PcGlobalTokenService.cs 17 Field TemporaryFailureLifetime private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20);
Avalonia-PC\Authentication\PcGlobalTokenService.cs 18 Field MaxTemporaryFailureWindow private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24);
Avalonia-PC\Authentication\PcGlobalTokenService.cs 20 Method AuthorizeAsync public async Task<PcTokenResponse?> AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToke...
Avalonia-PC\Authentication\PcGlobalTokenService.cs 36 Method RefreshAsync public async Task<PcTokenResponse?> RefreshAsync(string? token, CancellationToken cancellationToken = default) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 59 Method ValidateAsync public async Task<bool> ValidateAsync(string? token, CancellationToken cancellationToken = default) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 79 Method Logout public void Logout(string? token) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 90 Method IssueToken private PcTokenResponse IssueToken(string authorizationReference, TimeSpan lifetime, bool resetTemporaryFailureW...
Avalonia-PC\Authentication\PcGlobalTokenService.cs 108 Method RefreshAfterTemporaryFailure private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 119 Method ClearAndReturnNull private PcTokenResponse? ClearAndReturnNull() {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 129 Method IsCurrentToken private bool IsCurrentToken(string? token) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 136 Method HashToken private static string HashToken(string token) {
Avalonia-PC\Authentication\PcGlobalTokenService.cs 142 Type PcTokenState private sealed record PcTokenState( string TokenHash, string AuthorizationReference, DateTime ExpiresAt, DateTim...
Avalonia-PC\Program.cs 17 Type Program internal sealed class Program {
Avalonia-PC\Program.cs 19 Property Services public static IServiceProvider Services { get; private set; } = null!;
Avalonia-PC\Program.cs 22 Method Main public static void Main(string[] args) {
Avalonia-PC\Program.cs 43 Method ConfigureServices private static void ConfigureServices() {
Avalonia-PC\Program.cs 75 Method BuildAvaloniaApp public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>()
Avalonia-PC\ViewLocator.cs 15 Type ViewLocator public class ViewLocator : IDataTemplate {
Avalonia-PC\ViewLocator.cs 17 Method Build public Control? Build(object? param) {
Avalonia-PC\ViewLocator.cs 33 Method Match public bool Match(object? data) {
Avalonia-PC\ViewModels\MainWindowViewModel.cs 3 Type MainWindowViewModel public partial class MainWindowViewModel : ViewModelBase {
Avalonia-PC\ViewModels\MainWindowViewModel.cs 5 Property Greeting public string Greeting { get; } = "";
Avalonia-PC\ViewModels\ViewModelBase.cs 5 Type ViewModelBase public abstract class ViewModelBase : ObservableObject {
Avalonia-PC\Views\MainWindow.axaml.cs 15 Type MainWindow public partial class MainWindow : Window {
Avalonia-PC\Views\MainWindow.axaml.cs 17 Field AppScheme private const string AppScheme = "";
Avalonia-PC\Views\MainWindow.axaml.cs 18 Field OnlineStartupUrl private const string? OnlineStartupUrl = "";
Avalonia-PC\Views\MainWindow.axaml.cs 20 Field LocalStartupPath private const string? LocalStartupPath = null;
Avalonia-PC\Views\MainWindow.axaml.cs 26 Field _webView private NativeWebView? _webView;
Avalonia-PC\Views\MainWindow.axaml.cs 27 Field _eventsAttached private bool _eventsAttached;
Avalonia-PC\Views\MainWindow.axaml.cs 28 Field _webViewAdapter private object? _webViewAdapter;
Avalonia-PC\Views\MainWindow.axaml.cs 29 Field _localHttpServer private HttpListener? _localHttpServer;
Avalonia-PC\Views\MainWindow.axaml.cs 30 Field _localHttpServerCts private CancellationTokenSource? _localHttpServerCts;
Avalonia-PC\Views\MainWindow.axaml.cs 31 Field _localHttpBaseUrl private string? _localHttpBaseUrl;
Avalonia-PC\Views\MainWindow.axaml.cs 32 Field _localHttpRoot private string? _localHttpRoot;
Avalonia-PC\Views\MainWindow.axaml.cs 613 Type AppResponse private sealed class AppResponse {
Avalonia-PC\Views\MainWindow.axaml.cs 615 Property Kind public string Kind { get; set; } = string.Empty;
Avalonia-PC\Views\MainWindow.axaml.cs 617 Property Id public string? Id { get; set; }
Avalonia-PC\Views\MainWindow.axaml.cs 619 Property StatusCode public int StatusCode { get; set; }
Avalonia-PC\Views\MainWindow.axaml.cs 621 Property StatusMessage public string StatusMessage { get; set; } = string.Empty;
Avalonia-PC\Views\MainWindow.axaml.cs 623 Property Body public string Body { get; set; } = string.Empty;
Avalonia-PC\Views\MainWindow.axaml.cs 625 Property Headers public Dictionary<string, string> Headers { get; set; } = new();
Avalonia-PC\Views\MainWindow.BridgeScript.cs 3 Type MainWindow public partial class MainWindow {
Avalonia-PC\Views\MainWindow.BridgeScript.cs 109 Type BridgeXMLHttpRequest class BridgeXMLHttpRequest {
Avalonia-PC\Views\MainWindow.Routes.cs 12 Type MainWindow public partial class MainWindow {
Avalonia-PC\Views\MainWindow.Routes.cs 25 Method RegisterRoutes private void RegisterRoutes() {
Avalonia-Services\Core\GlobalExceptionFilter.cs 13 Field _includeDetails private readonly bool _includeDetails;
Avalonia-Services\Core\GlobalExceptionFilter.cs 23 Method InvokeAsync public async Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) {
Avalonia-Services\Core\GlobalExceptionFilter.cs 76 Method LogException private static void LogException(ServiceEndpointContext context, Exception ex) {
Avalonia-Services\Core\IAuthService.cs 26 Method AuthenticateAsync public Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context) {
Avalonia-Services\Core\IAuthService.cs 33 Method AuthorizeAsync public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy) {
Avalonia-Services\Core\IEndpointFilter.cs 28 Field _filter private readonly Func<ServiceEndpointContext, EndpointFilterDelegate, Task> _filter;
Avalonia-Services\Core\IEndpointFilter.cs 30 Constructor AnonymousEndpointFilter public AnonymousEndpointFilter(Func<ServiceEndpointContext, EndpointFilterDelegate, Task> filter) {
Avalonia-Services\Core\IEndpointFilter.cs 35 Method InvokeAsync public Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) {
Avalonia-Services\Core\ServiceEndpointCollection.cs 9 Type EndpointHostTarget public enum EndpointHostTarget {
Avalonia-Services\Core\ServiceEndpointCollection.cs 72 Method WithOpenApi public ServiceEndpoint WithOpenApi( string tag, string summary, string? description = null, Type? requestType = ...
Avalonia-Services\Core\ServiceEndpointCollection.cs 125 Method SupportsHost public bool SupportsHost(EndpointHostTarget host) {
Avalonia-Services\Core\ServiceEndpointCollection.cs 139 Method ForHost public IEnumerable<ServiceEndpoint> ForHost(EndpointHostTarget host) {
Avalonia-Services\Core\ServiceEndpointCollection.cs 155 Method MapGet public ServiceEndpoint MapGet<TService>( string pattern, Func<TService, ServiceEndpointContext, Task<object?>> h...
Avalonia-Services\Core\ServiceEndpointCollection.cs 171 Method MapPost public ServiceEndpoint MapPost<TService>( string pattern, Func<TService, ServiceEndpointContext, Task<object?>> ...
Avalonia-Services\Core\ServiceEndpointCollection.cs 187 Method MapPut public ServiceEndpoint MapPut<TService>( string pattern, Func<TService, ServiceEndpointContext, Task<object?>> h...
Avalonia-Services\Core\ServiceEndpointCollection.cs 203 Method MapDelete public ServiceEndpoint MapDelete<TService>( string pattern, Func<TService, ServiceEndpointContext, Task<object?>...
Avalonia-Services\Core\ServiceEndpointCollection.cs 229 Method AddEndpoint private ServiceEndpoint AddEndpoint(string pattern, string method, Func<ServiceEndpointContext, Task<object?>> h...
Avalonia-Services\Core\ServiceEndpointCollection.cs 241 Method CreateServiceHandler private static Func<ServiceEndpointContext, Task<object?>> CreateServiceHandler<TService>( Func<TService, Servic...
Avalonia-Services\Endpoints\AppEndpoints.cs 92 Method GetUserFromDatabaseAsync private static async Task<object?> GetUserFromDatabaseAsync(ServiceEndpointContext ctx) {
Avalonia-Services\Endpoints\AppEndpoints.cs 112 Method ProcessDataAsync private static async Task<object?> ProcessDataAsync(ServiceEndpointContext ctx) {
Avalonia-Services\Endpoints\AuthEndpoints.cs 11 Method ConfigureApi public static void ConfigureApi(ServiceEndpointBuilder builder) {
Avalonia-Services\Endpoints\AuthEndpoints.cs 32 Method ConfigurePc public static void ConfigurePc(ServiceEndpointBuilder builder) {
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 11 Field _endpoints private readonly ServiceEndpointCollection _endpoints;
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 12 Field _authService private readonly IAuthService _authService;
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 13 Field _serviceProvider private readonly IServiceProvider _serviceProvider;
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 20 Property IsMatched public bool IsMatched { get; init; }
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 21 Property StatusCode public int StatusCode { get; init; } = 200;
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 22 Property StatusMessage public string StatusMessage { get; init; } = "";
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 23 Property Data public object? Data { get; init; }
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 24 Property ResponseHeaders public Dictionary<string, string> ResponseHeaders { get; init; } = new();
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 26 Method Success public static RouteResult Success(object? data, ServiceEndpointContext ctx) {
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 38 Method NotFound public static RouteResult NotFound() => new()
Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 46 Constructor DesktopEndpointAdapter public DesktopEndpointAdapter( ServiceEndpointCollection endpoints, IAuthService authService, IServiceProvider s...
Avalonia-Services\Services\AuthService\AuthContracts.cs 3 Type ApiLoginRequest public sealed record ApiLoginRequest(string? Account, string? Password, string[]? Roles = null);
Avalonia-Services\Services\AuthService\AuthContracts.cs 5 Type ApiRefreshTokenRequest public sealed record ApiRefreshTokenRequest(string? RefreshToken);
Avalonia-Services\Services\AuthService\AuthContracts.cs 7 Type ApiLogoutRequest public sealed record ApiLogoutRequest(string? RefreshToken);
Avalonia-Services\Services\AuthService\AuthContracts.cs 9 Type AuthTokenResponse public sealed record AuthTokenResponse( string AccessToken, string RefreshToken, DateTime AccessTokenExpiresAt, ...
Avalonia-Services\Services\AuthService\AuthContracts.cs 16 Type PcAuthorizeRequest public sealed record PcAuthorizeRequest(string? AuthorizationCode);
Avalonia-Services\Services\AuthService\AuthContracts.cs 18 Type PcRefreshRequest public sealed record PcRefreshRequest(string? Token);
Avalonia-Services\Services\AuthService\AuthContracts.cs 20 Type PcLogoutRequest public sealed record PcLogoutRequest(string? Token);
Avalonia-Services\Services\AuthService\AuthContracts.cs 22 Type PcTokenResponse public sealed record PcTokenResponse(string Token, DateTime ExpiresAt, string[] Roles);
Avalonia-Services\Services\AuthService\AuthContracts.cs 24 Type ThirdPartyAuthCheckResult public enum ThirdPartyAuthCheckResult {
Avalonia-Services\Services\AuthService\AuthContracts.cs 31 Type IPcThirdPartyAuthorizationClient public interface IPcThirdPartyAuthorizationClient {
Avalonia-Services\Services\AuthService\AuthContracts.cs 33 InterfaceMethod ValidateAuthorizationCodeAsync Task<ThirdPartyAuthCheckResult> ValidateAuthorizationCodeAsync(string authorizationCode, CancellationToken cance...
Avalonia-Services\Services\AuthService\AuthContracts.cs 35 InterfaceMethod RefreshAuthorizationAsync Task<ThirdPartyAuthCheckResult> RefreshAuthorizationAsync(string authorizationReference, CancellationToken cance...
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 6 Type IApiAuthEndpointService public interface IApiAuthEndpointService {
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 8 InterfaceMethod LoginAsync Task<object?> LoginAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 10 InterfaceMethod RefreshAsync Task<object?> RefreshAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 12 InterfaceMethod LogoutAsync Task<object?> LogoutAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 15 Type IPcAuthEndpointService public interface IPcAuthEndpointService {
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 17 InterfaceMethod AuthorizeAsync Task<object?> AuthorizeAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 19 InterfaceMethod RefreshAsync Task<object?> RefreshAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 21 InterfaceMethod LogoutAsync Task<object?> LogoutAsync(ServiceEndpointContext ctx);
Avalonia-Services\Services\WeatherForecastService.cs 5 Type WeatherForecastService public class WeatherForecastService {
Avalonia-Services\Services\WeatherForecastService.cs 12 Method GetWeatherForecasts public IEnumerable<WeatherForecast> GetWeatherForecasts() {