Compare commits

..

9 Commits

Author SHA1 Message Date
Diego
1860c5f215 build:10.6.1 2025-05-15 09:12:33 +08:00
Diego
6d778b2d39 增加initDatabase配置项 2025-05-15 09:08:08 +08:00
Diego
f48b99c259 更新示例 2025-05-14 21:12:51 +08:00
Diego
3c73b93051 更新依赖 2025-05-14 18:52:19 +08:00
Diego
98f3f2d519 添加 `过滤离线变量` 插件属性 2025-05-14 13:01:12 +08:00
Diego
b76b4e8d68 变量表索引删除语句兼容性增强 2025-05-13 19:27:50 +08:00
Diego
07285a7c61 mqtt增加qos属性 2025-05-13 19:27:27 +08:00
Diego
03c0dfef37 增加业务设备日志 2025-05-12 15:26:51 +08:00
Diego
6ef6929c35 优化api权限树 2025-05-12 10:21:41 +08:00
222 changed files with 4449 additions and 2360 deletions

View File

@@ -26,6 +26,7 @@ namespace ThingsGateway.Admin.Application;
[Route("openapi/auth")] [Route("openapi/auth")]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer")]
[LoggingMonitor] [LoggingMonitor]
[ApiController]
public class OpenApiController : ControllerBase public class OpenApiController : ControllerBase
{ {
private readonly IAuthService _authService; private readonly IAuthService _authService;

View File

@@ -15,16 +15,13 @@ namespace ThingsGateway.Admin.Application;
[Route("api/[controller]/[action]")] [Route("api/[controller]/[action]")]
[AllowAnonymous] [AllowAnonymous]
[ApiController]
public class TestController : ControllerBase public class TestController : ControllerBase
{ {
[HttpPost] [HttpGet]
public Task Test(string data) public void Test()
{ {
for (int i = 0; i < 3; i++) GC.Collect();
{ GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
return Task.CompletedTask;
} }
} }

View File

@@ -1,4 +1,15 @@
{ {
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "User expired, please login again" "UserExpire": "User expired, please login again"
}, },
@@ -24,9 +35,6 @@
"LatestLoginTime": "LatestLoginTime", "LatestLoginTime": "LatestLoginTime",
"LatestLoginDevice": "LatestLoginDevice", "LatestLoginDevice": "LatestLoginDevice",
"LatestLoginAddress": "LatestLoginAddress", "LatestLoginAddress": "LatestLoginAddress",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"OrgNames": "OrgNames", "OrgNames": "OrgNames",
"PositionName": "PositionName", "PositionName": "PositionName",
"OrgId": "Org", "OrgId": "Org",
@@ -60,9 +68,6 @@
"Name": "Name", "Name": "Name",
"Name.Required": "{0} is required", "Name.Required": "{0} is required",
"Category": "Category", "Category": "Category",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"OrgId": "Org", "OrgId": "Org",
"Global": "Global", "Global": "Global",
"Status": "Status", "Status": "Status",
@@ -105,9 +110,6 @@
"Category": "Category", "Category": "Category",
"Target": "Target", "Target": "Target",
"NavLinkMatch": "NavLinkMatch", "NavLinkMatch": "NavLinkMatch",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"ParentId": "Parent", "ParentId": "Parent",
"ResourceDup": "Duplicate name {0} exists", "ResourceDup": "Duplicate name {0} exists",
"ResourceParentChoiceSelf": "Parent cannot choose itself", "ResourceParentChoiceSelf": "Parent cannot choose itself",
@@ -134,9 +136,6 @@
"Status": "Status", "Status": "Status",
"OrgId": "Organization", "OrgId": "Organization",
"Remark": "Remarks", "Remark": "Remarks",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"Dup": "Duplicate position exists with Category {0} and Name {1}", "Dup": "Duplicate position exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists", "CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists", "NameDup": "Duplicate name {0} exists",
@@ -159,9 +158,6 @@
"Names": "Names", "Names": "Names",
"Remark": "Remarks", "Remark": "Remarks",
"DirectorId": "Director", "DirectorId": "Director",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"Dup": "Duplicate organization exists with Category {0} and Name {1}", "Dup": "Duplicate organization exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists", "CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists", "NameDup": "Duplicate name {0} exists",
@@ -358,9 +354,6 @@
"Name": "Name", "Name": "Name",
"Code": "Code", "Code": "Code",
"Remark": "Remark", "Remark": "Remark",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings", "DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings",
"DictDup": "Duplicate configuration exists, category {0}, name {1}" "DictDup": "Duplicate configuration exists, category {0}, name {1}"
}, },

View File

@@ -1,469 +1,462 @@
{ {
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"SortCode": "排序",
"CreateTime": "创建时间",
"CreateUser": "创建人",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "用户登录已过期,请重新登录" "UserExpire": "用户登录已过期,请重新登录"
}, },
"ThingsGateway.Admin.Application.SysUser": { "ThingsGateway.Admin.Application.SysUser": {
"Disable": "禁用", "Disable": "禁用",
"Enable": "启用", "Enable": "启用",
"GrantRole": "分配角色", "GrantRole": "分配角色",
"ExitVerificat": "您已被强制下线", "ExitVerificat": "您已被强制下线",
"PasswordEdited": "密码被修改,已退出登录", "PasswordEdited": "密码被修改,已退出登录",
"Avatar": "头像", "Avatar": "头像",
"Account": "账号", "Account": "账号",
"Account.Required": " {0} 是必填项", "Account.Required": " {0} 是必填项",
"Password": "密码", "Password": "密码",
"Status": "状态", "Status": "状态",
"Phone": "手机", "Phone": "手机",
"Email": "邮箱", "Email": "邮箱",
"LastLoginIp": "上次登录ip", "LastLoginIp": "上次登录ip",
"LastLoginDevice": "上次登录设备", "LastLoginDevice": "上次登录设备",
"LastLoginTime": "上次登录时间", "LastLoginTime": "上次登录时间",
"LastLoginAddress": "上次登录地点", "LastLoginAddress": "上次登录地点",
"LatestLoginIp": "最新登录ip", "LatestLoginIp": "最新登录ip",
"LatestLoginTime": "最新登录时间", "LatestLoginTime": "最新登录时间",
"LatestLoginDevice": "最新登录设备", "LatestLoginDevice": "最新登录设备",
"LatestLoginAddress": "最新登录地点", "LatestLoginAddress": "最新登录地点",
"SortCode": "排序", "OrgNames": "机构名称",
"CreateTime": "创建时间", "PositionName": "职位名称",
"UpdateTime": "更新时间", "OrgId": "机构",
"OrgNames": "机构名称", "PositionId": "职位",
"PositionName": "职位名称", "DirectorId": "主管",
"OrgId": "机构", "CheckSelf": "禁止 {0} 自己",
"PositionId": "职位", "CanotDeleteAdminUser": "不可删除系统内置超管用户",
"DirectorId": "主管", "CanotEditAdminUser": "不可编辑超管用户",
"CheckSelf": "禁止 {0} 自己", "CanotGrantAdmin": "不能分配超管角色",
"CanotDeleteAdminUser": "不可删除系统内置超管用户", "EmailDup": "存在重复的邮箱 {0}",
"CanotEditAdminUser": "不可编辑超管用户", "AccountDup": "存在重复的账号 {0}",
"CanotGrantAdmin": "不能分配超管角色", "CanotDeleteSelf": "不可删除自己",
"EmailDup": "存在重复的邮箱 {0}", "EmailError": "邮箱 {0} 格式错误",
"AccountDup": "存在重复的账号 {0}", "PhoneError": "手机号码 {0} 格式错误",
"CanotDeleteSelf": "不可删除自己", "NoOrg": "组织机构不存在",
"EmailError": "邮箱 {0} 格式错误", "DirectorSelf": "不能设置自己为主管",
"PhoneError": "手机号码 {0} 格式错误",
"NoOrg": "组织机构不存在",
"DirectorSelf": "不能设置自己为主管",
"DemoCanotUpdatePassword": "DEMO环境不允许修改密码", "DemoCanotUpdatePassword": "DEMO环境不允许修改密码",
"OldPasswordError": "原密码错误", "OldPasswordError": "原密码错误",
"ConfirmPasswordDiff": "两次输入的密码不一致", "ConfirmPasswordDiff": "两次输入的密码不一致",
"PasswordLengthLess": "密码长度不能小于 {0} ", "PasswordLengthLess": "密码长度不能小于 {0} ",
"PasswordMustNum ": "密码必须包含数字", "PasswordMustNum ": "密码必须包含数字",
"PasswordMustLow": "密码必须包含小写字母", "PasswordMustLow": "密码必须包含小写字母",
"PasswordMustUpp": "密码必须包含大写字母", "PasswordMustUpp": "密码必须包含大写字母",
"PasswordMustSpecial": "密码必须包含特殊字符" "PasswordMustSpecial": "密码必须包含特殊字符"
}, },
"ThingsGateway.Admin.Application.SysRole": { "ThingsGateway.Admin.Application.SysRole": {
"Code": "编码", "Code": "编码",
"Name": "名称", "Name": "名称",
"Name.Required": " {0} 是必填项", "Name.Required": " {0} 是必填项",
"Category": "分类", "Category": "分类",
"SortCode": "排序", "Global": "全局",
"Global": "全局", "Status": "状态",
"Status": "状态", "OrgId": "机构",
"OrgId": "机构",
"CreateTime": "创建时间",
"UpdateTime": "更新时间",
"CanotDeleteAdmin": "不可删除系统内置超管角色", "CanotDeleteAdmin": "不可删除系统内置超管角色",
"CanotEditAdmin": "不可编辑超管角色", "CanotEditAdmin": "不可编辑超管角色",
"CanotGrantAdmin": "不能分配超管角色", "CanotGrantAdmin": "不能分配超管角色",
"NameDup": "存在重复的角色名称 {0}", "NameDup": "存在重复的角色名称 {0}",
"OrgNotNull": "机构不能为空", "OrgNotNull": "机构不能为空",
"SameOrgNameDup": "存在重复的角色名称 {0}", "SameOrgNameDup": "存在重复的角色名称 {0}",
"CannotRoleScopeAll": "机构角色不能选择全局数据范围", "CannotRoleScopeAll": "机构角色不能选择全局数据范围",
"CodeDup": "存在重复的编码 {0}" "CodeDup": "存在重复的编码 {0}"
}, },
"ThingsGateway.Admin.Application.RoleCategoryEnum": { "ThingsGateway.Admin.Application.RoleCategoryEnum": {
"Global": "全局", "Global": "全局",
"Org": "机构" "Org": "机构"
}, },
"ThingsGateway.Admin.Application.DataScopeEnum": { "ThingsGateway.Admin.Application.DataScopeEnum": {
"SCOPE_SELF": "仅自己", "SCOPE_SELF": "仅自己",
"SCOPE_ALL": "全部", "SCOPE_ALL": "全部",
"SCOPE_ORG": "仅所属组织", "SCOPE_ORG": "仅所属组织",
"SCOPE_ORG_CHILD": "所属组织及以下", "SCOPE_ORG_CHILD": "所属组织及以下",
"SCOPE_ORG_DEFINE": "自定义" "SCOPE_ORG_DEFINE": "自定义"
}, },
"ThingsGateway.Admin.Application.DefaultDataScope": { "ThingsGateway.Admin.Application.DefaultDataScope": {
"ScopeCategory": "数据范围", "ScopeCategory": "数据范围",
"ScopeDefineOrgIdList": "自定义列表" "ScopeDefineOrgIdList": "自定义列表"
}, },
"ThingsGateway.Admin.Application.SysResource": { "ThingsGateway.Admin.Application.SysResource": {
"Title": "标题", "Title": "标题",
"Module": "模块", "Module": "模块",
"Title.Required": "{0} 是必填项", "Title.Required": "{0} 是必填项",
"Href.Required": "{0} 是必填项", "Href.Required": "{0} 是必填项",
"Icon": "图标", "Icon": "图标",
"Href": "路径", "Href": "路径",
"Code": "编码", "Code": "编码",
"Category": "分类", "Category": "分类",
"Target": "跳转类型", "Target": "跳转类型",
"NavLinkMatch": "匹配类型", "NavLinkMatch": "匹配类型",
"SortCode": "排序", "ParentId": "上级菜单",
"ParentId": "上级菜单", "ResourceDup": "存在重复的名称 {0}",
"CreateTime": "创建时间", "ResourceParentChoiceSelf": "父级不能选择自己",
"UpdateTime": "更新时间", "ResourceParentNull": "父级不存在 {0}",
"ResourceDup": "存在重复的名称 {0}", "NotFoundResource": "系统异常,没找到该菜单",
"ResourceParentChoiceSelf": "父级不能选择自己", "ModuleIdDiff": "模块与上级菜单不一致",
"ResourceParentNull": "父级不存在 {0}", "CanotDeleteSystemResource": "不可删除系统资源 {0}",
"NotFoundResource": "系统异常,没找到该菜单", "ResourceMenuHrefNotNull": "菜单的路径不能为空"
"ModuleIdDiff": "模块与上级菜单不一致", },
"CanotDeleteSystemResource": "不可删除系统资源 {0}",
"ResourceMenuHrefNotNull": "菜单的路径不能为空"
},
"ThingsGateway.Admin.Application.SysOrgCopyInput": { "ThingsGateway.Admin.Application.SysOrgCopyInput": {
"TargetId": "目标机构", "TargetId": "目标机构",
"ContainsChild": "包含下级", "ContainsChild": "包含下级",
"ContainsPosition": "包含职位" "ContainsPosition": "包含职位"
}, },
"ThingsGateway.Admin.Application.SysPosition": { "ThingsGateway.Admin.Application.SysPosition": {
"Category.Required": "{0} 是必填项", "Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项", "Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项", "Code.Required": "{0} 是必填项",
"OrgId.MinValue": "{0} 是必填项", "OrgId.MinValue": "{0} 是必填项",
"Category": "分类", "Category": "分类",
"Name": "名称", "Name": "名称",
"Code": "代码", "Code": "代码",
"Status": "状态", "Status": "状态",
"OrgId": "机构", "OrgId": "机构",
"Remark": "备注", "Remark": "备注",
"SortCode": "排序", "Dup": "存在重复的岗位 分类 {0} 名称 {1}",
"CreateTime": "创建时间", "CodeDup": "存在重复的编码 {0}",
"UpdateTime": "更新时间", "NameDup": "存在重复的名称 {0}",
"Dup": "存在重复的岗位 分类 {0} 名称 {1}", "CanotContainsSelf": "不可包含自己",
"CodeDup": "存在重复的编码 {0}", "TargetNameDup": "目标节点存在重复的名称 {0}",
"NameDup": "存在重复的名称 {0}", "ParentChoiceSelf": "父级不能选择自己",
"CanotContainsSelf": "不可包含自己", "ParentNull": "父级不存在 {0}",
"TargetNameDup": "目标节点存在重复的名称 {0}", "DeleteUserFirst": "请先删除职位下的用户"
"ParentChoiceSelf": "父级不能选择自己",
"ParentNull": "父级不存在 {0}",
"DeleteUserFirst": "请先删除职位下的用户"
}, },
"ThingsGateway.Admin.Application.SysOrg": { "ThingsGateway.Admin.Application.SysOrg": {
"Category.Required": "{0} 是必填项", "Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项", "Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项", "Code.Required": "{0} 是必填项",
"Category": "分类", "Category": "分类",
"Name": "名称", "Name": "名称",
"Code": "代码", "Code": "代码",
"Status": "状态", "Status": "状态",
"ParentId": "上级机构", "ParentId": "上级机构",
"Names": "机构全称", "Names": "机构全称",
"Remark": "备注", "Remark": "备注",
"DirectorId": "主管", "DirectorId": "主管",
"SortCode": "排序", "Dup": "存在重复的机构 分类 {0} 名称 {1}",
"CreateTime": "创建时间", "CodeDup": "存在重复的编码 {0}",
"UpdateTime": "更新时间", "NameDup": "存在重复的名称 {0}",
"Dup": "存在重复的机构 分类 {0} 名称 {1}", "CanotContainsSelf": "不可包含自己",
"CodeDup": "存在重复的编码 {0}", "TargetNameDup": "目标节点存在重复的名称 {0}",
"NameDup": "存在重复的名称 {0}", "ParentChoiceSelf": "父级不能选择自己",
"CanotContainsSelf": "不可包含自己", "ParentNull": "父级不存在 {0}",
"TargetNameDup": "目标节点存在重复的名称 {0}", "DeleteUserFirst": "请先删除机构下的用户",
"ParentChoiceSelf": "父级不能选择自己", "DeleteRoleFirst": "请先删除机构下的角色",
"ParentNull": "父级不存在 {0}", "DeletePositionFirst": "请先删除机构下的职位",
"DeleteUserFirst": "请先删除机构下的用户", "RootOrg": "无法创建顶层机构"
"DeleteRoleFirst": "请先删除机构下的角色", },
"DeletePositionFirst": "请先删除机构下的职位", "ThingsGateway.Admin.Application.OrgEnum": {
"RootOrg": "无法创建顶层机构" "COMPANY": "公司",
}, "DEPT": "部门"
"ThingsGateway.Admin.Application.OrgEnum": { },
"COMPANY": "公司", "ThingsGateway.Admin.Application.PositionCategoryEnum": {
"DEPT": "部门" "HIGH": "高层",
}, "MIDDLE": "中层",
"ThingsGateway.Admin.Application.PositionCategoryEnum": { "LOW": "低层"
"HIGH": "高层", },
"MIDDLE": "中层",
"LOW": "低层"
},
//controller //controller
"ThingsGateway.Admin.Application.AuthController": { "ThingsGateway.Admin.Application.AuthController": {
//auth //auth
"AuthController": "登录API", "AuthController": "登录API",
"LoginAsync": "登录", "LoginAsync": "登录",
"LogoutAsync": "注销" "LogoutAsync": "注销"
}, },
"ThingsGateway.Admin.Application.TestController": { "ThingsGateway.Admin.Application.TestController": {
//auth //auth
"TestController": "测试API", "TestController": "测试API",
"Test": "测试" "Test": "测试"
}, },
"ThingsGateway.Admin.Application.OpenApiAuthController": { "ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth //auth
"OpenApiAuthController": "登录API", "OpenApiAuthController": "登录API",
"LoginAsync": "登录", "LoginAsync": "登录",
"LogoutAsync": "注销" "LogoutAsync": "注销"
}, },
"ThingsGateway.Admin.Application.FileService": { "ThingsGateway.Admin.Application.FileService": {
"FileNullError": "文件不能为空", "FileNullError": "文件不能为空",
"FileLengthError": "文件大小不允许超过 {0} M", "FileLengthError": "文件大小不允许超过 {0} M",
"FileTypeError": "不支持 {0} 格式" "FileTypeError": "不支持 {0} 格式"
}, },
"ThingsGateway.Admin.Application.UnifyResultProvider": { "ThingsGateway.Admin.Application.UnifyResultProvider": {
"TokenOver": "登录已过期,请重新登录", "TokenOver": "登录已过期,请重新登录",
"NoPermission": "禁止访问,没有权限" "NoPermission": "禁止访问,没有权限"
}, },
"ThingsGateway.Admin.Application.AuthService": { "ThingsGateway.Admin.Application.AuthService": {
"TenantNull": "租户不存在", "TenantNull": "租户不存在",
"OrgDisable": "所属公司/部门已停用,请联系管理员", "OrgDisable": "所属公司/部门已停用,请联系管理员",
"SingleLoginWarn": "您的账号已在别处登录", "SingleLoginWarn": "您的账号已在别处登录",
"UserNull": "用户 {0} 不存在", "UserNull": "用户 {0} 不存在",
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试", "PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ", "AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
"UserDisable": "账号 {0} 已停用", "UserDisable": "账号 {0} 已停用",
"MustDesc": "密码需要DESC加密后传入", "MustDesc": "密码需要DESC加密后传入",
"UserNoModule": "该账号未分配模块,请联系管理员" "UserNoModule": "该账号未分配模块,请联系管理员"
}, },
"ThingsGateway.Admin.Application.HardwareInfo": { "ThingsGateway.Admin.Application.HardwareInfo": {
"Environment": "主机环境", "Environment": "主机环境",
"FrameworkDescription": "NET框架", "FrameworkDescription": "NET框架",
"OsArchitecture": "系统架构", "OsArchitecture": "系统架构",
"UUID": "唯一编码", "UUID": "唯一编码",
"UpdateTime": "更新时间" "UpdateTime": "更新时间"
}, },
"ThingsGateway.Admin.Application.HistoryHardwareInfo": { "ThingsGateway.Admin.Application.HistoryHardwareInfo": {
"DriveUsage": "磁盘使用率", "DriveUsage": "磁盘使用率",
"MemoryUsage": "内存", "MemoryUsage": "内存",
"CpuUsage": "CPU使用率", "CpuUsage": "CPU使用率",
"Temperature": "温度", "Temperature": "温度",
"Battery": "电池" "Battery": "电池"
}, },
//oper //oper
"ThingsGateway.Admin.Application.OperDescAttribute": { "ThingsGateway.Admin.Application.OperDescAttribute": {
//dict //dict
"SaveDict": "修改字典", "SaveDict": "修改字典",
"DeleteDict": "删除字典", "DeleteDict": "删除字典",
"EditLoginPolicy": "修改登录策略", "EditLoginPolicy": "修改登录策略",
"EditPasswordPolicy": "修改密码策略", "EditPasswordPolicy": "修改密码策略",
"EditPagePolicy": "修改页面策略", "EditPagePolicy": "修改页面策略",
"EditWebsitePolicy": "修改网站设置", "EditWebsitePolicy": "修改网站设置",
//operlog //operlog
"DeleteOperLog": "删除操作日志", "DeleteOperLog": "删除操作日志",
"ExportOperLog": "导出操作日志", "ExportOperLog": "导出操作日志",
//resource //resource
"SaveResource": "修改资源", "SaveResource": "修改资源",
"DeleteResource": "删除资源", "DeleteResource": "删除资源",
//role //role
"SaveRole": "修改角色", "SaveRole": "修改角色",
"DeleteRole": "删除角色", "DeleteRole": "删除角色",
"RoleGrantResource": "角色授权资源", "RoleGrantResource": "角色授权资源",
"RoleGrantUser": "角色授权用户", "RoleGrantUser": "角色授权用户",
"RoleGrantApiPermission": "角色授权OpenApi", "RoleGrantApiPermission": "角色授权OpenApi",
"GrantApi": "API", "GrantApi": "API",
"GrantUser": "用户", "GrantUser": "用户",
"GrantRole": "角色", "GrantRole": "角色",
"GrantResource": "资源", "GrantResource": "资源",
//user //user
"SaveUser": "修改用户", "SaveUser": "修改用户",
"DeleteuSER": "删除用户", "DeleteuSER": "删除用户",
"ResetPassword": "重置密码", "ResetPassword": "重置密码",
"UserGrantRole": "用户授权角色", "UserGrantRole": "用户授权角色",
"UserGrantResource": "用户授权资源", "UserGrantResource": "用户授权资源",
"UserGrantApiPermission": "用户授权OpenApi", "UserGrantApiPermission": "用户授权OpenApi",
//usercenter //usercenter
"UpdateUserInfo": "更新个人信息", "UpdateUserInfo": "更新个人信息",
"WorkbenchInfo": "更新个人工作台", "WorkbenchInfo": "更新个人工作台",
"UpdatePassword": "更新个人密码", "UpdatePassword": "更新个人密码",
//session //session
"ExitVerificat": "强退令牌", "ExitVerificat": "强退令牌",
"ExitSession": "强退会话", "ExitSession": "强退会话",
"CopyOrg": "复制机构", "CopyOrg": "复制机构",
"DeleteOrg": "删除机构", "DeleteOrg": "删除机构",
"SaveOrg": "保存机构", "SaveOrg": "保存机构",
"DeletePosition": "删除岗位", "DeletePosition": "删除岗位",
"SavePosition": "保存岗位", "SavePosition": "保存岗位",
"NoPermission": "无权限操作", "NoPermission": "无权限操作",
"CopyResource": "复制资源", "CopyResource": "复制资源",
"ChangeParentResource": "更改父节点" "ChangeParentResource": "更改父节点"
}, },
//service //service
"ThingsGateway.Admin.Application.HardwareJob": { "ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "获取硬件信息出错" "GetHardwareInfoFail": "获取硬件信息出错"
}, },
//dto //dto
"ThingsGateway.Admin.Application.UserSelectorOutput": { "ThingsGateway.Admin.Application.UserSelectorOutput": {
"Account": "账号", "Account": "账号",
"OrgId": "机构" "OrgId": "机构"
}, },
"ThingsGateway.Admin.Application.ResourceTableSearchModel": { "ThingsGateway.Admin.Application.ResourceTableSearchModel": {
"Module": "模块", "Module": "模块",
"Href": "路径", "Href": "路径",
"Title": "标题" "Title": "标题"
}, },
"ThingsGateway.Admin.Application.WorkbenchInfo": { "ThingsGateway.Admin.Application.WorkbenchInfo": {
"Razor": "主页", "Razor": "主页",
"Shortcuts": "快捷方式" "Shortcuts": "快捷方式"
}, },
"ThingsGateway.Admin.Application.UpdatePasswordInput": { "ThingsGateway.Admin.Application.UpdatePasswordInput": {
"Password": "密码", "Password": "密码",
"NewPassword": "新密码", "NewPassword": "新密码",
"ConfirmPassword": "确认密码", "ConfirmPassword": "确认密码",
"Password.Required": " {0} 是必填项", "Password.Required": " {0} 是必填项",
"NewPassword.Required": " {0} 是必填项", "NewPassword.Required": " {0} 是必填项",
"ConfirmPassword.Required": " {0} 是必填项" "ConfirmPassword.Required": " {0} 是必填项"
}, },
"ThingsGateway.Admin.Application.VerificatInfo": { "ThingsGateway.Admin.Application.VerificatInfo": {
"Expire": "过期时间(分)", "Expire": "过期时间(分)",
"Online": "在线状态", "Online": "在线状态",
"VerificatTimeout": "超时时间", "VerificatTimeout": "超时时间",
"Device": "登录设备", "Device": "登录设备",
"LoginIp": "登录IP", "LoginIp": "登录IP",
"LoginTime": "登录时间" "LoginTime": "登录时间"
}, },
"ThingsGateway.Admin.Application.SessionOutput": { "ThingsGateway.Admin.Application.SessionOutput": {
"Account": "账号", "Account": "账号",
"Online": "在线状态", "Online": "在线状态",
"LatestLoginIp": "最新登录ip", "LatestLoginIp": "最新登录ip",
"LatestLoginTime": "最新登录时间", "LatestLoginTime": "最新登录时间",
"VerificatCount": "令牌数量" "VerificatCount": "令牌数量"
}, },
"ThingsGateway.Admin.Application.SysDict": { "ThingsGateway.Admin.Application.SysDict": {
"Category.Required": "{0} 是必填项", "Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项", "Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项", "Code.Required": "{0} 是必填项",
"Category": "分类", "Category": "分类",
"Name": "名称", "Name": "名称",
"Code": "代码", "Code": "代码",
"Remark": "备注", "Remark": "备注",
"SortCode": "排序", "DictDup": "存在重复的配置 分类 {0} 名称 {1}",
"CreateTime": "创建时间", "DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置"
"UpdateTime": "更新时间", },
"DictDup": "存在重复的配置 分类 {0} 名称 {1}",
"DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置"
},
"ThingsGateway.Admin.Application.SysOperateLog": { "ThingsGateway.Admin.Application.SysOperateLog": {
"ClassName": "类名", "ClassName": "类名",
"ExeMessage": "具体消息", "ExeMessage": "具体消息",
"MethodName": "方法名称", "MethodName": "方法名称",
"ParamJson": "请求参数", "ParamJson": "请求参数",
"ReqMethod": "请求方式", "ReqMethod": "请求方式",
"ReqUrl": "请求地址", "ReqUrl": "请求地址",
"ResultJson": "返回结果", "ResultJson": "返回结果",
"Category": "日志分类", "Category": "日志分类",
"ExeStatus": "执行状态", "ExeStatus": "执行状态",
"Name": "日志名称", "Name": "日志名称",
"OpAccount": "账号", "OpAccount": "账号",
"OpBrowser": "浏览器", "OpBrowser": "浏览器",
"OpIp": "ip", "OpIp": "ip",
"OpOs": "系统", "OpOs": "系统",
"OpTime": "操作时间", "OpTime": "操作时间",
"VerificatId": "验证Id" "VerificatId": "验证Id"
}, },
"ThingsGateway.Admin.Application.OperateLogPageInput": { "ThingsGateway.Admin.Application.OperateLogPageInput": {
"SearchDate": "时间范围", "SearchDate": "时间范围",
"Account": "操作账号", "Account": "操作账号",
"Category": "分类" "Category": "分类"
}, },
"ThingsGateway.Admin.Application.LoginInput": { "ThingsGateway.Admin.Application.LoginInput": {
"Account": "登录账号", "Account": "登录账号",
"Password": "登录密码", "Password": "登录密码",
"Account.Required": "{0} 是必填项", "Account.Required": "{0} 是必填项",
"Password.Required": "{0} 是必填项" "Password.Required": "{0} 是必填项"
}, },
"ThingsGateway.Admin.Application.LogoutInput": { "ThingsGateway.Admin.Application.LogoutInput": {
"VerificatId.Required": "{0} 是必填项" "VerificatId.Required": "{0} 是必填项"
}, },
"ThingsGateway.Admin.Application.AppConfig": { "ThingsGateway.Admin.Application.AppConfig": {
"LoginPolicy": "登录策略", "LoginPolicy": "登录策略",
"PasswordPolicy": "密码策略", "PasswordPolicy": "密码策略",
"PagePolicy": "页面设置", "PagePolicy": "页面设置",
"WebsitePolicy": "网站设置" "WebsitePolicy": "网站设置"
}, },
"ThingsGateway.Admin.Application.LoginPolicy": { "ThingsGateway.Admin.Application.LoginPolicy": {
"SingleOpen": "单用户登录开关", "SingleOpen": "单用户登录开关",
"ErrorLockTime": "登录错误锁定时长(分)", "ErrorLockTime": "登录错误锁定时长(分)",
"ErrorResetTime": "登录错误次数过期时长(分)", "ErrorResetTime": "登录错误次数过期时长(分)",
"ErrorCount": "登录错误次数锁定阈值", "ErrorCount": "登录错误次数锁定阈值",
"VerificatExpireTime": "登录过期时间(分)", "VerificatExpireTime": "登录过期时间(分)",
"ErrorLockTime.MinValue": " {0} 值太小", "ErrorLockTime.MinValue": " {0} 值太小",
"ErrorResetTime.MinValue": " {0} 值太小", "ErrorResetTime.MinValue": " {0} 值太小",
"ErrorCount.MinValue": " {0} 值太小", "ErrorCount.MinValue": " {0} 值太小",
"VerificatExpireTime.MinValue": " {0} 值太小" "VerificatExpireTime.MinValue": " {0} 值太小"
}, },
"ThingsGateway.Admin.Application.PagePolicy": { "ThingsGateway.Admin.Application.PagePolicy": {
"Shortcuts": "默认快捷方式", "Shortcuts": "默认快捷方式",
"Razor": "默认主页" "Razor": "默认主页"
}, },
"ThingsGateway.Admin.Application.PasswordPolicy": { "ThingsGateway.Admin.Application.PasswordPolicy": {
"DefaultPassword": "默认用户密码", "DefaultPassword": "默认用户密码",
"DefaultPassword.Required": " {0} 是必填项", "DefaultPassword.Required": " {0} 是必填项",
"PasswordMinLen": "密码最小长度", "PasswordMinLen": "密码最小长度",
"PasswordMinLen.MinValue": " {0} 值太小", "PasswordMinLen.MinValue": " {0} 值太小",
"PasswordContainNum": "包含数字", "PasswordContainNum": "包含数字",
"PasswordContainLower": "包含小写字母", "PasswordContainLower": "包含小写字母",
"PasswordContainUpper": "包含大写字母", "PasswordContainUpper": "包含大写字母",
"PasswordContainChar": "包含特殊字符" "PasswordContainChar": "包含特殊字符"
}, },
"ThingsGateway.Admin.Application.WebsitePolicy": { "ThingsGateway.Admin.Application.WebsitePolicy": {
"WebStatus": "是否开放", "WebStatus": "是否开放",
"CloseTip": "关闭提示", "CloseTip": "关闭提示",
"CloseTip.Required": " {0} 是必填项" "CloseTip.Required": " {0} 是必填项"
}, },
//enum //enum
"ThingsGateway.Admin.Application.ResourceCategoryEnum": { "ThingsGateway.Admin.Application.ResourceCategoryEnum": {
"Module": "模块", "Module": "模块",
"Menu": "菜单", "Menu": "菜单",
"Button": "按钮" "Button": "按钮"
}, },
"ThingsGateway.Admin.Application.TargetEnum": { "ThingsGateway.Admin.Application.TargetEnum": {
"_self": "本窗口", "_self": "本窗口",
"_blank": "新窗口", "_blank": "新窗口",
"_parent": "父级窗口", "_parent": "父级窗口",
"_top": "顶级窗口" "_top": "顶级窗口"
}, },
"ThingsGateway.Admin.Application.DictTypeEnum": { "ThingsGateway.Admin.Application.DictTypeEnum": {
"System": "系统配置", "System": "系统配置",
"Define": "业务配置" "Define": "业务配置"
}, },
"ThingsGateway.Admin.Application.LogCateGoryEnum": { "ThingsGateway.Admin.Application.LogCateGoryEnum": {
"Login": "登录", "Login": "登录",
"Logout": "注销", "Logout": "注销",
"Operate": "操作", "Operate": "操作",
"Exception": "异常" "Exception": "异常"
}, },
"ThingsGateway.Admin.Application.LogEnum": { "ThingsGateway.Admin.Application.LogEnum": {
"SUCCESS": "成功", "SUCCESS": "成功",
"FAIL": "失败" "FAIL": "失败"
}
} }
}

View File

@@ -22,26 +22,19 @@ using System.Globalization;
using System.Reflection; using System.Reflection;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.SpecificationDocument;
namespace ThingsGateway.Admin.Application; namespace ThingsGateway.Admin.Application;
internal sealed class ApiPermissionService : IApiPermissionService internal sealed class ApiPermissionService : IApiPermissionService
{ {
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionGroupCollectionProvider; private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionGroupCollectionProvider;
private readonly SwaggerGeneratorOptions _generatorOptions;
public ApiPermissionService( public ApiPermissionService(
IOptions<SwaggerGeneratorOptions> generatorOptions, IOptions<SwaggerGeneratorOptions> generatorOptions,
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider) IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider)
{ {
_generatorOptions = generatorOptions.Value;
_apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider; _apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
} }
private IEnumerable<string> GetDocumentNames()
{
return _generatorOptions.SwaggerDocs.Keys;
}
/// <inheritdoc /> /// <inheritdoc />
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector() public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector()
@@ -53,37 +46,37 @@ internal sealed class ApiPermissionService : IApiPermissionService
permissions = new(); permissions = new();
Dictionary<string, OpenApiPermissionTreeSelector> groupOpenApis = new(); Dictionary<string, OpenApiPermissionTreeSelector> groupOpenApis = new();
foreach (var item in GetDocumentNames())
{
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = item ?? "Default" };
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
}
var apiDescriptions = _apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items; var apiDescriptions = _apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
foreach (var item1 in apiDescriptions)
{
foreach (var item in item1.Items)
{
if (item.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = controllerActionDescriptor.ControllerName ?? "Default" };
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
}
}
}
// 获取所有需要数据权限的控制器 // 获取所有需要数据权限的控制器
var controllerTypes = var controllerTypes =
App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false)); App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false));
foreach (var groupOpenApi in groupOpenApis) //foreach (var groupOpenApi in groupOpenApis)
{ {
foreach (var apiDescriptionGroup in apiDescriptions) foreach (var apiDescriptionGroup in apiDescriptions)
{ {
var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor); var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor);
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = groupOpenApi.Value;
Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new(); Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new();
foreach (var route in routes) foreach (var route in routes)
{ {
if (!SpecificationDocumentBuilder.CheckApiDescriptionInCurrentGroup(groupOpenApi.Key, route))
{
continue;
}
var actionDesc = (ControllerActionDescriptor)route.ActionDescriptor; var actionDesc = (ControllerActionDescriptor)route.ActionDescriptor;
if (!actionDesc.ControllerTypeInfo.CustomAttributes.Any(a => a.AttributeType == typeof(RolePermissionAttribute))) if (!actionDesc.ControllerTypeInfo.CustomAttributes.Any(a => a.AttributeType == typeof(RolePermissionAttribute)))
continue; continue;
@@ -116,10 +109,8 @@ internal sealed class ApiPermissionService : IApiPermissionService
} }
openApiPermissionTreeSelector.Children.AddRange(openApiPermissionTreeSelectorDict.Values); if (openApiPermissionTreeSelectorDict.Values.Any(a => a.Children.Count > 0))
permissions.AddRange(openApiPermissionTreeSelectorDict.Values);
if (openApiPermissionTreeSelector.Children.Any(a => a.Children.Count > 0))
permissions.Add(openApiPermissionTreeSelector);
} }

View File

@@ -96,9 +96,9 @@ public class AuthService : IAuthService
/// </summary> /// </summary>
public async Task LoginOutAsync() public async Task LoginOutAsync()
{ {
if (UserManager.UserId == 0) if (UserManager.VerificatId == 0)
return; return;
var verificatId = UserManager.UserId; var verificatId = UserManager.VerificatId;
//获取用户信息 //获取用户信息
var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false); var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false);
if (userinfo != null) if (userinfo != null)

View File

@@ -169,7 +169,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
public void RemoveAllClientId() public void RemoveAllClientId()
{ {
using var db = GetDB(); using var db = GetDB();
db.Updateable<VerificatInfo>().SetColumns("ClientIds", new ConcurrentList<long>().ToJsonNetString()).Where(a => a.Id >= 0).ExecuteCommand(); db.Updateable<VerificatInfo>().SetColumns(a=>a.ClientIds==null).Where(a => a.Id > 0).ExecuteCommand();
VerificatInfoService.RemoveCache(); VerificatInfoService.RemoveCache();
} }

View File

@@ -24,6 +24,11 @@ public sealed class SqlSugarOption : ConnectionConfig
/// </summary> /// </summary>
public bool InitSeedData { get; set; } = false; public bool InitSeedData { get; set; } = false;
/// <summary>
/// 初始化数据库
/// </summary>
public bool InitDatabase { get; set; } = false;
/// <summary> /// <summary>
/// 初始化表 /// 初始化表
/// </summary> /// </summary>

View File

@@ -89,7 +89,7 @@ public class Startup : AppStartup
DbContext.DbConfigs?.ForEach(it => DbContext.DbConfigs?.ForEach(it =>
{ {
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象 var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
if (it.InitTable == true) if (it.InitDatabase == true)
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建 connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
}); });

View File

@@ -30,9 +30,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.4" /> <PackageReference Include="System.Formats.Asn1" Version="9.0.5" />
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.4" /> <PackageReference Include="System.Threading.RateLimiting" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Remove="SeedData\Admin\*.json" /> <Content Remove="SeedData\Admin\*.json" />

View File

@@ -41,7 +41,7 @@ public partial class SessionPage
{ {
var op = new DialogOption() var op = new DialogOption()
{ {
IsScrolling = false, IsScrolling = true,
Title = Localizer[nameof(VerificatInfo)], Title = Localizer[nameof(VerificatInfo)],
ShowMaximizeButton = true, ShowMaximizeButton = true,
Class = "dialog-table", Class = "dialog-table",

View File

@@ -9,10 +9,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> <ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.14" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.16" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net9.0'"> <ItemGroup Condition="'$(TargetFramework)'=='net9.0'">
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.4" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -45,7 +45,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.4" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<!--安装服务守护--> <!--安装服务守护-->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
@@ -54,8 +54,8 @@
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,81 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
using ThingsGateway;
namespace Microsoft.Extensions.Hosting;
/// <summary>
/// HostApplication 拓展
/// </summary>
public static class AppHostApplicationBuilderExtensions
{
/// <summary>
/// Host 应用注入
/// </summary>
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
/// <param name="autoRegisterBackgroundService"></param>
/// <returns>HostApplicationBuilder</returns>
public static HostApplicationBuilder Inject(this HostApplicationBuilder hostApplicationBuilder, bool autoRegisterBackgroundService = true)
{
// 初始化配置
InternalApp.ConfigureApplication(hostApplicationBuilder, autoRegisterBackgroundService);
return hostApplicationBuilder;
}
/// <summary>
/// 注册依赖组件
/// </summary>
/// <typeparam name="TComponent">派生自 <see cref="IServiceComponent"/></typeparam>
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public static HostApplicationBuilder AddComponent<TComponent>(this HostApplicationBuilder hostApplicationBuilder, object options = default)
where TComponent : class, IServiceComponent, new()
{
hostApplicationBuilder.Services.AddComponent<TComponent>(options);
return hostApplicationBuilder;
}
/// <summary>
/// 注册依赖组件
/// </summary>
/// <typeparam name="TComponent">派生自 <see cref="IServiceComponent"/></typeparam>
/// <typeparam name="TComponentOptions">组件参数</typeparam>
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
/// <param name="options">组件参数</param>
/// <returns><see cref="HostApplicationBuilder"/></returns>
public static HostApplicationBuilder AddComponent<TComponent, TComponentOptions>(this HostApplicationBuilder hostApplicationBuilder, TComponentOptions options = default)
where TComponent : class, IServiceComponent, new()
{
hostApplicationBuilder.Services.AddComponent<TComponent, TComponentOptions>(options);
return hostApplicationBuilder;
}
/// <summary>
/// 注册依赖组件
/// </summary>
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns><see cref="HostApplicationBuilder"/></returns>
public static HostApplicationBuilder AddComponent(this HostApplicationBuilder hostApplicationBuilder, Type componentType, object options = default)
{
hostApplicationBuilder.Services.AddComponent(componentType, options);
return hostApplicationBuilder;
}
}

View File

@@ -467,18 +467,20 @@ public static class ObjectExtensions
return obj; return obj;
} }
/// <summary> /// <summary>
/// 查找方法指定特性,如果没找到则继续查找声明类 /// 查找方法指定特性,如果没找到则继续查找声明类
/// </summary> /// </summary>
/// <typeparam name="TAttribute"></typeparam> /// <typeparam name="TAttribute"></typeparam>
/// <param name="method"></param> /// <param name="method"></param>
/// <param name="inherit"></param> /// <param name="inherit"></param>
/// <param name="searchFromReflectedType">searchFromRuntimeType</param>
/// <returns></returns> /// <returns></returns>
internal static TAttribute GetFoundAttribute<TAttribute>(this MethodInfo method, bool inherit) internal static TAttribute GetFoundAttribute<TAttribute>(this MethodInfo method, bool inherit, bool searchFromReflectedType = false)
where TAttribute : Attribute where TAttribute : Attribute
{ {
// 获取方法所在类型 // 获取方法所在类型
var declaringType = method.DeclaringType; var declaringType = !searchFromReflectedType ? method.DeclaringType : method.ReflectedType; // 解决嵌套继承问题
var attributeType = typeof(TAttribute); var attributeType = typeof(TAttribute);
@@ -493,7 +495,6 @@ public static class ObjectExtensions
return foundAttribute; return foundAttribute;
} }
/// <summary> /// <summary>
/// 格式化字符串 /// 格式化字符串
/// </summary> /// </summary>

View File

@@ -132,6 +132,34 @@ internal static class InternalApp
}); });
} }
/// <summary>
/// 配置 Furion 框架(非 Web
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="autoRegisterBackgroundService"></param>
internal static void ConfigureApplication(IHostApplicationBuilder hostApplicationBuilder, bool autoRegisterBackgroundService = true)
{
// 存储环境对象
HostEnvironment = hostApplicationBuilder.Environment;
// 加载配置
AddJsonFiles(hostApplicationBuilder.Configuration, hostApplicationBuilder.Environment);
// 存储配置对象
Configuration = hostApplicationBuilder.Configuration;
// 存储服务提供器
InternalServices = hostApplicationBuilder.Services;
// 存储根服务
hostApplicationBuilder.Services.AddHostedService<GenericHostLifetimeEventsHostedService>();
// 初始化应用服务
hostApplicationBuilder.Services.AddApp();
// 自动注册 BackgroundService
if (autoRegisterBackgroundService) hostApplicationBuilder.Services.AddAppHostedService();
}
/// <summary> /// <summary>
/// 自动装载主机配置 /// 自动装载主机配置
/// </summary> /// </summary>

View File

@@ -9,7 +9,7 @@
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@@ -29,34 +29,36 @@ public class AESEncryption
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns></returns> /// <returns></returns>
public static string Encrypt(string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static string Encrypt(string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
var bKey = Encoding.UTF8.GetBytes(skey); var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
using var aesAlg = Aes.Create(); using var aesAlg = Aes.Create();
aesAlg.Key = bKey; aesAlg.Key = bKey;
aesAlg.Mode = mode; aesAlg.Mode = mode;
aesAlg.Padding = padding; aesAlg.Padding = padding;
// 如果是 ECB 模式,不需要 IV
if (mode != CipherMode.ECB) if (mode != CipherMode.ECB)
{ {
aesAlg.IV = iv ?? aesAlg.IV; // 如果未提供 IV则使用随机生成的 IV aesAlg.IV = iv ?? aesAlg.IV;
if (iv != null && iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
} }
using var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); using var encryptor = aesAlg.CreateEncryptor();
using var msEncrypt = new MemoryStream(); using var msEncrypt = new MemoryStream();
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (var swEncrypt = new StreamWriter(csEncrypt)) using (var swEncrypt = new StreamWriter(csEncrypt, Encoding.UTF8))
{ {
swEncrypt.Write(text); swEncrypt.Write(text);
} }
var encryptedContent = msEncrypt.ToArray(); var encryptedContent = msEncrypt.ToArray();
// 如果是 CBC 模式,将 IV 和密文拼接在一起 // 仅在未提供 IV 时拼接 IV
if (mode != CipherMode.ECB) if (mode != CipherMode.ECB && iv == null)
{ {
var result = new byte[aesAlg.IV.Length + encryptedContent.Length]; var result = new byte[aesAlg.IV.Length + encryptedContent.Length];
Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length); Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length);
@@ -76,35 +78,43 @@ public class AESEncryption
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns></returns> /// <returns></returns>
public static string Decrypt(string hash, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static string Decrypt(string hash, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
var fullCipher = Convert.FromBase64String(hash); var fullCipher = Convert.FromBase64String(hash);
var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
var bKey = Encoding.UTF8.GetBytes(skey); if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
using var aesAlg = Aes.Create(); using var aesAlg = Aes.Create();
aesAlg.Key = bKey; aesAlg.Key = bKey;
aesAlg.Mode = mode; aesAlg.Mode = mode;
aesAlg.Padding = padding; aesAlg.Padding = padding;
// 如果是 ECB 模式,不需要 IV
if (mode != CipherMode.ECB) if (mode != CipherMode.ECB)
{ {
var bVector = new byte[16]; if (iv == null)
var cipher = new byte[fullCipher.Length - bVector.Length]; {
if (fullCipher.Length < aesAlg.BlockSize / 8) throw new ArgumentException("The ciphertext length is insufficient to extract the IV.");
Unsafe.CopyBlock(ref bVector[0], ref fullCipher[0], (uint)bVector.Length); iv = new byte[aesAlg.BlockSize / 8];
Unsafe.CopyBlock(ref cipher[0], ref fullCipher[bVector.Length], (uint)(fullCipher.Length - bVector.Length)); var cipher = new byte[fullCipher.Length - iv.Length];
Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
aesAlg.IV = iv ?? bVector; Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, cipher.Length);
fullCipher = cipher; aesAlg.IV = iv;
fullCipher = cipher;
}
else
{
if (iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
aesAlg.IV = iv;
}
} }
using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); using var decryptor = aesAlg.CreateDecryptor();
using var msDecrypt = new MemoryStream(fullCipher); using var msDecrypt = new MemoryStream(fullCipher);
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using var srDecrypt = new StreamReader(csDecrypt); using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8);
return srDecrypt.ReadToEnd(); return srDecrypt.ReadToEnd();
} }
@@ -117,19 +127,13 @@ public class AESEncryption
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns>加密后的字节数组</returns> /// <returns>加密后的字节数组</returns>
public static byte[] Encrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static byte[] Encrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
// 确保密钥长度为 128 位、192 位或 256 位 // 验证密钥长度
var bKey = new byte[32]; // 256 位密钥 var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
var keyBytes = Encoding.UTF8.GetBytes(skey); if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
Array.Copy(keyBytes, bKey, Math.Min(keyBytes.Length, bKey.Length));
// 如果是 ECB 模式,不需要 IV
if (mode != CipherMode.ECB)
{
iv ??= GenerateRandomIV(); // 生成随机 IV
}
using var aesAlg = Aes.Create(); using var aesAlg = Aes.Create();
aesAlg.Key = bKey; aesAlg.Key = bKey;
@@ -138,34 +142,29 @@ public class AESEncryption
if (mode != CipherMode.ECB) if (mode != CipherMode.ECB)
{ {
aesAlg.IV = iv; aesAlg.IV = iv ?? GenerateRandomIV();
if (aesAlg.IV.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
} }
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV), CryptoStreamMode.Write); using (var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateEncryptor(), CryptoStreamMode.Write))
cryptoStream.Write(bytes, 0, bytes.Length);
cryptoStream.FlushFinalBlock();
// 如果是 CBC 模式,将 IV 和密文拼接在一起
if (mode != CipherMode.ECB)
{ {
var result = new byte[aesAlg.IV.Length + memoryStream.ToArray().Length]; cryptoStream.Write(bytes, 0, bytes.Length);
cryptoStream.FlushFinalBlock();
}
var encryptedContent = memoryStream.ToArray();
// 仅在未提供 IV 时拼接 IV
if (mode != CipherMode.ECB && iv == null)
{
var result = new byte[aesAlg.IV.Length + encryptedContent.Length];
Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length); Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length);
Buffer.BlockCopy(memoryStream.ToArray(), 0, result, aesAlg.IV.Length, memoryStream.ToArray().Length); Buffer.BlockCopy(encryptedContent, 0, result, aesAlg.IV.Length, encryptedContent.Length);
return result; return result;
} }
// 如果是 ECB 模式,直接返回密文 return encryptedContent;
return memoryStream.ToArray();
}
// 生成随机 IV
private static byte[] GenerateRandomIV()
{
using var aes = Aes.Create();
aes.GenerateIV();
return aes.IV;
} }
/// <summary> /// <summary>
@@ -176,25 +175,13 @@ public class AESEncryption
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns></returns> /// <returns></returns>
public static byte[] Decrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static byte[] Decrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
// 确保密钥长度为 128 位、192 位或 256 位 // 验证密钥长度
var bKey = new byte[32]; // 256 位密钥 var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
var keyBytes = Encoding.UTF8.GetBytes(skey); if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
Array.Copy(keyBytes, bKey, Math.Min(keyBytes.Length, bKey.Length));
// 如果是 ECB 模式,不需要 IV
if (mode != CipherMode.ECB)
{
if (iv == null)
{
// 从密文中提取 IV
iv = new byte[16];
Array.Copy(bytes, iv, iv.Length);
bytes = bytes.Skip(iv.Length).ToArray();
}
}
using var aesAlg = Aes.Create(); using var aesAlg = Aes.Create();
aesAlg.Key = bKey; aesAlg.Key = bKey;
@@ -203,21 +190,36 @@ public class AESEncryption
if (mode != CipherMode.ECB) if (mode != CipherMode.ECB)
{ {
if (iv == null)
{
// 提取IV
if (bytes.Length < 16) throw new ArgumentException("The ciphertext length is insufficient to extract the IV.");
iv = bytes.Take(16).ToArray();
bytes = bytes.Skip(16).ToArray();
}
else
{
if (iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
}
aesAlg.IV = iv; aesAlg.IV = iv;
} }
using var memoryStream = new MemoryStream(bytes); using var memoryStream = new MemoryStream(bytes);
using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV), CryptoStreamMode.Read); using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read);
using var originalStream = new MemoryStream(); using var originalStream = new MemoryStream();
var buffer = new byte[1024]; cryptoStream.CopyTo(originalStream);
var readBytes = 0;
while ((readBytes = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
{
originalStream.Write(buffer, 0, readBytes);
}
return originalStream.ToArray(); return originalStream.ToArray();
} }
/// <summary>
/// 生成随机 IV
/// </summary>
/// <returns></returns>
private static byte[] GenerateRandomIV()
{
using var aes = Aes.Create();
aes.GenerateIV();
return aes.IV;
}
} }

View File

@@ -0,0 +1,92 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using System.IO.Compression;
using System.Text;
namespace ThingsGateway.DataEncryption;
/// <summary>
/// GZip 压缩解压
/// </summary>
[SuppressSniffer]
public static class GzipEncryption
{
/// <summary>
/// 压缩字符串并返回字节数组
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static byte[] Compress(string text)
{
var buffer = Encoding.UTF8.GetBytes(text);
using var ms = new MemoryStream();
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
zip.Write(buffer, 0, buffer.Length);
}
return ms.ToArray();
}
/// <summary>
/// 从字节数组解压
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string Decompress(byte[] bytes)
{
using var ms = new MemoryStream(bytes);
using var zip = new GZipStream(ms, CompressionMode.Decompress);
using var outStream = new MemoryStream();
zip.CopyTo(outStream);
return Encoding.UTF8.GetString(outStream.ToArray());
}
/// <summary>
/// 压缩字符串并返回 Base64 字符串
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static string CompressToBase64(string text)
{
var buffer = Encoding.UTF8.GetBytes(text);
using var ms = new MemoryStream();
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
zip.Write(buffer, 0, buffer.Length);
}
return Convert.ToBase64String(ms.ToArray());
}
/// <summary>
/// 从 Base64 字符串解压
/// </summary>
/// <param name="base64String"></param>
/// <returns></returns>
public static string DecompressFromBase64(string base64String)
{
var compressedData = Convert.FromBase64String(base64String);
using var ms = new MemoryStream(compressedData);
using var zip = new GZipStream(ms, CompressionMode.Decompress);
using var outStream = new MemoryStream();
zip.CopyTo(outStream);
return Encoding.UTF8.GetString(outStream.ToArray());
}
}

View File

@@ -77,10 +77,11 @@ public static class StringEncryptionExtensions
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns>string</returns> /// <returns>string</returns>
public static string ToAESEncrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static string ToAESEncrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
return AESEncryption.Encrypt(text, skey, iv, mode, padding); return AESEncryption.Encrypt(text, skey, iv, mode, padding, isBase64);
} }
/// <summary> /// <summary>
@@ -91,10 +92,11 @@ public static class StringEncryptionExtensions
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns>string</returns> /// <returns>string</returns>
public static string ToAESDecrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static string ToAESDecrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
return AESEncryption.Decrypt(text, skey, iv, mode, padding); return AESEncryption.Decrypt(text, skey, iv, mode, padding, isBase64);
} }
/// <summary> /// <summary>
@@ -105,10 +107,11 @@ public static class StringEncryptionExtensions
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns>string</returns> /// <returns>string</returns>
public static byte[] ToAESEncrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static byte[] ToAESEncrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
return AESEncryption.Encrypt(bytes, skey, iv, mode, padding); return AESEncryption.Encrypt(bytes, skey, iv, mode, padding, isBase64);
} }
/// <summary> /// <summary>
@@ -119,10 +122,11 @@ public static class StringEncryptionExtensions
/// <param name="iv">偏移量</param> /// <param name="iv">偏移量</param>
/// <param name="mode">模式</param> /// <param name="mode">模式</param>
/// <param name="padding">填充</param> /// <param name="padding">填充</param>
/// <param name="isBase64"></param>
/// <returns>string</returns> /// <returns>string</returns>
public static byte[] ToAESDecrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) public static byte[] ToAESDecrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
{ {
return AESEncryption.Decrypt(bytes, skey, iv, mode, padding); return AESEncryption.Decrypt(bytes, skey, iv, mode, padding, isBase64);
} }
/// <summary> /// <summary>
@@ -243,4 +247,44 @@ public static class StringEncryptionExtensions
{ {
return PBKDF2Encryption.Compare(text, hash, saltSize, iterationCount, derivedKeyLength); return PBKDF2Encryption.Compare(text, hash, saltSize, iterationCount, derivedKeyLength);
} }
/// <summary>
/// Gzip 压缩字符串并返回字节数组
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static byte[] ToGzipCompress(this string text)
{
return GzipEncryption.Compress(text);
}
/// <summary>
/// Gzip 从字节数组解压
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string ToGzipDecompress(this byte[] bytes)
{
return GzipEncryption.Decompress(bytes);
}
/// <summary>
/// Gzip 压缩字符串并返回 Base64 字符串
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static string ToGzipCompressToBase64(this string text)
{
return GzipEncryption.CompressToBase64(text);
}
/// <summary>
/// Gzip 从 Base64 字符串解压
/// </summary>
/// <param name="base64String"></param>
/// <returns></returns>
public static string ToGzipDecompressFromBase64(this string base64String)
{
return GzipEncryption.DecompressFromBase64(base64String);
}
} }

View File

@@ -565,10 +565,10 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
if (isLowerCamelCase) parameterModel.ParameterName = parameterModel.ParameterName.ToLowerCamelCase(); if (isLowerCamelCase) parameterModel.ParameterName = parameterModel.ParameterName.ToLowerCamelCase();
// 判断是否贴有任何 [FromXXX] 特性了 // 判断是否贴有任何 [FromXXX] 特性了
var hasFormAttribute = parameterAttributes.Any(u => typeof(IBindingSourceMetadata).IsAssignableFrom(u.GetType())); var hasFromAttribute = parameterAttributes.Any(u => typeof(IBindingSourceMetadata).IsAssignableFrom(u.GetType()));
// 判断方法贴有 [QueryParameters] 特性且当前参数没有任何 [FromXXX] 特性,则添加 [FromQuery] 特性 // 判断方法贴有 [QueryParameters] 特性且当前参数没有任何 [FromXXX] 特性,则添加 [FromQuery] 特性
if (isQueryParametersAction && !hasFormAttribute) if (isQueryParametersAction && !hasFromAttribute)
{ {
parameterModel.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromQueryAttribute() }); parameterModel.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromQueryAttribute() });
continue; continue;
@@ -577,7 +577,7 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
// 如果没有贴 [FromRoute] 特性且不是基元类型,则跳过 // 如果没有贴 [FromRoute] 特性且不是基元类型,则跳过
// 如果没有贴 [FromRoute] 特性且有任何绑定特性,则跳过 // 如果没有贴 [FromRoute] 特性且有任何绑定特性,则跳过
if (!parameterAttributes.Any(u => u is FromRouteAttribute) if (!parameterAttributes.Any(u => u is FromRouteAttribute)
&& (!parameterType.IsRichPrimitive() || hasFormAttribute)) continue; && (!parameterType.IsRichPrimitive() || hasFromAttribute)) continue;
// 处理基元数组数组类型,还有全局配置参数问题 // 处理基元数组数组类型,还有全局配置参数问题
if (_dynamicApiControllerSettings?.UrlParameterization == true || parameterType.IsArray) if (_dynamicApiControllerSettings?.UrlParameterization == true || parameterType.IsArray)
@@ -588,7 +588,7 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
// 处理 [ApiController] 特性情况 // 处理 [ApiController] 特性情况
// https://docs.microsoft.com/en-US/aspnet/core/web-api/?view=aspnetcore-5.0#binding-source-parameter-inference // https://docs.microsoft.com/en-US/aspnet/core/web-api/?view=aspnetcore-5.0#binding-source-parameter-inference
if (!hasFormAttribute && hasApiControllerAttribute) continue; if (!hasFromAttribute && hasApiControllerAttribute) continue;
// 处理默认基元参数绑定方式,若是 query[FromQuery])则跳过 // 处理默认基元参数绑定方式,若是 query[FromQuery])则跳过
if (_dynamicApiControllerSettings?.DefaultBindingInfo?.ToLower() == "query") if (_dynamicApiControllerSettings?.DefaultBindingInfo?.ToLower() == "query")

View File

@@ -84,8 +84,6 @@ internal sealed class DynamicApiRuntimeChangeProvider : IDynamicApiRuntimeChange
if (applicationPart != null) _applicationPartManager.ApplicationParts.Remove(applicationPart); if (applicationPart != null) _applicationPartManager.ApplicationParts.Remove(applicationPart);
} }
GC.Collect();
GC.WaitForPendingFinalizers();
} }
} }

View File

@@ -72,7 +72,7 @@ public sealed class EventBusOptionsBuilder
/// <summary> /// <summary>
/// 是否启用执行完成触发 GC 回收 /// 是否启用执行完成触发 GC 回收
/// </summary> /// </summary>
public bool GCCollect { get; set; } = true; public bool GCCollect { get; set; } = false;
/// <summary> /// <summary>
/// 是否启用日志记录 /// 是否启用日志记录

View File

@@ -10,6 +10,7 @@
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Reflection; using System.Reflection;
using System.Text.Json;
namespace ThingsGateway.EventBus; namespace ThingsGateway.EventBus;
@@ -57,4 +58,31 @@ public abstract class EventHandlerContext
/// </summary> /// </summary>
/// <remarks><remarks>如果是动态订阅,可能为 null</remarks></remarks> /// <remarks><remarks>如果是动态订阅,可能为 null</remarks></remarks>
public EventSubscribeAttribute Attribute { get; } public EventSubscribeAttribute Attribute { get; }
private static JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default)
{
PropertyNameCaseInsensitive = true
};
/// <summary>
/// 获取负载数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetPayload<T>()
{
var rawPayload = Source.Payload;
if (rawPayload is null)
{
return default;
}
else if (rawPayload is JsonElement jsonElement)
{
return JsonSerializer.Deserialize<T>(jsonElement.GetRawText(), JsonSerializerOptions);
}
else
{
return (T)rawPayload;
}
}
} }

View File

@@ -38,4 +38,18 @@ public sealed class EventHandlerExecutingContext : EventHandlerContext
/// 执行前时间 /// 执行前时间
/// </summary> /// </summary>
public DateTime ExecutingTime { get; internal set; } public DateTime ExecutingTime { get; internal set; }
/// <summary>
/// 执行结果
/// </summary>
internal object Result { get; private set; }
/// <summary>
/// 设置执行结果
/// </summary>
/// <param name="result"></param>
public void SetResult(object result)
{
Result = result;
}
} }

View File

@@ -42,4 +42,10 @@ public sealed class EventHandlerEventArgs : EventArgs
/// 异常信息 /// 异常信息
/// </summary> /// </summary>
public Exception Exception { get; internal set; } public Exception Exception { get; internal set; }
/// <summary>
/// 执行结果
/// </summary>
public object Result { get; internal set; }
} }

View File

@@ -304,7 +304,10 @@ internal sealed class EventBusHostedService : BackgroundService
} }
// 触发事件处理程序事件 // 触发事件处理程序事件
_eventPublisher.InvokeEvents(new(eventSource, true)); _eventPublisher.InvokeEvents(new(eventSource, true)
{
Result = eventHandlerExecutingContext.Result
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -198,8 +198,9 @@ public class JWTEncryption
/// <param name="refreshTokenExpiredTime">新刷新 Token 有效期(分钟)</param> /// <param name="refreshTokenExpiredTime">新刷新 Token 有效期(分钟)</param>
/// <param name="tokenPrefix"></param> /// <param name="tokenPrefix"></param>
/// <param name="clockSkew"></param> /// <param name="clockSkew"></param>
/// <param name="onRefreshing">当刷新时触发</param>
/// <returns></returns> /// <returns></returns>
public static async Task<bool> AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5) public static async Task<bool> AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5, Action<string, string> onRefreshing = null)
{ {
// 如果验证有效,则跳过刷新 // 如果验证有效,则跳过刷新
if (context.User.Identity.IsAuthenticated) if (context.User.Identity.IsAuthenticated)
@@ -245,7 +246,11 @@ public class JWTEncryption
// 返回新的 Token // 返回新的 Token
httpContext.Response.Headers[accessTokenKey] = accessToken; httpContext.Response.Headers[accessTokenKey] = accessToken;
// 返回新的 刷新Token // 返回新的 刷新Token
httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken, refreshTokenExpiredTime); var refreshAccessToken = GenerateRefreshToken(accessToken, refreshTokenExpiredTime); ;
httpContext.Response.Headers[xAccessTokenKey] = refreshAccessToken;
// 调用刷新后回调函数
onRefreshing?.Invoke(accessToken, refreshAccessToken);
// 处理 axios 问题 // 处理 axios 问题
httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs); httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);

View File

@@ -90,7 +90,8 @@ public sealed class ConsoleFormatterExtend : ConsoleFormatter, IDisposable
, true , true
, _disableColors , _disableColors
, _formatterOptions.WithTraceId , _formatterOptions.WithTraceId
, _formatterOptions.WithStackFrame); , _formatterOptions.WithStackFrame
, _formatterOptions.FormatProvider);
} }
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件 // 判断是否自定义了日志筛选器,如果是则检查是否符合条件

View File

@@ -12,6 +12,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Logging.Console;
using System.Globalization;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -69,4 +71,10 @@ public sealed class ConsoleFormatterExtendOptions : ConsoleFormatterOptions
/// 日志消息内容转换(如脱敏处理) /// 日志消息内容转换(如脱敏处理)
/// </summary> /// </summary>
public Func<string, string> MessageProcess { get; set; } public Func<string, string> MessageProcess { get; set; }
/// <summary>
/// 格式化提供器
/// </summary>
/// <remarks></remarks>
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
} }

View File

@@ -118,7 +118,7 @@ public sealed class DatabaseLogger : ILogger
// 设置日志消息模板 // 设置日志消息模板
logMsg.Message = _options.MessageFormat != null logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg) ? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame); : Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
// 空检查 // 空检查
if (logMsg.Message is null) if (logMsg.Message is null)

View File

@@ -11,6 +11,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -80,4 +82,10 @@ public sealed class DatabaseLoggerOptions
/// 日志消息内容转换(如脱敏处理) /// 日志消息内容转换(如脱敏处理)
/// </summary> /// </summary>
public Func<string, string> MessageProcess { get; set; } public Func<string, string> MessageProcess { get; set; }
/// <summary>
/// 格式化提供器
/// </summary>
/// <remarks></remarks>
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
} }

View File

@@ -116,7 +116,7 @@ public sealed class FileLogger : ILogger
// 设置日志消息模板 // 设置日志消息模板
logMsg.Message = _options.MessageFormat != null logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg) ? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame); : Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
// 空检查 // 空检查
if (logMsg.Message is null) if (logMsg.Message is null)

View File

@@ -11,6 +11,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -104,4 +106,10 @@ public sealed class FileLoggerOptions
/// 日志消息内容转换(如脱敏处理) /// 日志消息内容转换(如脱敏处理)
/// </summary> /// </summary>
public Func<string, string> MessageProcess { get; set; } public Func<string, string> MessageProcess { get; set; }
/// <summary>
/// 格式化提供器
/// </summary>
/// <remarks></remarks>
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
} }

View File

@@ -11,6 +11,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -120,6 +122,6 @@ public struct LogMessage
/// <returns><see cref="string"/></returns> /// <returns><see cref="string"/></returns>
public override readonly string ToString() public override readonly string ToString()
{ {
return Penetrates.OutputStandardMessage(this); return Penetrates.OutputStandardMessage(this, provider: CultureInfo.InvariantCulture);
} }
} }

View File

@@ -192,7 +192,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
/// <param name="claimsPrincipal"></param> /// <param name="claimsPrincipal"></param>
/// <param name="authorization"></param> /// <param name="authorization"></param>
/// <returns></returns> /// <returns></returns>
private static List<string> GenerateAuthorizationTemplate(Utf8JsonWriter writer, ClaimsPrincipal claimsPrincipal, StringValues authorization) private List<string> GenerateAuthorizationTemplate(Utf8JsonWriter writer, ClaimsPrincipal claimsPrincipal, StringValues authorization)
{ {
var templates = new List<string>(); var templates = new List<string>();
@@ -219,7 +219,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
var succeed = long.TryParse(value, out var seconds); var succeed = long.TryParse(value, out var seconds);
if (succeed) if (succeed)
{ {
value = $"{value} ({DateTimeOffset.FromUnixTimeSeconds(seconds).ToLocalTime():yyyy-MM-dd HH:mm:ss:ffff(zzz) dddd} L)"; value = $"{value} ({DateTimeOffset.FromUnixTimeSeconds(seconds).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:ffff(zzz) dddd", Settings.FormatProvider)} L)";
} }
} }

View File

@@ -12,6 +12,7 @@
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@@ -143,4 +144,11 @@ public sealed class LoggingMonitorSettings
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
SkipValidation = true SkipValidation = true
}; };
/// <summary>
/// 格式化提供器
/// </summary>
/// <remarks></remarks>
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
} }

View File

@@ -107,13 +107,15 @@ internal static class Penetrates
/// <param name="isConsole"></param> /// <param name="isConsole"></param>
/// <param name="withTraceId"></param> /// <param name="withTraceId"></param>
/// <param name="withStackFrame"></param> /// <param name="withStackFrame"></param>
/// <param name="provider"></param>
/// <returns></returns> /// <returns></returns>
internal static string OutputStandardMessage(LogMessage logMsg internal static string OutputStandardMessage(LogMessage logMsg
, string dateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd" , string dateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd"
, bool isConsole = false , bool isConsole = false
, bool disableColors = true , bool disableColors = true
, bool withTraceId = false , bool withTraceId = false
, bool withStackFrame = false) , bool withStackFrame = false
, IFormatProvider? provider = null)
{ {
// 空检查 // 空检查
if (logMsg.Message is null) return null; if (logMsg.Message is null) return null;
@@ -127,7 +129,7 @@ internal static class Penetrates
_ = AppendWithColor(formatString, GetLogLevelString(logMsg.LogLevel), logLevelColors); _ = AppendWithColor(formatString, GetLogLevelString(logMsg.LogLevel), logLevelColors);
formatString.Append(": "); formatString.Append(": ");
formatString.Append(logMsg.LogDateTime.ToString(dateFormat)); formatString.Append(logMsg.LogDateTime.ToString(dateFormat, provider));
formatString.Append(' '); formatString.Append(' ');
formatString.Append(logMsg.UseUtcTimestamp ? "U" : "L"); formatString.Append(logMsg.UseUtcTimestamp ? "U" : "L");
formatString.Append(' '); formatString.Append(' ');

View File

@@ -78,9 +78,9 @@ public partial interface ISchedulerFactory
/// <returns><see cref="IJob"/></returns> /// <returns><see cref="IJob"/></returns>
IJob CreateJob(IServiceProvider serviceProvider, JobFactoryContext context); IJob CreateJob(IServiceProvider serviceProvider, JobFactoryContext context);
/// <summary> ///// <summary>
/// GC 垃圾回收器回收处理 ///// GC 垃圾回收器回收处理
/// </summary> ///// </summary>
/// <remarks>避免频繁 GC 回收</remarks> ///// <remarks>避免频繁 GC 回收</remarks>
void GCCollect(); //void GCCollect();
} }

View File

@@ -183,9 +183,10 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
// 标记当前方法初始化完成 // 标记当前方法初始化完成
PreloadCompleted = true; PreloadCompleted = true;
// 释放引用内存并立即回收GC // 释放引用内存
_schedulerBuilders.Clear(); _schedulerBuilders.Clear();
GCCollect();
//GCCollect();
// 输出作业调度器初始化日志 // 输出作业调度器初始化日志
if (!preloadSucceed) _logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count); if (!preloadSucceed) _logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
@@ -393,22 +394,22 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
return jobHandler; return jobHandler;
} }
/// <summary> ///// <summary>
/// GC 垃圾回收器回收处理 ///// GC 垃圾回收器回收处理
/// </summary> ///// </summary>
/// <remarks>避免频繁 GC 回收</remarks> ///// <remarks>避免频繁 GC 回收</remarks>
public void GCCollect() //public void GCCollect()
{ //{
var nowTime = DateTime.UtcNow; // var nowTime = DateTime.UtcNow;
if ((LastGCCollectTime == null || (nowTime - LastGCCollectTime.Value).TotalMilliseconds > GC_COLLECT_INTERVAL_MILLISECONDS)) // if ((LastGCCollectTime == null || (nowTime - LastGCCollectTime.Value).TotalMilliseconds > GC_COLLECT_INTERVAL_MILLISECONDS))
{ // {
LastGCCollectTime = nowTime; // LastGCCollectTime = nowTime;
// 通知 GC 垃圾回收器立即回收 // // 通知 GC 垃圾回收器立即回收
GC.Collect(); // GC.Collect();
GC.WaitForPendingFinalizers(); // GC.WaitForPendingFinalizers();
} // }
} //}
/// <summary> /// <summary>
/// 释放非托管资源 /// 释放非托管资源
@@ -535,7 +536,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
//_logger.LogWarning("Schedule hosted service cancels hibernation."); //_logger.LogWarning("Schedule hosted service cancels hibernation.");
// 通知 GC 垃圾回收器立即回收 // 通知 GC 垃圾回收器立即回收
GCCollect(); //GCCollect();
}); });
} }

View File

@@ -389,7 +389,7 @@ internal sealed class ScheduleHostedService : BackgroundService
_jobCancellationToken.Cancel(jobId, triggerId, false); _jobCancellationToken.Cancel(jobId, triggerId, false);
// 通知 GC 垃圾回收器回收 // 通知 GC 垃圾回收器回收
_schedulerFactory.GCCollect(); //_schedulerFactory.GCCollect();
} }
}, stoppingToken); }, stoppingToken);
}); });

View File

@@ -113,10 +113,8 @@ public static class SpecificationDocumentBuilder
} }
// 处理贴有 [ApiExplorerSettings(IgnoreApi = true)] 或者 [ApiDescriptionSettings(false)] 特性的接口 // 处理贴有 [ApiExplorerSettings(IgnoreApi = true)] 或者 [ApiDescriptionSettings(false)] 特性的接口
var apiExplorerSettings = method.GetFoundAttribute<ApiExplorerSettingsAttribute>(true); var apiExplorerSettings = method.GetFoundAttribute<ApiExplorerSettingsAttribute>(true, true);
var apiDescriptionSettings = method.GetFoundAttribute<ApiDescriptionSettingsAttribute>(true, true);
var apiDescriptionSettings = method.GetFoundAttribute<ApiDescriptionSettingsAttribute>(true);
if (apiExplorerSettings?.IgnoreApi == true || apiDescriptionSettings?.IgnoreApi == true) return false; if (apiExplorerSettings?.IgnoreApi == true || apiDescriptionSettings?.IgnoreApi == true) return false;
if (currentGroup == AllGroupsKey) if (currentGroup == AllGroupsKey)

View File

@@ -43,18 +43,18 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.14" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.16" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.1" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.1" />
<PackageReference Include="System.Text.Json" Version="8.0.5" /> <PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.5" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.4" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.5" />
<PackageReference Include="System.Text.Json" Version="9.0.4" /> <PackageReference Include="System.Text.Json" Version="9.0.5" />
</ItemGroup> </ItemGroup>

View File

@@ -433,10 +433,15 @@ public partial class Crontab
{ {
newValue = newValue.AddSeconds(-newValue.Second); newValue = newValue.AddSeconds(-newValue.Second);
} }
// 初始化是否存在随机 R 标识符
var randomSecond = false;
var randomMinute = false;
var randomHour = false;
// 获取分钟、小时所有字符解析器 // 获取分钟、小时所有字符解析器
var minuteParsers = Parsers[CrontabFieldKind.Minute].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList(); var minuteParsers = Parsers[CrontabFieldKind.Minute].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
randomMinute = minuteParsers.OfType<RandomParser>().Any();
var hourParsers = Parsers[CrontabFieldKind.Hour].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList(); var hourParsers = Parsers[CrontabFieldKind.Hour].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
randomHour = hourParsers.OfType<RandomParser>().Any();
// 获取秒、分钟、小时解析器中最小起始值 // 获取秒、分钟、小时解析器中最小起始值
// 该值主要用来获取下一个发生值的输入参数 // 该值主要用来获取下一个发生值的输入参数
@@ -456,7 +461,7 @@ public partial class Crontab
{ {
// 获取秒所有字符解析器 // 获取秒所有字符解析器
var secondParsers = Parsers[CrontabFieldKind.Second].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList(); var secondParsers = Parsers[CrontabFieldKind.Second].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
randomSecond = secondParsers.OfType<RandomParser>().Any();
// 获取秒解析器最小起始值 // 获取秒解析器最小起始值
firstSecondValue = secondParsers.Select(x => x.First()).Min(); firstSecondValue = secondParsers.Select(x => x.First()).Min();
@@ -519,8 +524,8 @@ public partial class Crontab
// 设置起始时间为下一个小时时间 // 设置起始时间为下一个小时时间
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours, newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours,
overflow ? firstMinuteValue : newMinutes, overflow && !randomMinute ? firstMinuteValue : newMinutes,
overflow ? firstSecondValue : newSeconds); overflow && !randomSecond ? firstSecondValue : newSeconds);
// 如果当前小时并没有进入下一轮循环但存在不匹配的字符过滤器 // 如果当前小时并没有进入下一轮循环但存在不匹配的字符过滤器
if (!overflow && !IsMatch(newValue)) if (!overflow && !IsMatch(newValue))
@@ -534,7 +539,7 @@ public partial class Crontab
} }
// 如果程序到达这里,说明并没有进入上面分支,则直接返回下一小时时间 // 如果程序到达这里,说明并没有进入上面分支,则直接返回下一小时时间
if (!overflow) if (!randomHour && !overflow)
{ {
return MinDate(newValue, endTime); return MinDate(newValue, endTime);
} }
@@ -788,8 +793,15 @@ public partial class Crontab
/// <param name="defaultValue">默认值</param> /// <param name="defaultValue">默认值</param>
/// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param> /// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
/// <returns><see cref="int"/></returns> /// <returns><see cref="int"/></returns>
private static int Increment(IEnumerable<ITimeParser> parsers, int value, int defaultValue, out bool overflow) private static int Increment(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
{ {
// 检查是否是随机 R 字符解析器
if (parsers.Count == 1 && parsers.First() is RandomParser randomParser)
{
overflow = true;
return randomParser.Next(value).Value;
}
var nextValue = parsers.Select(x => x.Next(value)) var nextValue = parsers.Select(x => x.Next(value))
.Where(x => x > value) .Where(x => x > value)
.Min() .Min()
@@ -808,7 +820,7 @@ public partial class Crontab
/// <param name="defaultValue">默认值</param> /// <param name="defaultValue">默认值</param>
/// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param> /// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
/// <returns><see cref="int"/></returns> /// <returns><see cref="int"/></returns>
private static int Decrement(IEnumerable<ITimeParser> parsers, int value, int defaultValue, out bool overflow) private static int Decrement(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
{ {
var previousValue = parsers.Select(x => x.Previous(value)) var previousValue = parsers.Select(x => x.Previous(value))
.Where(x => x < value) .Where(x => x < value)

View File

@@ -69,7 +69,7 @@ internal sealed class RandomParser : ICronParser, ITimeParser
/// <returns><see cref="bool"/></returns> /// <returns><see cref="bool"/></returns>
public bool IsMatch(DateTime datetime) public bool IsMatch(DateTime datetime)
{ {
return true; return Kind is not CrontabFieldKind.Hour;
} }
/// <summary> /// <summary>

View File

@@ -168,7 +168,7 @@ public static class UnifyContext
if (context.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) return null; if (context.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) return null;
// 获取序列化配置 // 获取序列化配置
var unifySerializerSettingAttribute = actionDescriptor.MethodInfo.GetFoundAttribute<UnifySerializerSettingAttribute>(true); var unifySerializerSettingAttribute = actionDescriptor.MethodInfo.GetFoundAttribute<UnifySerializerSettingAttribute>(true, true);
if (unifySerializerSettingAttribute == null || string.IsNullOrWhiteSpace(unifySerializerSettingAttribute.Name)) return null; if (unifySerializerSettingAttribute == null || string.IsNullOrWhiteSpace(unifySerializerSettingAttribute.Name)) return null;
// 解析全局配置 // 解析全局配置
@@ -225,7 +225,8 @@ public static class UnifyContext
|| method.GetRealReturnType().HasImplementedRawGeneric(unityMetadata.ResultType) || method.GetRealReturnType().HasImplementedRawGeneric(unityMetadata.ResultType)
|| method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType)) || method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true) || method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData"); || method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData")
|| method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>);
if (!isWebRequest) if (!isWebRequest)
{ {
@@ -255,7 +256,8 @@ public static class UnifyContext
!method.CustomAttributes.Any(x => typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType)) !method.CustomAttributes.Any(x => typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
&& method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true) && method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
) )
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData"); || method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData")
|| method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>);
unifyResult = isSkip ? null : App.RootServices.GetService(unityMetadata.ProviderType) as IUnifyResultProvider; unifyResult = isSkip ? null : App.RootServices.GetService(unityMetadata.ProviderType) as IUnifyResultProvider;
return unifyResult == null || isSkip; return unifyResult == null || isSkip;
@@ -398,7 +400,7 @@ public static class UnifyContext
{ {
if (method == default) return default; if (method == default) return default;
var unityProviderAttribute = method.GetFoundAttribute<UnifyProviderAttribute>(true); var unityProviderAttribute = method.GetFoundAttribute<UnifyProviderAttribute>(true, true);
// 获取元数据 // 获取元数据
var isExists = UnifyProviders.TryGetValue(unityProviderAttribute?.Name ?? string.Empty, out var metadata); var isExists = UnifyProviders.TryGetValue(unityProviderAttribute?.Name ?? string.Empty, out var metadata);

View File

@@ -0,0 +1,38 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.Converters.Json;
/// <summary>
/// <see cref="DateTime" /> JSON 序列化转换器
/// </summary>
/// <remarks>在不符合 <c>ISO 8601-1:2019</c> 格式的 <see cref="DateTime" /> 时间使用 <c>DateTime.Parse</c> 作为回退。</remarks>
public sealed class DateTimeConverterUsingDateTimeParseAsFallback : JsonConverter<DateTime>
{
/// <inheritdoc />
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// 尝试获取 ISO 8601-1:2019 格式时间
if (!reader.TryGetDateTime(out var value))
{
value = DateTime.Parse(reader.GetString()!);
}
return value;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value);
}

View File

@@ -0,0 +1,38 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.Converters.Json;
/// <summary>
/// <see cref="DateTimeOffset" /> JSON 序列化转换器
/// </summary>
/// <remarks>在不符合 <c>ISO 8601-1:2019</c> 格式的 <see cref="DateTimeOffset" /> 时间使用 <c>DateTimeOffset.Parse</c> 作为回退。</remarks>
public sealed class DateTimeOffsetConverterUsingDateTimeOffsetParseAsFallback : JsonConverter<DateTimeOffset>
{
/// <inheritdoc />
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// 尝试获取 ISO 8601-1:2019 格式时间
if (!reader.TryGetDateTimeOffset(out var value))
{
value = DateTimeOffset.Parse(reader.GetString()!);
}
return value;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value);
}

View File

@@ -0,0 +1,37 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using System.Text.Json;
using System.Text.Json.Serialization;
using ThingsGateway.Extensions;
namespace ThingsGateway.Converters.Json;
/// <summary>
/// <see cref="string" /> JSON 序列化转换器
/// </summary>
/// <remarks>解决 Number 类型和 Boolean 类型转 String 类型时异常。</remarks>
public sealed class StringJsonConverter : JsonConverter<string>
{
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.True or JsonTokenType.False => reader.GetBoolean().ToString(),
JsonTokenType.Number => reader.ConvertRawValueToString(),
_ => reader.GetString()
};
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
writer.WriteStringValue(value);
}

View File

@@ -10,6 +10,7 @@
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
namespace ThingsGateway.Extensions; namespace ThingsGateway.Extensions;
@@ -43,7 +44,7 @@ internal static class LinqExpressionExtensions
} }
/// <summary> /// <summary>
/// 解析表达式属性名称 /// 解析表达式并获取属性的 <see cref="PropertyInfo" /> 实例
/// </summary> /// </summary>
/// <typeparam name="T">对象类型</typeparam> /// <typeparam name="T">对象类型</typeparam>
/// <typeparam name="TProperty">属性类型</typeparam> /// <typeparam name="TProperty">属性类型</typeparam>
@@ -51,48 +52,54 @@ internal static class LinqExpressionExtensions
/// <see cref="Expression{TDelegate}" /> /// <see cref="Expression{TDelegate}" />
/// </param> /// </param>
/// <returns> /// <returns>
/// <see cref="string" /> /// <see cref="PropertyInfo" />
/// </returns> /// </returns>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
internal static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty?>> propertySelector) => internal static PropertyInfo GetProperty<T, TProperty>(this Expression<Func<T, TProperty?>> propertySelector) =>
propertySelector.Body switch propertySelector.Body switch
{ {
// 检查 Lambda 表达式的主体是否是 MemberExpression 类型 // 检查 Lambda 表达式的主体是否是 MemberExpression 类型
MemberExpression memberExpression => GetPropertyName<T>(memberExpression), MemberExpression memberExpression => GetProperty<T>(memberExpression),
// 如果主体是 UnaryExpression 类型,则继续解析 // 如果主体是 UnaryExpression 类型,则继续解析
UnaryExpression { Operand: MemberExpression nestedMemberExpression } => GetPropertyName<T>( UnaryExpression { Operand: MemberExpression nestedMemberExpression } => GetProperty<T>(
nestedMemberExpression), nestedMemberExpression),
_ => throw new ArgumentException("Expression must be a simple member access (e.g. x => x.Property).",
_ => throw new ArgumentException("Expression is not valid for property selection.") nameof(propertySelector))
}; };
/// <summary> /// <summary>
/// 解析表达式属性名称 /// 从成员表达式中提取 <see cref="PropertyInfo" /> 实例
/// </summary> /// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="memberExpression"> /// <param name="memberExpression">
/// <see cref="MemberExpression" /> /// <see cref="MemberExpression" />
/// </param> /// </param>
/// <typeparam name="T">对象类型</typeparam>
/// <returns> /// <returns>
/// <see cref="string" /> /// <see cref="PropertyInfo" />
/// </returns> /// </returns>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
internal static string GetPropertyName<T>(MemberExpression memberExpression) internal static PropertyInfo GetProperty<T>(MemberExpression memberExpression)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(memberExpression); ArgumentNullException.ThrowIfNull(memberExpression);
// 获取属性声明类型 // 确保表达式根是 T 类型的参数
var propertyType = memberExpression.Member.DeclaringType; if (memberExpression.Expression is not ParameterExpression parameterExpression ||
parameterExpression.Type != typeof(T))
// 检查是否越界访问属性
if (propertyType != typeof(T))
{ {
throw new ArgumentException("Invalid property selection."); throw new ArgumentException(
$"Expression '{memberExpression}' must refer to a member of type '{typeof(T)}'.",
nameof(memberExpression));
} }
// 返回属性名称 // 确保成员是属性(非字段)
return memberExpression.Member.Name; if (memberExpression.Member is not PropertyInfo propertyInfo)
{
throw new ArgumentException(
$"Expression '{memberExpression}' refers to a field. Only properties are supported.",
nameof(memberExpression));
}
return propertyInfo;
} }
} }

View File

@@ -11,6 +11,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@@ -149,7 +150,7 @@ internal static partial class StringExtensions
var pairs = (trimChar is null ? keyValueString : keyValueString.TrimStart(trimChar.Value)).Split(separators); var pairs = (trimChar is null ? keyValueString : keyValueString.TrimStart(trimChar.Value)).Split(separators);
return (from pair in pairs return (from pair in pairs
select pair.Split('=') select pair.Split('=', 2) // 限制只分割一次
into keyValue into keyValue
where keyValue.Length == 2 where keyValue.Length == 2
select new KeyValuePair<string, string?>(keyValue[0].Trim(), keyValue[1])).ToList(); select new KeyValuePair<string, string?>(keyValue[0].Trim(), keyValue[1])).ToList();
@@ -328,6 +329,18 @@ internal static partial class StringExtensions
}); });
} }
/// <summary>
/// 转换输入字符串中的任何转义字符
/// </summary>
/// <param name="input">
/// <see cref="string" />
/// </param>
/// <returns>
/// <see cref="string" />
/// </returns>
internal static string? Unescape([NotNullIfNotNull(nameof(input))] this string? input) =>
string.IsNullOrWhiteSpace(input) ? input : Regex.Unescape(input);
/// <summary> /// <summary>
/// 占位符匹配正则表达式 /// 占位符匹配正则表达式
/// </summary> /// </summary>

View File

@@ -9,6 +9,8 @@
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Buffers;
using System.Text;
using System.Text.Json; using System.Text.Json;
namespace ThingsGateway.Extensions; namespace ThingsGateway.Extensions;
@@ -34,4 +36,17 @@ internal static class Utf8JsonReaderExtensions
return jsonDocument.RootElement.Clone().GetRawText(); return jsonDocument.RootElement.Clone().GetRawText();
} }
/// <summary>
/// 从 <see cref="Utf8JsonReader" /> 中提取原始值,并将其转换为字符串
/// </summary>
/// <remarks>支持处理各种类型的原始值(例如数字、布尔值等)。</remarks>
/// <param name="reader">
/// <see cref="Utf8JsonReader" />
/// </param>
/// <returns>
/// <see cref="string" />
/// </returns>
internal static string ConvertRawValueToString(this Utf8JsonReader reader) =>
Encoding.UTF8.GetString(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan);
} }

View File

@@ -97,16 +97,45 @@ internal static class V5_ObjectExtensions
case ICollection collection: case ICollection collection:
count = collection.Count; count = collection.Count;
return true; return true;
// 检查对象是否实现了 IEnumerable 接口
case IEnumerable enumerable:
// 获取集合枚举数
var enumerator = enumerable.GetEnumerator();
try
{
// 检查枚举数是否可以推进到下一个元素
if (!enumerator.MoveNext())
{
count = 0;
return true;
}
// 枚举数循环推进到下一个元素并叠加推进次数
var c = 1;
while (enumerator.MoveNext())
{
c++;
}
count = c;
return true;
}
finally
{
// 检查枚举数是否实现了 IDisposable 接口
if (enumerator is IDisposable disposable)
{
disposable.Dispose();
}
}
} }
// 反射查找是否存在 Count 属性 // 反射查找是否存在 Count 属性
var runtimeProperty = obj.GetType() var runtimeProperty = obj.GetType().GetRuntimeProperty("Count");
.GetRuntimeProperty("Count");
// 反射获取 Count 属性值 // 反射获取 Count 属性值
if (runtimeProperty is not null if (runtimeProperty is not null && runtimeProperty.CanRead && runtimeProperty.PropertyType == typeof(int))
&& runtimeProperty.CanRead
&& runtimeProperty.PropertyType == typeof(int))
{ {
count = (int)runtimeProperty.GetValue(obj)!; count = (int)runtimeProperty.GetValue(obj)!;
return true; return true;

View File

@@ -38,7 +38,7 @@ public sealed class HttpContextForwardBuilder
/// <summary> /// <summary>
/// 忽略在转发时需要跳过的请求标头列表 /// 忽略在转发时需要跳过的请求标头列表
/// </summary> /// </summary>
internal static HashSet<string> _ignoreRequestHeaders = internal static readonly HashSet<string> _ignoreRequestHeaders =
[ [
Constants.X_FORWARD_TO_HEADER, "Host", "Accept", "Accept-CH", "Accept-Charset", "Accept-Encoding", Constants.X_FORWARD_TO_HEADER, "Host", "Accept", "Accept-CH", "Accept-Charset", "Accept-Encoding",
"Accept-Language", "Accept-Patch", "Accept-Post", "Accept-Ranges" "Accept-Language", "Accept-Patch", "Accept-Post", "Accept-Ranges"
@@ -356,8 +356,7 @@ public sealed class HttpContextForwardBuilder
if (multipartSection.AsFileSection() is not null) if (multipartSection.AsFileSection() is not null)
{ {
// 复制多部分表单内容文件节内容 // 复制多部分表单内容文件节内容
await CopyFileMultipartSectionAsync(multipartSection, httpMultipartFormDataBuilder, httpRequestBuilder, await CopyFileMultipartSectionAsync(multipartSection, httpMultipartFormDataBuilder, cancellationToken).ConfigureAwait(false);
cancellationToken).ConfigureAwait(false);
} }
else else
{ {
@@ -410,15 +409,11 @@ public sealed class HttpContextForwardBuilder
/// <param name="httpMultipartFormDataBuilder"> /// <param name="httpMultipartFormDataBuilder">
/// <see cref="HttpMultipartFormDataBuilder" /> /// <see cref="HttpMultipartFormDataBuilder" />
/// </param> /// </param>
/// <param name="httpRequestBuilder">
/// <see cref="HttpRequestBuilder" />
/// </param>
/// <param name="cancellationToken"> /// <param name="cancellationToken">
/// <see cref="CancellationToken" /> /// <see cref="CancellationToken" />
/// </param> /// </param>
internal static async Task CopyFileMultipartSectionAsync(MultipartSection multipartSection, internal static async Task CopyFileMultipartSectionAsync(MultipartSection multipartSection,
HttpMultipartFormDataBuilder httpMultipartFormDataBuilder, HttpRequestBuilder httpRequestBuilder, HttpMultipartFormDataBuilder httpMultipartFormDataBuilder, CancellationToken cancellationToken)
CancellationToken cancellationToken)
{ {
// 初始化 MemoryStream 实例 // 初始化 MemoryStream 实例
var memoryStream = new MemoryStream(); var memoryStream = new MemoryStream();
@@ -433,10 +428,8 @@ public sealed class HttpContextForwardBuilder
var fileMultipartSection = multipartSection.AsFileSection()!; var fileMultipartSection = multipartSection.AsFileSection()!;
// 添加文件流 // 添加文件流
httpMultipartFormDataBuilder.AddStream(memoryStream, fileMultipartSection.Name, fileMultipartSection.FileName); httpMultipartFormDataBuilder.AddStream(memoryStream, fileMultipartSection.Name, fileMultipartSection.FileName,
disposeStreamOnRequestCompletion: true);
// 添加文件流到请求结束时需要释放的集合中
httpRequestBuilder.AddDisposable(memoryStream);
} }
/// <summary> /// <summary>

View File

@@ -124,12 +124,9 @@ public sealed class HttpMultipartFormDataBuilder
/// <see cref="HttpMultipartFormDataBuilder" /> /// <see cref="HttpMultipartFormDataBuilder" />
/// </returns> /// </returns>
/// <exception cref="JsonException"></exception> /// <exception cref="JsonException"></exception>
public HttpMultipartFormDataBuilder AddJson(object rawJson, string? name = null, Encoding? contentEncoding = null, public HttpMultipartFormDataBuilder AddJson(object? rawJson, string? name = null, Encoding? contentEncoding = null,
string? contentType = null) string? contentType = null)
{ {
// 空检查
ArgumentNullException.ThrowIfNull(rawJson);
// 检查是否配置表单名或不是字符串类型 // 检查是否配置表单名或不是字符串类型
if (!string.IsNullOrWhiteSpace(name) || rawJson is not string rawString) if (!string.IsNullOrWhiteSpace(name) || rawJson is not string rawString)
{ {
@@ -292,10 +289,8 @@ public sealed class HttpMultipartFormDataBuilder
// 从互联网 URL 地址中加载流 // 从互联网 URL 地址中加载流
var fileStream = Helpers.GetStreamFromRemote(url); var fileStream = Helpers.GetStreamFromRemote(url);
// 添加文件流到请求结束时需要释放的集合中 return AddStream(fileStream, name, newFileName, contentType, contentEncoding,
_httpRequestBuilder.AddDisposable(fileStream); true);
return AddStream(fileStream, name, newFileName, contentType, contentEncoding);
} }
/// <summary> /// <summary>
@@ -365,10 +360,8 @@ public sealed class HttpMultipartFormDataBuilder
// 读取文件流(没有 using // 读取文件流(没有 using
var fileStream = File.OpenRead(filePath); var fileStream = File.OpenRead(filePath);
// 添加文件流到请求结束时需要释放的集合中 return AddStream(fileStream, name, newFileName, contentType, contentEncoding,
_httpRequestBuilder.AddDisposable(fileStream); true);
return AddStream(fileStream, name, newFileName, contentType, contentEncoding);
} }
/// <summary> /// <summary>
@@ -407,10 +400,8 @@ public sealed class HttpMultipartFormDataBuilder
// 初始化带读写进度的文件流 // 初始化带读写进度的文件流
var progressFileStream = new ProgressFileStream(fileStream, filePath, progressChannel, newFileName); var progressFileStream = new ProgressFileStream(fileStream, filePath, progressChannel, newFileName);
// 添加文件流到请求结束时需要释放的集合中 return AddStream(progressFileStream, name, newFileName, contentType, contentEncoding,
_httpRequestBuilder.AddDisposable(progressFileStream); true);
return AddStream(progressFileStream, name, newFileName, contentType, contentEncoding);
} }
/// <summary> /// <summary>
@@ -500,11 +491,12 @@ public sealed class HttpMultipartFormDataBuilder
/// <param name="fileName">文件的名称</param> /// <param name="fileName">文件的名称</param>
/// <param name="contentType">内容类型</param> /// <param name="contentType">内容类型</param>
/// <param name="contentEncoding">内容编码</param> /// <param name="contentEncoding">内容编码</param>
/// <param name="disposeStreamOnRequestCompletion">是否在请求结束后自动释放流。默认值为:<c>false</c></param>
/// <returns> /// <returns>
/// <see cref="HttpMultipartFormDataBuilder" /> /// <see cref="HttpMultipartFormDataBuilder" />
/// </returns> /// </returns>
public HttpMultipartFormDataBuilder AddStream(Stream stream, string name = "file", string? fileName = null, public HttpMultipartFormDataBuilder AddStream(Stream stream, string name = "file", string? fileName = null,
string? contentType = null, Encoding? contentEncoding = null) string? contentType = null, Encoding? contentEncoding = null, bool disposeStreamOnRequestCompletion = false)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(stream); ArgumentNullException.ThrowIfNull(stream);
@@ -529,6 +521,12 @@ public sealed class HttpMultipartFormDataBuilder
FileName = fileName FileName = fileName
}); });
// 是否在请求结束后自动释放流
if (disposeStreamOnRequestCompletion)
{
_httpRequestBuilder.AddDisposable(stream);
}
return this; return this;
} }
@@ -697,6 +695,20 @@ public sealed class HttpMultipartFormDataBuilder
return this; return this;
} }
/// <summary>
/// 设置是否移除默认的多部分内容的 <c>Content-Type</c>
/// </summary>
/// <param name="omit">如果为 <c>true</c> 则移除,默认为 <c>false</c></param>
/// <returns>
/// <see cref="HttpMultipartFormDataBuilder" />
/// </returns>
public HttpMultipartFormDataBuilder SetOmitContentType(bool omit)
{
OmitContentType = omit;
return this;
}
/// <summary> /// <summary>
/// 构建 <see cref="MultipartFormDataContent" /> 实例 /// 构建 <see cref="MultipartFormDataContent" /> 实例
/// </summary> /// </summary>

View File

@@ -327,23 +327,20 @@ public sealed partial class HttpRequestBuilder
/// <param name="key">键</param> /// <param name="key">键</param>
/// <param name="value">值</param> /// <param name="value">值</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithHeader(string key, object? value, bool escape = false, CultureInfo? culture = null, public HttpRequestBuilder WithHeader(string key, object? value, bool escape = false, bool replace = false,
IEqualityComparer<string>? comparer = null, bool replace = false) CultureInfo? culture = null)
{ {
// 空检查 // 空检查
ArgumentException.ThrowIfNullOrWhiteSpace(key); ArgumentException.ThrowIfNullOrWhiteSpace(key);
return WithHeaders(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer, replace); return WithHeaders(new Dictionary<string, object?> { { key, value } }, escape, replace, culture);
} }
/// <summary> /// <summary>
@@ -352,25 +349,22 @@ public sealed partial class HttpRequestBuilder
/// <remarks>支持多次调用。</remarks> /// <remarks>支持多次调用。</remarks>
/// <param name="headers">请求标头集合</param> /// <param name="headers">请求标头集合</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithHeaders(IDictionary<string, object?> headers, bool escape = false, public HttpRequestBuilder WithHeaders(IDictionary<string, object?> headers, bool escape = false,
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false) bool replace = false, CultureInfo? culture = null)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(headers); ArgumentNullException.ThrowIfNull(headers);
// 初始化请求标头 // 初始化请求标头
Headers ??= new Dictionary<string, List<string?>>(comparer); Headers ??= new Dictionary<string, List<string?>>(StringComparer.OrdinalIgnoreCase);
var objectHeaders = new Dictionary<string, List<object?>>(comparer); var objectHeaders = new Dictionary<string, List<object?>>(StringComparer.OrdinalIgnoreCase);
// 存在则合并否则添加 // 存在则合并否则添加
objectHeaders.AddOrUpdate(Headers.ToDictionary(u => u.Key, object? (u) => u.Value), false); objectHeaders.AddOrUpdate(Headers.ToDictionary(u => u.Key, object? (u) => u.Value), false);
@@ -380,7 +374,7 @@ public sealed partial class HttpRequestBuilder
Headers = objectHeaders.ToDictionary(kvp => kvp.Key, Headers = objectHeaders.ToDictionary(kvp => kvp.Key,
kvp => kvp.Value.Select(u => kvp => kvp.Value.Select(u =>
u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(), u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(),
comparer); StringComparer.OrdinalIgnoreCase);
return this; return this;
} }
@@ -391,26 +385,23 @@ public sealed partial class HttpRequestBuilder
/// <remarks>支持多次调用。</remarks> /// <remarks>支持多次调用。</remarks>
/// <param name="headerSource">请求标头源对象</param> /// <param name="headerSource">请求标头源对象</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithHeaders(object headerSource, bool escape = false, CultureInfo? culture = null, public HttpRequestBuilder WithHeaders(object headerSource, bool escape = false, bool replace = false,
IEqualityComparer<string>? comparer = null, bool replace = false) CultureInfo? culture = null)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(headerSource); ArgumentNullException.ThrowIfNull(headerSource);
return WithHeaders( return WithHeaders(
headerSource.ObjectToDictionary()!.ToDictionary( headerSource.ObjectToDictionary()!.ToDictionary(
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture, u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, replace,
comparer, replace); culture);
} }
/// <summary> /// <summary>
@@ -474,6 +465,7 @@ public sealed partial class HttpRequestBuilder
public HttpRequestBuilder SetTimeout(TimeSpan timeout) public HttpRequestBuilder SetTimeout(TimeSpan timeout)
{ {
Timeout = timeout; Timeout = timeout;
TimeoutAction = null;
return this; return this;
} }
@@ -494,6 +486,43 @@ public sealed partial class HttpRequestBuilder
} }
Timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds); Timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
TimeoutAction = null;
return this;
}
/// <summary>
/// 设置超时时间
/// </summary>
/// <param name="timeout">超时时间</param>
/// <param name="onTimeout">超时发生时要执行的操作</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetTimeout(TimeSpan timeout, Action onTimeout)
{
// 空检查
ArgumentNullException.ThrowIfNull(onTimeout);
SetTimeout(timeout).TimeoutAction = onTimeout;
return this;
}
/// <summary>
/// 设置超时时间
/// </summary>
/// <param name="timeoutMilliseconds">超时时间(毫秒)</param>
/// <param name="onTimeout">超时发生时要执行的操作</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetTimeout(double timeoutMilliseconds, Action onTimeout)
{
// 空检查
ArgumentNullException.ThrowIfNull(onTimeout);
SetTimeout(timeoutMilliseconds).TimeoutAction = onTimeout;
return this; return this;
} }
@@ -570,26 +599,22 @@ public sealed partial class HttpRequestBuilder
/// <param name="key">键</param> /// <param name="key">键</param>
/// <param name="value">值</param> /// <param name="value">值</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithQueryParameter(string key, object? value, bool escape = false, public HttpRequestBuilder WithQueryParameter(string key, object? value, bool escape = false, bool replace = false,
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false, bool ignoreNullValues = false, CultureInfo? culture = null)
bool ignoreNullValues = false)
{ {
// 空检查 // 空检查
ArgumentException.ThrowIfNullOrWhiteSpace(key); ArgumentException.ThrowIfNullOrWhiteSpace(key);
return WithQueryParameters(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer, return WithQueryParameters(new Dictionary<string, object?> { { key, value } }, escape, replace,
replace, ignoreNullValues); ignoreNullValues, culture);
} }
/// <summary> /// <summary>
@@ -598,27 +623,23 @@ public sealed partial class HttpRequestBuilder
/// <remarks>支持多次调用。</remarks> /// <remarks>支持多次调用。</remarks>
/// <param name="parameters">查询参数集合</param> /// <param name="parameters">查询参数集合</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithQueryParameters(IDictionary<string, object?> parameters, bool escape = false, public HttpRequestBuilder WithQueryParameters(IDictionary<string, object?> parameters, bool escape = false,
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false, bool replace = false, bool ignoreNullValues = false, CultureInfo? culture = null)
bool ignoreNullValues = false)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(parameters); ArgumentNullException.ThrowIfNull(parameters);
// 初始化查询参数 // 初始化查询参数
QueryParameters ??= new Dictionary<string, List<string?>>(comparer); QueryParameters ??= new Dictionary<string, List<string?>>(StringComparer.OrdinalIgnoreCase);
var objectQueryParameters = new Dictionary<string, List<object?>>(comparer); var objectQueryParameters = new Dictionary<string, List<object?>>(StringComparer.OrdinalIgnoreCase);
// 存在则合并否则添加 // 存在则合并否则添加
objectQueryParameters.AddOrUpdate(QueryParameters.ToDictionary(u => u.Key, object? (u) => u.Value), false); objectQueryParameters.AddOrUpdate(QueryParameters.ToDictionary(u => u.Key, object? (u) => u.Value), false);
@@ -629,7 +650,7 @@ public sealed partial class HttpRequestBuilder
QueryParameters = objectQueryParameters.ToDictionary(kvp => kvp.Key, QueryParameters = objectQueryParameters.ToDictionary(kvp => kvp.Key,
kvp => kvp.Value.Select(u => kvp => kvp.Value.Select(u =>
u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(), u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(),
comparer); StringComparer.OrdinalIgnoreCase);
return this; return this;
} }
@@ -641,20 +662,16 @@ public sealed partial class HttpRequestBuilder
/// <param name="parameterSource">查询参数集合</param> /// <param name="parameterSource">查询参数集合</param>
/// <param name="prefix">参数前缀。对于对象类型可生成如 <c>prefix.Name=furion</c> 与 <c>prefix.Age=30</c> 参数格式。</param> /// <param name="prefix">参数前缀。对于对象类型可生成如 <c>prefix.Name=furion</c> 与 <c>prefix.Age=30</c> 参数格式。</param>
/// <param name="escape">是否转义字符串,默认 <c>false</c></param> /// <param name="escape">是否转义字符串,默认 <c>false</c></param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithQueryParameters(object parameterSource, string? prefix = null, bool escape = false, public HttpRequestBuilder WithQueryParameters(object parameterSource, string? prefix = null, bool escape = false,
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false, bool replace = false, bool ignoreNullValues = false, CultureInfo? culture = null)
bool ignoreNullValues = false)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(parameterSource); ArgumentNullException.ThrowIfNull(parameterSource);
@@ -663,7 +680,7 @@ public sealed partial class HttpRequestBuilder
parameterSource.ObjectToDictionary()!.ToDictionary( parameterSource.ObjectToDictionary()!.ToDictionary(
u => u =>
$"{(string.IsNullOrWhiteSpace(prefix) ? null : $"{prefix}.")}{u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!}", $"{(string.IsNullOrWhiteSpace(prefix) ? null : $"{prefix}.")}{u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!}",
u => u.Value), escape, culture, comparer, replace, ignoreNullValues); u => u.Value), escape, replace, ignoreNullValues, culture);
} }
/// <summary> /// <summary>
@@ -709,19 +726,16 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithPathParameter(string key, object? value, bool escape = false, public HttpRequestBuilder WithPathParameter(string key, object? value, bool escape = false,
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null) CultureInfo? culture = null)
{ {
// 空检查 // 空检查
ArgumentException.ThrowIfNullOrWhiteSpace(key); ArgumentException.ThrowIfNullOrWhiteSpace(key);
return WithPathParameters(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer); return WithPathParameters(new Dictionary<string, object?> { { key, value } }, escape, culture);
} }
/// <summary> /// <summary>
@@ -733,26 +747,21 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithPathParameters(IDictionary<string, object?> parameters, public HttpRequestBuilder WithPathParameters(IDictionary<string, object?> parameters, bool escape = false,
bool escape = false, CultureInfo? culture = null)
CultureInfo? culture = null,
IEqualityComparer<string>? comparer = null)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(parameters); ArgumentNullException.ThrowIfNull(parameters);
PathParameters ??= new Dictionary<string, string?>(comparer); PathParameters ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
// 存在则更新否则添加 // 存在则更新否则添加
PathParameters.AddOrUpdate(parameters.ToDictionary(u => u.Key, PathParameters.AddOrUpdate(parameters.ToDictionary(u => u.Key,
u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape), u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape),
comparer)); StringComparer.OrdinalIgnoreCase));
return this; return this;
} }
@@ -767,15 +776,11 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithPathParameters(object? parameterSource, string? prefix = null, bool escape = false, public HttpRequestBuilder WithPathParameters(object? parameterSource, string? prefix = null, bool escape = false,
CultureInfo? culture = null, CultureInfo? culture = null)
IEqualityComparer<string>? comparer = null)
{ {
// 检查是否设置了模板字符串前缀 // 检查是否设置了模板字符串前缀
if (string.IsNullOrWhiteSpace(prefix)) if (string.IsNullOrWhiteSpace(prefix))
@@ -786,7 +791,7 @@ public sealed partial class HttpRequestBuilder
return WithPathParameters( return WithPathParameters(
parameterSource.ObjectToDictionary()!.ToDictionary( parameterSource.ObjectToDictionary()!.ToDictionary(
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape,
culture, comparer); culture);
} }
ObjectPathParameters ??= new Dictionary<string, object?>(); ObjectPathParameters ??= new Dictionary<string, object?>();
@@ -823,19 +828,15 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithCookie(string key, object? value, bool escape = false, CultureInfo? culture = null, public HttpRequestBuilder WithCookie(string key, object? value, bool escape = false, CultureInfo? culture = null)
IEqualityComparer<string>? comparer = null)
{ {
// 空检查 // 空检查
ArgumentException.ThrowIfNullOrWhiteSpace(key); ArgumentException.ThrowIfNullOrWhiteSpace(key);
return WithCookies(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer); return WithCookies(new Dictionary<string, object?> { { key, value } }, escape, culture);
} }
/// <summary> /// <summary>
@@ -847,26 +848,21 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithCookies(IDictionary<string, object?> cookies, public HttpRequestBuilder WithCookies(IDictionary<string, object?> cookies, bool escape = false,
bool escape = false, CultureInfo? culture = null)
CultureInfo? culture = null,
IEqualityComparer<string>? comparer = null)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(cookies); ArgumentNullException.ThrowIfNull(cookies);
Cookies ??= new Dictionary<string, string?>(comparer); Cookies ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
// 存在则更新否则添加 // 存在则更新否则添加
Cookies.AddOrUpdate(cookies.ToDictionary(u => u.Key, Cookies.AddOrUpdate(cookies.ToDictionary(u => u.Key,
u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape), u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape),
comparer)); StringComparer.OrdinalIgnoreCase));
return this; return this;
} }
@@ -880,15 +876,10 @@ public sealed partial class HttpRequestBuilder
/// <param name="culture"> /// <param name="culture">
/// <see cref="CultureInfo" /> /// <see cref="CultureInfo" />
/// </param> /// </param>
/// <param name="comparer">
/// <see cref="IEqualityComparer{T}" />
/// </param>
/// <returns> /// <returns>
/// <see cref="HttpRequestBuilder" /> /// <see cref="HttpRequestBuilder" />
/// </returns> /// </returns>
public HttpRequestBuilder WithCookies(object cookieSource, bool escape = false, public HttpRequestBuilder WithCookies(object cookieSource, bool escape = false, CultureInfo? culture = null)
CultureInfo? culture = null,
IEqualityComparer<string>? comparer = null)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(cookieSource); ArgumentNullException.ThrowIfNull(cookieSource);
@@ -896,8 +887,7 @@ public sealed partial class HttpRequestBuilder
// 存在则更新否则添加 // 存在则更新否则添加
return WithCookies( return WithCookies(
cookieSource.ObjectToDictionary()!.ToDictionary( cookieSource.ObjectToDictionary()!.ToDictionary(
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture, u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture);
comparer);
} }
/// <summary> /// <summary>
@@ -1193,6 +1183,17 @@ public sealed partial class HttpRequestBuilder
return this; return this;
} }
/// <summary>
/// 设置身份验证凭据请求授权标头
/// </summary>
/// <param name="scheme">身份验证的方案</param>
/// <param name="parameter">身份验证的凭证</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder AddAuthentication(string scheme, string? parameter) =>
AddAuthentication(new AuthenticationHeaderValue(scheme, parameter));
/// <summary> /// <summary>
/// 设置身份验证凭据请求授权标头 /// 设置身份验证凭据请求授权标头
/// </summary> /// </summary>
@@ -1328,6 +1329,17 @@ public sealed partial class HttpRequestBuilder
ReleaseDisposables(); ReleaseDisposables();
} }
/// <summary>
/// 设置请求来源地址
/// </summary>
/// <remarks>设置此配置后,将在单次请求标头中添加 <c>Referer</c> 标头。</remarks>
/// <param name="referer">请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetReferer(string? referer) =>
WithHeader(HeaderNames.Referer, referer, replace: true);
/// <summary> /// <summary>
/// 设置模拟浏览器环境 /// 设置模拟浏览器环境
/// </summary> /// </summary>
@@ -1364,6 +1376,17 @@ public sealed partial class HttpRequestBuilder
public HttpRequestBuilder WithAnyStatusCodeHandler(Func<HttpResponseMessage, CancellationToken, Task> handler) => public HttpRequestBuilder WithAnyStatusCodeHandler(Func<HttpResponseMessage, CancellationToken, Task> handler) =>
WithStatusCodeHandler(["*"], handler); WithStatusCodeHandler(["*"], handler);
/// <summary>
/// 添加请求成功200-299状态码处理程序
/// </summary>
/// <param name="handler">自定义处理程序</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder
WithSuccessStatusCodeHandler(Func<HttpResponseMessage, CancellationToken, Task> handler) =>
WithStatusCodeHandler("200-299", handler);
/// <summary> /// <summary>
/// 添加状态码处理程序 /// 添加状态码处理程序
/// </summary> /// </summary>
@@ -1590,6 +1613,107 @@ public sealed partial class HttpRequestBuilder
? null ? null
: new Uri(baseAddress, UriKind.RelativeOrAbsolute)); : new Uri(baseAddress, UriKind.RelativeOrAbsolute));
/// <summary>
/// 设置 HTTP 版本
/// </summary>
/// <param name="version">版本号</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetVersion(string? version) =>
SetVersion(string.IsNullOrWhiteSpace(version) ? null : new Version(version));
/// <summary>
/// 设置 HTTP 版本
/// </summary>
/// <param name="version">
/// <see cref="Version" />
/// </param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetVersion(Version? version)
{
Version = version;
return this;
}
/// <summary>
/// 设置异常抑制
/// </summary>
/// <remarks>抑制所有异常。重复调用仅最后一次调用生效。</remarks>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SuppressExceptions() => SuppressExceptions(true);
/// <summary>
/// 设置异常抑制
/// </summary>
/// <remarks>重复调用仅最后一次调用生效。</remarks>
/// <param name="enable">是否启用异常抑制。当设置为 <c>false</c> 时,将禁用异常抑制机制。</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SuppressExceptions(bool enable) => SuppressExceptions(enable ? [typeof(Exception)] : []);
/// <summary>
/// 设置是否移除默认的内容的 <c>Content-Type</c>
/// </summary>
/// <param name="omit">如果为 <c>true</c> 则移除,默认为 <c>false</c></param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
public HttpRequestBuilder SetOmitContentType(bool omit)
{
OmitContentType = omit;
return this;
}
/// <summary>
/// 设置异常抑制
/// </summary>
/// <remarks>重复调用仅最后一次调用生效。</remarks>
/// <param name="exceptionTypes">异常抑制类型集合</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
/// <exception cref="ArgumentException"></exception>
public HttpRequestBuilder SuppressExceptions(Type[] exceptionTypes)
{
// 空检查
ArgumentNullException.ThrowIfNull(exceptionTypes);
// 检查是否包含 null 或者不是 Exception 类型的元素
if (exceptionTypes.Any(u => (Type?)u is null || !typeof(Exception).IsAssignableFrom(u)))
{
throw new ArgumentException(
"All elements in exceptionTypes must be non-null and assignable to System.Exception.");
}
// 释放引用(无关紧要)
SuppressExceptionTypes = null;
// 空检查
if (exceptionTypes.Length == 0)
{
return this;
}
// 确保每次都能覆盖
SuppressExceptionTypes = [];
// 遍历异常抑制类型集合逐条追加
foreach (var exceptionType in exceptionTypes)
{
SuppressExceptionTypes.Add(exceptionType);
}
return this;
}
/// <summary> /// <summary>
/// 释放可释放的对象集合 /// 释放可释放的对象集合
/// </summary> /// </summary>

View File

@@ -157,6 +157,11 @@ public sealed partial class HttpRequestBuilder
/// </summary> /// </summary>
public Uri? BaseAddress { get; private set; } public Uri? BaseAddress { get; private set; }
/// <summary>
/// HTTP 版本
/// </summary>
public Version? Version { get; private set; }
/// <summary> /// <summary>
/// <see cref="HttpClient" /> 实例提供器 /// <see cref="HttpClient" /> 实例提供器
/// </summary> /// </summary>
@@ -181,7 +186,7 @@ public sealed partial class HttpRequestBuilder
/// <summary> /// <summary>
/// 用于处理在设置 <see cref="HttpRequestMessage" /> 的请求消息的内容时的操作 /// 用于处理在设置 <see cref="HttpRequestMessage" /> 的请求消息的内容时的操作
/// </summary> /// </summary>
public Action<HttpContent?>? OnPreSetContent { get; private set; } public Action<HttpContent>? OnPreSetContent { get; private set; }
/// <summary> /// <summary>
/// 用于处理在发送 HTTP 请求之前的操作 /// 用于处理在发送 HTTP 请求之前的操作
@@ -201,7 +206,13 @@ public sealed partial class HttpRequestBuilder
/// <summary> /// <summary>
/// <inheritdoc cref="HttpMultipartFormDataBuilder" /> /// <inheritdoc cref="HttpMultipartFormDataBuilder" />
/// </summary> /// </summary>
internal HttpMultipartFormDataBuilder? MultipartFormDataBuilder { get; private set; } public HttpMultipartFormDataBuilder? MultipartFormDataBuilder { get; private set; }
/// <summary>
/// 是否移除默认的内容的 <c>Content-Type</c>
/// </summary>
/// <remarks>默认值为:<c>false</c>。</remarks>
public bool OmitContentType { get; private set; }
/// <summary> /// <summary>
/// 如果 HTTP 响应的 <c>IsSuccessStatusCode</c> 属性是 <c>false</c>,则引发异常。 /// 如果 HTTP 响应的 <c>IsSuccessStatusCode</c> 属性是 <c>false</c>,则引发异常。
@@ -273,4 +284,15 @@ public sealed partial class HttpRequestBuilder
get; get;
private set; private set;
} }
/// <summary>
/// 异常抑制类型集合
/// </summary>
/// <remarks>当配置了异常抑制类型集合后,框架将抑制(即不抛出)该集合中匹配的异常类型。</remarks>
internal HashSet<Type>? SuppressExceptionTypes { get; private set; }
/// <summary>
/// 超时发生时要执行的操作
/// </summary>
internal Action? TimeoutAction { get; private set; }
} }

View File

@@ -10,6 +10,9 @@
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Reflection; using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace ThingsGateway.HttpRemote; namespace ThingsGateway.HttpRemote;
@@ -614,4 +617,116 @@ public sealed partial class HttpRequestBuilder
/// </returns> /// </returns>
public static HttpDeclarativeBuilder Declarative(MethodInfo method, object?[] args) => public static HttpDeclarativeBuilder Declarative(MethodInfo method, object?[] args) =>
new(method, args); new(method, args);
/// <summary>
/// 从 JSON 中创建 <see cref="HttpRequestBuilder" /> 实例
/// </summary>
/// <param name="json">JSON 字符串</param>
/// <param name="configure">自定义配置委托</param>
/// <returns>
/// <see cref="HttpRequestBuilder" />
/// </returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static HttpRequestBuilder FromJson(string json, Action<HttpRequestBuilder>? configure = null)
{
// 空检查
ArgumentException.ThrowIfNullOrWhiteSpace(json);
/*
* 手动解析 JSON 字符串
*
* 不采用 JSON 反序列化的原因如下:
* 1. HttpRequestBuilder 的属性设计为只读,无法直接通过反序列化赋值。
* 2. 避免引入 [JsonInclude] 特性对 System.Text.Json 的强耦合,保持依赖解耦。
* 3. 简化 JSON 字符串的结构定义,无需严格遵循 HttpRequestBuilder 的属性定义,从而省略 [JsonPropertyName] 等自定义映射。
* 4. 精确控制需要解析的键,减少不必要的自定义 JsonConverter 操作,提升性能与可维护性。
*/
var jsonObject = JsonNode.Parse(json, new JsonNodeOptions { PropertyNameCaseInsensitive = true },
new JsonDocumentOptions { AllowTrailingCommas = true })?.AsObject();
// 空检查
ArgumentNullException.ThrowIfNull(jsonObject);
// 验证必填字段
if (!jsonObject.TryGetPropertyValue("method", out var methodNode) || methodNode is not JsonValue methodValue)
{
throw new ArgumentException("Missing required `method` in JSON.");
}
// 允许 "url" 为 null但必须定义
if (!jsonObject.ContainsKey("url"))
{
throw new ArgumentException("Missing required `url` in JSON.");
}
// 初始化 HttpRequestBuilder 实例
var httpRequestBuilder = Create(methodValue.ToString(), jsonObject["url"]?.GetValue<string?>());
// 处理可选字段
HandleJsonNode(jsonObject, "baseAddress", node => httpRequestBuilder.SetBaseAddress(node.GetValue<string>()));
HandleJsonNode(jsonObject, "headers", node => httpRequestBuilder.WithHeaders(node));
HandleJsonNode(jsonObject, "queries", node => httpRequestBuilder.WithQueryParameters(node));
HandleJsonNode(jsonObject, "cookies", node => httpRequestBuilder.WithCookies(node));
HandleJsonNode(jsonObject, "timeout", node => httpRequestBuilder.SetTimeout(node.GetValue<double>()));
HandleJsonNode(jsonObject, "client", node => httpRequestBuilder.SetHttpClientName(node.GetValue<string?>()));
HandleJsonNode(jsonObject, "profiler", node => httpRequestBuilder.Profiler(node.GetValue<bool>()));
// 处理请求内容
if (jsonObject.TryGetPropertyValue("data", out var dataNode))
{
// "data" 和 "contentType" 必须同时存在或同时不存在
if (!jsonObject.TryGetPropertyValue("contentType", out var contentTypeNode) ||
contentTypeNode is not JsonValue contentTypeValue)
{
throw new InvalidOperationException("The `contentType` key is required when `data` is present.");
}
// 设置请求内容
httpRequestBuilder
.SetContent(
dataNode?.ToJsonString(new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}), contentTypeValue.ToString()).AddStringContentForFormUrlEncodedContentProcessor();
// 设置内容编码
HandleJsonNode(jsonObject, "encoding",
node => httpRequestBuilder.SetContentEncoding(node.GetValue<string>()));
}
// 处理多部分表单
if (jsonObject.TryGetPropertyValue("multipart", out var multipartNode))
{
// 设置多部分表单内容
httpRequestBuilder.SetMultipartContent(multipart => multipart.AddJson(multipartNode?.AsObject()
.ToJsonString(new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })));
}
// 调用自定义配置委托
configure?.Invoke(httpRequestBuilder);
return httpRequestBuilder;
}
/// <summary>
/// 处理 <see cref="JsonNode" />
/// </summary>
/// <param name="jsonObject">
/// <see cref="JsonObject" />
/// </param>
/// <param name="propertyName">属性名</param>
/// <param name="action">自定义操作</param>
internal static void HandleJsonNode(JsonObject jsonObject, string propertyName, Action<JsonNode> action)
{
// 空检查
ArgumentNullException.ThrowIfNull(jsonObject);
ArgumentNullException.ThrowIfNull(propertyName);
ArgumentNullException.ThrowIfNull(action);
if (jsonObject.TryGetPropertyValue(propertyName, out var node) && node is not null)
{
action(node);
}
}
} }

View File

@@ -76,6 +76,12 @@ public sealed partial class HttpRequestBuilder
// 初始化 HttpRequestMessage 实例 // 初始化 HttpRequestMessage 实例
var httpRequestMessage = new HttpRequestMessage(HttpMethod, finalRequestUri); var httpRequestMessage = new HttpRequestMessage(HttpMethod, finalRequestUri);
// 设置 HTTP 版本
if (Version is not null)
{
httpRequestMessage.Version = Version;
}
// 启用性能优化 // 启用性能优化
EnablePerformanceOptimization(httpRequestMessage); EnablePerformanceOptimization(httpRequestMessage);
@@ -160,18 +166,44 @@ public sealed partial class HttpRequestBuilder
/// </param> /// </param>
internal void AppendPathSegments(UriBuilder uriBuilder) internal void AppendPathSegments(UriBuilder uriBuilder)
{ {
// 空检查
if ((PathSegments == null || PathSegments.Count == 0) &&
(PathSegmentsToRemove == null || PathSegmentsToRemove.Count == 0))
{
return;
}
// 记录原路径是否以斜杠结尾(修复核心逻辑)
var originalPath = uriBuilder.Uri.AbsolutePath;
var endsWithSlash = originalPath.Length > 1 && originalPath.EndsWith('/');
// 解析 URL 中的路径片段列表 // 解析 URL 中的路径片段列表
var pathSegments = uriBuilder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries).Concat([]); var pathSegments = uriBuilder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
// 追加路径片段 // 追加并处理新路径片段
pathSegments = pathSegments.Concat(PathSegments.ConcatIgnoreNull([]).Where(u => !string.IsNullOrWhiteSpace(u)) var newPathSegments = pathSegments.Concat(PathSegments.ConcatIgnoreNull([])
.Select(u => u.TrimStart('/').TrimEnd('/'))); .Where(u => !string.IsNullOrWhiteSpace(u)).Select(u => u.TrimStart('/').TrimEnd('/')));
// 构建路径片段赋值给 UriBuilder 的 Path 属性 // 过滤需要移除的路径片段
uriBuilder.Path = '/' + string.Join('/', var filteredSegments = newPathSegments.WhereIf(PathSegmentsToRemove is { Count: > 0 },
// 过滤已标记为移除的路径片段 u => PathSegmentsToRemove?.Contains(u) == false).ToArray();
pathSegments.WhereIf(PathSegmentsToRemove is { Count: > 0 },
u => PathSegmentsToRemove?.TryGetValue(u, out _) == false)); // 构建最终路径
if (filteredSegments.Length != 0)
{
uriBuilder.Path = $"/{string.Join('/', filteredSegments)}";
// 恢复原路径的结尾斜杠(当存在路径片段时)
if (endsWithSlash)
{
uriBuilder.Path += "/";
}
}
// 没有路径片段时设置为根路径
else
{
uriBuilder.Path = "/";
}
} }
/// <summary> /// <summary>
@@ -182,6 +214,13 @@ public sealed partial class HttpRequestBuilder
/// </param> /// </param>
internal void AppendQueryParameters(UriBuilder uriBuilder) internal void AppendQueryParameters(UriBuilder uriBuilder)
{ {
// 空检查
if ((QueryParameters is null || QueryParameters.Count == 0) &&
(QueryParametersToRemove is null || QueryParametersToRemove.Count == 0))
{
return;
}
// 解析 URL 中的查询字符串为键值对列表 // 解析 URL 中的查询字符串为键值对列表
var queryParameters = uriBuilder.Query.ParseFormatKeyValueString(['&'], '?'); var queryParameters = uriBuilder.Query.ParseFormatKeyValueString(['&'], '?');
@@ -300,6 +339,16 @@ public sealed partial class HttpRequestBuilder
// 遍历请求标头集合并追加到 HttpRequestMessage.Headers 中 // 遍历请求标头集合并追加到 HttpRequestMessage.Headers 中
foreach (var (key, values) in Headers) foreach (var (key, values) in Headers)
{ {
// 替换 Referer 标头的 "{BASE_ADDRESS}" 模板字符串
if (key.IsIn([HeaderNames.Referer], StringComparer.OrdinalIgnoreCase) &&
values.FirstOrDefault() == Constants.REFERER_HEADER_BASE_ADDRESS_TEMPLATE)
{
httpRequestMessage.Headers.Referrer = new Uri(
$"{httpRequestMessage.RequestUri?.Scheme}://{httpRequestMessage.RequestUri?.Host}{(httpRequestMessage.RequestUri?.IsDefaultPort != true ? $":{httpRequestMessage.RequestUri?.Port}" : string.Empty)}",
UriKind.RelativeOrAbsolute);
continue;
}
httpRequestMessage.Headers.TryAddWithoutValidation(key, values); httpRequestMessage.Headers.TryAddWithoutValidation(key, values);
} }
} }
@@ -486,6 +535,18 @@ public sealed partial class HttpRequestBuilder
// 构建 HttpContent 实例 // 构建 HttpContent 实例
var httpContent = httpContentProcessorFactory.Build(RawContent, ContentType!, ContentEncoding, processors); var httpContent = httpContentProcessorFactory.Build(RawContent, ContentType!, ContentEncoding, processors);
// 空检查
if (httpContent is null)
{
return;
}
// 检查是否移除默认的内容的 Content-Type解决对接 Java 程序时可能出现失败问题
if (OmitContentType)
{
httpContent.Headers.ContentType = null;
}
// 调用用于处理在设置请求消息的内容时的操作 // 调用用于处理在设置请求消息的内容时的操作
OnPreSetContent?.Invoke(httpContent); OnPreSetContent?.Invoke(httpContent);
@@ -513,6 +574,9 @@ public sealed partial class HttpRequestBuilder
{ {
httpRequestMessage.Options.AddOrUpdate(Constants.DISABLED_PROFILER_KEY, "TRUE"); httpRequestMessage.Options.AddOrUpdate(Constants.DISABLED_PROFILER_KEY, "TRUE");
} }
// 添加 HttpClient 实例的配置名称
httpRequestMessage.Options.AddOrUpdate(Constants.HTTP_CLIENT_NAME, HttpClientName ?? string.Empty);
} }
/// <summary> /// <summary>

View File

@@ -88,15 +88,26 @@ internal static class Constants
/// <remarks>被用于从 <see cref="HttpRequestMessage" /> 的 <c>Options</c> 属性中读取。</remarks> /// <remarks>被用于从 <see cref="HttpRequestMessage" /> 的 <c>Options</c> 属性中读取。</remarks>
internal const string DECLARATIVE_METHOD_KEY = "__DECLARATIVE_METHOD__"; internal const string DECLARATIVE_METHOD_KEY = "__DECLARATIVE_METHOD__";
/// <summary>
/// HTTP 请求 <see cref="HttpClient" /> 实例的配置名称键
/// </summary>
/// <remarks>被用于从 <see cref="HttpRequestMessage" /> 的 <c>Options</c> 属性中读取。</remarks>
internal const string HTTP_CLIENT_NAME = "__HTTP_CLIENT_NAME__";
/// <summary> /// <summary>
/// 浏览器的 <c>User-Agent</c> 标头值 /// 浏览器的 <c>User-Agent</c> 标头值
/// </summary> /// </summary>
internal const string USER_AGENT_OF_BROWSER = internal const string USER_AGENT_OF_BROWSER =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"; "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0";
/// <summary> /// <summary>
/// 移动端浏览器的 <c>User-Agent</c> 标头值 /// 移动端浏览器的 <c>User-Agent</c> 标头值
/// </summary> /// </summary>
internal const string USER_AGENT_OF_MOBILE_BROWSER = internal const string USER_AGENT_OF_MOBILE_BROWSER =
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36 Edg/133.0.0.0"; "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36 Edg/135.0.0.0";
/// <summary>
/// <c>Referer</c> 标头请求基地址模板
/// </summary>
internal const string REFERER_HEADER_BASE_ADDRESS_TEMPLATE = "{BASE_ADDRESS}";
} }

View File

@@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text.Json;
namespace ThingsGateway.HttpRemote; namespace ThingsGateway.HttpRemote;
@@ -27,16 +28,45 @@ public class ObjectContentConverter : IHttpContentConverter
/// <inheritdoc /> /// <inheritdoc />
public virtual object? Read(Type resultType, HttpResponseMessage httpResponseMessage, public virtual object? Read(Type resultType, HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
httpResponseMessage.Content.ReadFromJsonAsync(resultType, httpResponseMessage.Content
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ?? .ReadFromJsonAsync(resultType, GetJsonSerializerOptions(httpResponseMessage), cancellationToken)
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).GetAwaiter().GetResult(); .GetAwaiter().GetResult();
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage, public virtual async Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
await httpResponseMessage.Content.ReadFromJsonAsync(resultType, await httpResponseMessage.Content.ReadFromJsonAsync(resultType, GetJsonSerializerOptions(httpResponseMessage),
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ?? cancellationToken).ConfigureAwait(false);
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).ConfigureAwait(false);
/// <summary>
/// 获取 JSON 序列化选项实例
/// </summary>
/// <param name="httpResponseMessage">
/// <see cref="HttpResponseMessage" />
/// </param>
/// <returns>
/// <see cref="JsonSerializerOptions" />
/// </returns>
protected virtual JsonSerializerOptions GetJsonSerializerOptions(HttpResponseMessage httpResponseMessage)
{
// 空检查
ArgumentNullException.ThrowIfNull(httpResponseMessage);
// 获取 HttpClient 实例的配置名称
if (httpResponseMessage.RequestMessage?.Options.TryGetValue(
new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME), out var httpClientName) != true)
{
httpClientName = string.Empty;
}
// 获取 HttpClientOptions 实例
var httpClientOptions = ServiceProvider?.GetService<IOptionsMonitor<HttpClientOptions>>()?.Get(httpClientName);
// 优先级:指定名称的 HttpClientOptions -> HttpRemoteOptions -> 默认值
return (httpClientOptions?.IsDefault != false ? null : httpClientOptions.JsonSerializerOptions) ??
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
HttpRemoteOptions.JsonSerializerOptionsDefault;
}
} }
/// <summary> /// <summary>
@@ -48,14 +78,13 @@ public class ObjectContentConverter<TResult> : ObjectContentConverter, IHttpCont
/// <inheritdoc /> /// <inheritdoc />
public virtual TResult? Read(HttpResponseMessage httpResponseMessage, public virtual TResult? Read(HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
httpResponseMessage.Content.ReadFromJsonAsync<TResult>( httpResponseMessage.Content
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ?? .ReadFromJsonAsync<TResult>(GetJsonSerializerOptions(httpResponseMessage), cancellationToken).GetAwaiter()
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).GetAwaiter().GetResult(); .GetResult();
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<TResult?> ReadAsync(HttpResponseMessage httpResponseMessage, public virtual async Task<TResult?> ReadAsync(HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
await httpResponseMessage.Content.ReadFromJsonAsync<TResult>( await httpResponseMessage.Content.ReadFromJsonAsync<TResult>(GetJsonSerializerOptions(httpResponseMessage),
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ?? cancellationToken).ConfigureAwait(false);
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).ConfigureAwait(false);
} }

View File

@@ -18,10 +18,10 @@ public class VoidContentConverter : HttpContentConverterBase<VoidContent>
{ {
/// <inheritdoc /> /// <inheritdoc />
public override VoidContent? Read(HttpResponseMessage httpResponseMessage, public override VoidContent? Read(HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => default; CancellationToken cancellationToken = default) => null;
/// <inheritdoc /> /// <inheritdoc />
public override Task<VoidContent?> ReadAsync(HttpResponseMessage httpResponseMessage, public override Task<VoidContent?> ReadAsync(HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
Task.FromResult<VoidContent?>(default); Task.FromResult<VoidContent?>(null);
} }

View File

@@ -0,0 +1,30 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式 HTTP 版本特性
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
public sealed class HttpVersionAttribute : Attribute
{
/// <summary>
/// <inheritdoc cref="HttpVersionAttribute" />
/// </summary>
/// <param name="version">HTTP 版本</param>
public HttpVersionAttribute(string? version) => Version = version;
/// <summary>
/// HTTP 版本
/// </summary>
public string? Version { get; set; }
}

View File

@@ -0,0 +1,30 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式请求来源地址特性
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
public sealed class RefererAttribute : Attribute
{
/// <summary>
/// <inheritdoc cref="RefererAttribute" />
/// </summary>
/// <param name="referer">请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址</param>
public RefererAttribute(string? referer) => Referer = referer;
/// <summary>
/// 请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址
/// </summary>
public string? Referer { get; set; }
}

View File

@@ -0,0 +1,45 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式异常抑制特性
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
public sealed class SuppressExceptionsAttribute : Attribute
{
/// <summary>
/// <inheritdoc cref="SuppressExceptionsAttribute" />
/// </summary>
/// <remarks>抑制所有异常。</remarks>
public SuppressExceptionsAttribute()
: this(true)
{
}
/// <summary>
/// <inheritdoc cref="SuppressExceptionsAttribute" />
/// </summary>
/// <param name="enabled">是否启用异常抑制。当设置为 <c>false</c> 时,将禁用异常抑制机制。</param>
public SuppressExceptionsAttribute(bool enabled) => Types = enabled ? [typeof(Exception)] : [];
/// <summary>
/// <inheritdoc cref="SuppressExceptionsAttribute" />
/// </summary>
/// <param name="types">异常抑制类型集合</param>
public SuppressExceptionsAttribute(params Type[] types) => Types = types;
/// <summary>
/// 异常抑制类型集合
/// </summary>
public Type[] Types { get; set; }
}

View File

@@ -44,8 +44,11 @@ public sealed class HttpDeclarativeBuilder
new(typeof(QueryDeclarativeExtractor), new QueryDeclarativeExtractor()), new(typeof(QueryDeclarativeExtractor), new QueryDeclarativeExtractor()),
new(typeof(PathDeclarativeExtractor), new PathDeclarativeExtractor()), new(typeof(PathDeclarativeExtractor), new PathDeclarativeExtractor()),
new(typeof(CookieDeclarativeExtractor), new CookieDeclarativeExtractor()), new(typeof(CookieDeclarativeExtractor), new CookieDeclarativeExtractor()),
new(typeof(RefererDeclarativeExtractor), new RefererDeclarativeExtractor()),
new(typeof(HeaderDeclarativeExtractor), new HeaderDeclarativeExtractor()), new(typeof(HeaderDeclarativeExtractor), new HeaderDeclarativeExtractor()),
new(typeof(PropertyDeclarativeExtractor), new PropertyDeclarativeExtractor()), new(typeof(PropertyDeclarativeExtractor), new PropertyDeclarativeExtractor()),
new(typeof(HttpVersionDeclarativeExtractor), new HttpVersionDeclarativeExtractor()),
new(typeof(SuppressExceptionsDeclarativeExtractor), new SuppressExceptionsDeclarativeExtractor()),
new(typeof(BodyDeclarativeExtractor), new BodyDeclarativeExtractor()) new(typeof(BodyDeclarativeExtractor), new BodyDeclarativeExtractor())
]); ]);

View File

@@ -45,7 +45,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
if (headerAttribute.HasSetValue) if (headerAttribute.HasSetValue)
{ {
httpRequestBuilder.WithHeader(headerName, headerAttribute.Value, headerAttribute.Escape, httpRequestBuilder.WithHeader(headerName, headerAttribute.Value, headerAttribute.Escape,
replace: headerAttribute.Replace); headerAttribute.Replace);
} }
// 移除请求标头 // 移除请求标头
else else
@@ -91,7 +91,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection()) if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection())
{ {
httpRequestBuilder.WithHeader(parameterName, value ?? headerAttribute.Value, httpRequestBuilder.WithHeader(parameterName, value ?? headerAttribute.Value,
headerAttribute.Escape, replace: headerAttribute.Replace); headerAttribute.Escape, headerAttribute.Replace);
continue; continue;
} }
@@ -99,7 +99,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
// 空检查 // 空检查
if (value is not null) if (value is not null)
{ {
httpRequestBuilder.WithHeaders(value, headerAttribute.Escape, replace: headerAttribute.Replace); httpRequestBuilder.WithHeaders(value, headerAttribute.Escape, headerAttribute.Replace);
} }
} }
} }

View File

@@ -0,0 +1,31 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式 <see cref="HttpVersionAttribute" /> 特性提取器
/// </summary>
internal sealed class HttpVersionDeclarativeExtractor : IHttpDeclarativeExtractor
{
/// <inheritdoc />
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
{
// 检查方法或接口是否贴有 [HttpVersion] 特性
if (!context.IsMethodDefined<HttpVersionAttribute>(out var versionAttribute, true))
{
return;
}
// 设置 HTTP 版本
httpRequestBuilder.SetVersion(versionAttribute.Version);
}
}

View File

@@ -45,7 +45,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
if (queryAttribute.HasSetValue) if (queryAttribute.HasSetValue)
{ {
httpRequestBuilder.WithQueryParameter(queryName, queryAttribute.Value, queryAttribute.Escape, httpRequestBuilder.WithQueryParameter(queryName, queryAttribute.Value, queryAttribute.Escape,
replace: queryAttribute.Replace, ignoreNullValues: queryAttribute.IgnoreNullValues); queryAttribute.Replace, queryAttribute.IgnoreNullValues);
} }
// 移除查询参数 // 移除查询参数
else else
@@ -91,8 +91,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection()) if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection())
{ {
httpRequestBuilder.WithQueryParameter(parameterName, value ?? queryAttribute.Value, httpRequestBuilder.WithQueryParameter(parameterName, value ?? queryAttribute.Value,
queryAttribute.Escape, replace: queryAttribute.Replace, queryAttribute.Escape, queryAttribute.Replace, queryAttribute.IgnoreNullValues);
ignoreNullValues: queryAttribute.IgnoreNullValues);
continue; continue;
} }
@@ -101,7 +100,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
if (value is not null) if (value is not null)
{ {
httpRequestBuilder.WithQueryParameters(value, queryAttribute.Prefix, queryAttribute.Escape, httpRequestBuilder.WithQueryParameters(value, queryAttribute.Prefix, queryAttribute.Escape,
replace: queryAttribute.Replace, ignoreNullValues: queryAttribute.IgnoreNullValues); queryAttribute.Replace, queryAttribute.IgnoreNullValues);
} }
} }
} }

View File

@@ -0,0 +1,31 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式 <see cref="RefererAttribute" /> 特性提取器
/// </summary>
internal sealed class RefererDeclarativeExtractor : IHttpDeclarativeExtractor
{
/// <inheritdoc />
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
{
// 检查方法或接口是否贴有 [Referer] 特性
if (!context.IsMethodDefined<RefererAttribute>(out var refererAttribute, true))
{
return;
}
// 设置请求来源地址
httpRequestBuilder.SetReferer(refererAttribute.Referer);
}
}

View File

@@ -0,0 +1,31 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.HttpRemote;
/// <summary>
/// HTTP 声明式 <see cref="SuppressExceptionsAttribute" /> 特性提取器
/// </summary>
internal sealed class SuppressExceptionsDeclarativeExtractor : IHttpDeclarativeExtractor
{
/// <inheritdoc />
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
{
// 检查方法或接口是否贴有 [SuppressExceptions] 特性
if (!context.IsMethodDefined<SuppressExceptionsAttribute>(out var suppressExceptionsAttribute, true))
{
return;
}
// 设置异常抑制
httpRequestBuilder.SuppressExceptions(suppressExceptionsAttribute.Types);
}
}

View File

@@ -25,7 +25,7 @@ public sealed class HttpDeclarativeExtractorContext
/// 冻结参数类型集合 /// 冻结参数类型集合
/// </summary> /// </summary>
/// <remarks>此类参数类型不应作为外部提取对象。</remarks> /// <remarks>此类参数类型不应作为外部提取对象。</remarks>
internal static Type[] _frozenParameterTypes = internal static readonly Type[] _frozenParameterTypes =
[ [
typeof(Action<HttpRequestBuilder>), typeof(Action<HttpMultipartFormDataBuilder>), typeof(HttpCompletionOption), typeof(Action<HttpRequestBuilder>), typeof(Action<HttpMultipartFormDataBuilder>), typeof(HttpCompletionOption),
typeof(CancellationToken) typeof(CancellationToken)

View File

@@ -42,7 +42,7 @@ public static partial class HttpContextExtensions
/// </item> /// </item>
/// </list> /// </list>
/// </remarks> /// </remarks>
internal static HashSet<string> _ignoreResponseHeaders = internal static readonly HashSet<string> _ignoreResponseHeaders =
[ [
"Content-Type", "Connection", "Transfer-Encoding", "Keep-Alive", "Upgrade", "Proxy-Connection" "Content-Type", "Connection", "Transfer-Encoding", "Keep-Alive", "Upgrade", "Proxy-Connection"
]; ];
@@ -64,7 +64,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static HttpResponseMessage Forward(this HttpContext? httpContext, string? requestUri = null, public static HttpResponseMessage? Forward(this HttpContext? httpContext, string? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -90,7 +90,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static HttpResponseMessage Forward(this HttpContext? httpContext, HttpMethod httpMethod, public static HttpResponseMessage? Forward(this HttpContext? httpContext, HttpMethod httpMethod,
string? requestUri = null, Action<HttpRequestBuilder>? configure = null, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -115,7 +115,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static HttpResponseMessage Forward(this HttpContext? httpContext, Uri? requestUri = null, public static HttpResponseMessage? Forward(this HttpContext? httpContext, Uri? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -140,7 +140,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static HttpResponseMessage Forward(this HttpContext? httpContext, HttpMethod httpMethod, public static HttpResponseMessage? Forward(this HttpContext? httpContext, HttpMethod httpMethod,
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) HttpContextForwardOptions? forwardOptions = null)
@@ -180,7 +180,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, string? requestUri = null, public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, string? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -206,7 +206,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod, public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
string? requestUri = null, Action<HttpRequestBuilder>? configure = null, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -231,7 +231,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, Uri? requestUri = null, public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, Uri? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -256,7 +256,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
public static async Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod, public static async Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) HttpContextForwardOptions? forwardOptions = null)
@@ -297,7 +297,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, string? requestUri = null, public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, string? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -324,7 +324,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod, public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
string? requestUri = null, Action<HttpRequestBuilder>? configure = null, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -350,7 +350,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, Uri? requestUri = null, public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, Uri? requestUri = null,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -376,7 +376,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod, public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) HttpContextForwardOptions? forwardOptions = null)
@@ -393,7 +393,7 @@ public static partial class HttpContextExtensions
var result = httpRemoteService.Send<TResult>(httpRequestBuilder, completionOption, httpContext.RequestAborted); var result = httpRemoteService.Send<TResult>(httpRequestBuilder, completionOption, httpContext.RequestAborted);
// 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中 // 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中
ForwardResponseMessage(httpContext, result.ResponseMessage, httpContextForwardBuilder.ForwardOptions); ForwardResponseMessage(httpContext, result?.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
return result; return result;
} }
@@ -416,7 +416,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext, public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
string? requestUri = null, Action<HttpRequestBuilder>? configure = null, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -443,7 +443,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext, public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
HttpMethod httpMethod, string? requestUri = null, Action<HttpRequestBuilder>? configure = null, HttpMethod httpMethod, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -469,7 +469,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext, public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) => HttpContextForwardOptions? forwardOptions = null) =>
@@ -495,7 +495,7 @@ public static partial class HttpContextExtensions
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
public static async Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext, public static async Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
HttpMethod httpMethod, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null, HttpMethod httpMethod, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
HttpContextForwardOptions? forwardOptions = null) HttpContextForwardOptions? forwardOptions = null)
@@ -513,7 +513,7 @@ public static partial class HttpContextExtensions
httpContext.RequestAborted).ConfigureAwait(false); httpContext.RequestAborted).ConfigureAwait(false);
// 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中 // 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中
ForwardResponseMessage(httpContext, result.ResponseMessage, httpContextForwardBuilder.ForwardOptions); ForwardResponseMessage(httpContext, result?.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
return result; return result;
} }
@@ -605,14 +605,22 @@ public static partial class HttpContextExtensions
/// <param name="forwardOptions"> /// <param name="forwardOptions">
/// <see cref="HttpContextForwardOptions" /> /// <see cref="HttpContextForwardOptions" />
/// </param> /// </param>
internal static void ForwardResponseMessage(HttpContext httpContext, HttpResponseMessage httpResponseMessage, internal static void ForwardResponseMessage(HttpContext httpContext, HttpResponseMessage? httpResponseMessage,
HttpContextForwardOptions forwardOptions) HttpContextForwardOptions forwardOptions)
{ {
// 空检查 // 空检查
ArgumentNullException.ThrowIfNull(httpContext); ArgumentNullException.ThrowIfNull(httpContext);
ArgumentNullException.ThrowIfNull(httpResponseMessage);
ArgumentNullException.ThrowIfNull(forwardOptions); ArgumentNullException.ThrowIfNull(forwardOptions);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 获取 HttpResponse 实例 // 获取 HttpResponse 实例
var httpResponse = httpContext.Response; var httpResponse = httpContext.Response;

View File

@@ -0,0 +1,88 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using System.Text;
namespace ThingsGateway.HttpRemote;
/// <summary>
/// <see cref="HttpMultipartFormDataBuilder" /> 拓展类
/// </summary>
public static class HttpMultipartFormDataBuilderExtensions
{
/// <summary>
/// 添加文件
/// </summary>
/// <param name="httpMultipartFormDataBuilder">
/// <see cref="HttpMultipartFormDataBuilder" />
/// </param>
/// <param name="formFile">
/// <see cref="IFormFile" />
/// </param>
/// <param name="name">表单名称</param>
/// <param name="fileName">文件的名称</param>
/// <param name="contentType">内容类型</param>
/// <param name="contentEncoding">内容编码</param>
/// <returns>
/// <see cref="HttpMultipartFormDataBuilder" />
/// </returns>
public static HttpMultipartFormDataBuilder AddFile(this HttpMultipartFormDataBuilder httpMultipartFormDataBuilder,
IFormFile formFile, string? name = null, string? fileName = null, string? contentType = null,
Encoding? contentEncoding = null)
{
// 空检查
ArgumentNullException.ThrowIfNull(formFile);
// 初始化 MemoryStream 实例
var memoryStream = new MemoryStream();
// 将 IFormFile 内容复制到内存流
formFile.CopyTo(memoryStream);
// 将内存流的位置重置到起始位置
memoryStream.Position = 0;
// 添加文件流
return httpMultipartFormDataBuilder.AddStream(memoryStream, name ?? formFile.Name,
fileName ?? formFile.FileName, contentType ?? formFile.ContentType, contentEncoding,
true);
}
/// <summary>
/// 添加多个文件
/// </summary>
/// <param name="httpMultipartFormDataBuilder">
/// <see cref="HttpMultipartFormDataBuilder" />
/// </param>
/// <param name="formFiles">
/// <see cref="IFormFileCollection" />
/// </param>
/// <param name="name">表单名称</param>
/// <returns>
/// <see cref="HttpMultipartFormDataBuilder" />
/// </returns>
public static HttpMultipartFormDataBuilder AddFiles(this HttpMultipartFormDataBuilder httpMultipartFormDataBuilder,
IEnumerable<IFormFile> formFiles, string? name = null)
{
// 空检查
ArgumentNullException.ThrowIfNull(formFiles);
// 逐条添加文件
foreach (var formFile in formFiles)
{
httpMultipartFormDataBuilder.AddFile(formFile, name ?? formFile.Name);
}
return httpMultipartFormDataBuilder;
}
}

View File

@@ -16,6 +16,7 @@ using Microsoft.Net.Http.Headers;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using ThingsGateway.Extensions; using ThingsGateway.Extensions;
using ThingsGateway.Utilities; using ThingsGateway.Utilities;
@@ -27,7 +28,7 @@ namespace ThingsGateway.HttpRemote.Extensions;
/// <summary> /// <summary>
/// HTTP 远程服务拓展类 /// HTTP 远程服务拓展类
/// </summary> /// </summary>
public static class HttpRemoteExtensions public static partial class HttpRemoteExtensions
{ {
/// <summary> /// <summary>
/// 添加 HTTP 远程请求分析工具处理委托 /// 添加 HTTP 远程请求分析工具处理委托
@@ -70,6 +71,56 @@ public static class HttpRemoteExtensions
builder.AddProfilerDelegatingHandler(() => builder.AddProfilerDelegatingHandler(() =>
disableInProduction && GetHostEnvironmentName(builder.Services)?.ToLower() == "production"); disableInProduction && GetHostEnvironmentName(builder.Services)?.ToLower() == "production");
/// <summary>
/// 配置 <see cref="HttpClient" /> 额外选项
/// </summary>
/// <param name="builder">
/// <see cref="IHttpClientBuilder" />
/// </param>
/// <param name="configure">自定义配置选项</param>
/// <returns>
/// <see cref="IHttpClientBuilder" />
/// </returns>
public static IHttpClientBuilder ConfigureOptions(this IHttpClientBuilder builder,
Action<HttpClientOptions> configure)
{
// 空检查
ArgumentNullException.ThrowIfNull(configure);
builder.Services.AddOptions<HttpClientOptions>(builder.Name).Configure(options =>
{
options.IsDefault = false;
configure.Invoke(options);
});
return builder;
}
/// <summary>
/// 配置 <see cref="HttpClient" /> 额外选项
/// </summary>
/// <param name="builder">
/// <see cref="IHttpClientBuilder" />
/// </param>
/// <param name="configure">自定义配置选项</param>
/// <returns>
/// <see cref="IHttpClientBuilder" />
/// </returns>
public static IHttpClientBuilder ConfigureOptions(this IHttpClientBuilder builder,
Action<HttpClientOptions, IServiceProvider> configure)
{
// 空检查
ArgumentNullException.ThrowIfNull(configure);
builder.Services.AddOptions<HttpClientOptions>(builder.Name).Configure<IServiceProvider>((options, provider) =>
{
options.IsDefault = false;
configure.Invoke(options, provider);
});
return builder;
}
/// <summary> /// <summary>
/// 为 <see cref="HttpClient" /> 启用性能优化 /// 为 <see cref="HttpClient" /> 启用性能优化
/// </summary> /// </summary>
@@ -160,17 +211,26 @@ public static class HttpRemoteExtensions
? [new KeyValuePair<string, IEnumerable<string>>("Declarative", [methodSignature])] ? [new KeyValuePair<string, IEnumerable<string>>("Declarative", [methodSignature])]
: null; : null;
// 格式化 HttpClient 实例的配置条目
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? httpClientKeyValues =
httpRequestMessage.Options.TryGetValue(new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME),
out var httpClientName)
? [new KeyValuePair<string, IEnumerable<string>>("HttpClient Name", [httpClientName])]
: null;
// 格式化常规条目 // 格式化常规条目
var generalEntry = StringUtility.FormatKeyValuesSummary(new[] var generalEntry = StringUtility.FormatKeyValuesSummary(new[]
{ {
new KeyValuePair<string, IEnumerable<string>>("Request URL", new KeyValuePair<string, IEnumerable<string>>("Request URL",
[httpRequestMessage.RequestUri?.OriginalString!]), [httpRequestMessage.RequestUri?.OriginalString!]),
new KeyValuePair<string, IEnumerable<string>>("HTTP Method", [httpRequestMessage.Method.ToString()]), new KeyValuePair<string, IEnumerable<string>>("HTTP Method", [httpRequestMessage.Method.ToString()]),
new KeyValuePair<string, IEnumerable<string>>("Status Code", new KeyValuePair<string, IEnumerable<string>>("Status Code",
[$"{(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"]), [$"{(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"]),
new KeyValuePair<string, IEnumerable<string>>("HTTP Content", new KeyValuePair<string, IEnumerable<string>>("HTTP Version", [httpResponseMessage.Version.ToString()]),
[$"{httpContent?.GetType().Name}"]) new KeyValuePair<string, IEnumerable<string>>("HTTP Content",
}.ConcatIgnoreNull(declarativeKeyValues).ConcatIgnoreNull(generalCustomKeyValues), generalSummary); [$"{httpContent?.GetType().Name}"])
}.ConcatIgnoreNull(httpClientKeyValues).ConcatIgnoreNull(declarativeKeyValues)
.ConcatIgnoreNull(generalCustomKeyValues), generalSummary);
// 格式化响应条目 // 格式化响应条目
var responseEntry = httpResponseMessage.ProfilerHeaders(responseSummary); var responseEntry = httpResponseMessage.ProfilerHeaders(responseSummary);
@@ -203,19 +263,43 @@ public static class HttpRemoteExtensions
// 默认只读取 5KB 的内容 // 默认只读取 5KB 的内容
const int maxBytesToDisplay = 5120; const int maxBytesToDisplay = 5120;
// 读取内容为字节数组 /*
* 读取内容为字节数组
*
* 由于 HttpContent 的流设计为单次读取(即流内容在首次读取后会被消耗,无法重复读取),
* 当前实现(即使用 ReadAsByteArrayAsync(cancellationToken))中对于较大内容会一次性加载至内存,
* 这可能导致性能问题(如内存占用过高或响应延迟),不过目前尚未找到更优的解决方案。
*
* 强烈建议在生产环境中禁用或关闭此类一次性读取操作,尤其是对于高并发或大流量场景,
* 以避免因内存溢出OOM或线程阻塞导致的服务不可用风险。
*/
var buffer = await httpContent.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); var buffer = await httpContent.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
var total = buffer.Length;
// 计算要显示的部分 // 计算要显示的部分
var bytesToShow = Math.Min(buffer.Length, maxBytesToDisplay); var bytesToShow = Math.Min(total, maxBytesToDisplay);
var partialContent = Encoding.UTF8.GetString(buffer, 0, bytesToShow);
// 注册 CodePagesEncodingProvider使得程序能够识别并使用 Windows 代码页中的各种编码
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// 获取内容编码
var charset = httpContent.Headers.ContentType?.CharSet ?? "utf-8";
var partialContent = Encoding.GetEncoding(charset).GetString(buffer, 0, bytesToShow);
// 检查是否是完整的 Unicode 转义字符串
if (total == bytesToShow && UnicodeEscapeRegex().IsMatch(partialContent))
{
partialContent = Regex.Unescape(partialContent);
}
// 如果实际读取的数据小于最大显示大小,则直接返回;否则,添加省略号表示内容被截断 // 如果实际读取的数据小于最大显示大小,则直接返回;否则,添加省略号表示内容被截断
var bodyString = buffer.Length <= maxBytesToDisplay ? partialContent : partialContent + " ... [truncated]"; var bodyString = total <= maxBytesToDisplay
? partialContent
: partialContent + $" ... [truncated, total: {total} bytes]";
return StringUtility.FormatKeyValuesSummary( return StringUtility.FormatKeyValuesSummary(
[new KeyValuePair<string, IEnumerable<string>>(string.Empty, [bodyString])], [new KeyValuePair<string, IEnumerable<string>>(string.Empty, [bodyString])],
$"{summary} ({httpContent.GetType().Name})"); $"{summary} ({httpContent.GetType().Name}, total: {total} bytes)");
} }
/// <summary> /// <summary>
@@ -359,4 +443,13 @@ public static class HttpRemoteExtensions
? null ? null
: Convert.ToString(hostEnvironment.GetType().GetProperty("EnvironmentName")?.GetValue(hostEnvironment)); : Convert.ToString(hostEnvironment.GetType().GetProperty("EnvironmentName")?.GetValue(hostEnvironment));
} }
/// <summary>
/// Unicode 转义正则表达式
/// </summary>
/// <returns>
/// <see cref="Regex" />
/// </returns>
[GeneratedRegex(@"\\u([0-9a-fA-F]{4})")]
private static partial Regex UnicodeEscapeRegex();
} }

View File

@@ -52,27 +52,44 @@ internal sealed class HttpContentConverterFactory : IHttpContentConverterFactory
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
/// <inheritdoc /> /// <inheritdoc />
public TResult? Read<TResult>(HttpResponseMessage httpResponseMessage, IHttpContentConverter[]? converters = null, public TResult? Read<TResult>(HttpResponseMessage? httpResponseMessage, IHttpContentConverter[]? converters = null,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
GetConverter<TResult>(converters).Read(httpResponseMessage, cancellationToken); httpResponseMessage is null
? default
: GetConverter<TResult>(converters).Read(httpResponseMessage, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public object? Read(Type resultType, HttpResponseMessage httpResponseMessage, public object? Read(Type resultType, HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default) =>
CancellationToken cancellationToken = default) => httpResponseMessage is null
GetConverter(resultType, converters).Read(resultType, httpResponseMessage, cancellationToken); ? null
: GetConverter(resultType, converters).Read(resultType, httpResponseMessage, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public async Task<TResult?> ReadAsync<TResult>(HttpResponseMessage httpResponseMessage, public async Task<TResult?> ReadAsync<TResult>(HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default) => {
await GetConverter<TResult>(converters).ReadAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false); // 空检查
if (httpResponseMessage is null)
{
return default;
}
return await GetConverter<TResult>(converters).ReadAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc /> /// <inheritdoc />
public async Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage, public async Task<object?> ReadAsync(Type resultType, HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default) => {
await GetConverter(resultType, converters).ReadAsync(resultType, httpResponseMessage, cancellationToken).ConfigureAwait(false); // 空检查
if (httpResponseMessage is null)
{
return null;
}
return await GetConverter(resultType, converters).ReadAsync(resultType, httpResponseMessage, cancellationToken).ConfigureAwait(false);
}
/// <summary> /// <summary>
/// 获取 <see cref="IHttpContentConverter{TResult}" /> 实例 /// 获取 <see cref="IHttpContentConverter{TResult}" /> 实例

View File

@@ -37,7 +37,7 @@ public interface IHttpContentConverterFactory
/// <returns> /// <returns>
/// <typeparamref name="TResult" /> /// <typeparamref name="TResult" />
/// </returns> /// </returns>
TResult? Read<TResult>(HttpResponseMessage httpResponseMessage, TResult? Read<TResult>(HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default); IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -54,7 +54,7 @@ public interface IHttpContentConverterFactory
/// <returns> /// <returns>
/// <see cref="object" /> /// <see cref="object" />
/// </returns> /// </returns>
object? Read(Type resultType, HttpResponseMessage httpResponseMessage, object? Read(Type resultType, HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default); IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -73,7 +73,7 @@ public interface IHttpContentConverterFactory
/// <returns> /// <returns>
/// <typeparamref name="TResult" /> /// <typeparamref name="TResult" />
/// </returns> /// </returns>
Task<TResult?> ReadAsync<TResult>(HttpResponseMessage httpResponseMessage, Task<TResult?> ReadAsync<TResult>(HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default); IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -90,6 +90,6 @@ public interface IHttpContentConverterFactory
/// <returns> /// <returns>
/// <see cref="object" /> /// <see cref="object" />
/// </returns> /// </returns>
Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage, Task<object?> ReadAsync(Type resultType, HttpResponseMessage? httpResponseMessage,
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default); IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
} }

View File

@@ -115,6 +115,15 @@ internal sealed class FileDownloadManager
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead, var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead,
cancellationToken); cancellationToken);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 根据文件是否存在及配置的行为来决定是否应继续进行文件下载 // 根据文件是否存在及配置的行为来决定是否应继续进行文件下载
if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath)) if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath))
{ {
@@ -228,6 +237,15 @@ internal sealed class FileDownloadManager
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder, var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder,
HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 根据文件是否存在及配置的行为来决定是否应继续进行文件下载 // 根据文件是否存在及配置的行为来决定是否应继续进行文件下载
if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath)) if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath))
{ {
@@ -246,7 +264,7 @@ internal sealed class FileDownloadManager
bufferSize, true); bufferSize, true);
// 获取 HTTP 响应体中的内容流 // 获取 HTTP 响应体中的内容流
using var contentStream = (await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)); using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
// 循环读取数据直到取消请求或读取完毕 // 循环读取数据直到取消请求或读取完毕
int numBytesRead; int numBytesRead;
@@ -485,6 +503,9 @@ internal sealed class FileDownloadManager
/// </returns> /// </returns>
internal string GetFileName(HttpResponseMessage httpResponseMessage) internal string GetFileName(HttpResponseMessage httpResponseMessage)
{ {
// 空检查
ArgumentNullException.ThrowIfNull(httpResponseMessage);
// 获取文件下载保存的文件的名称 // 获取文件下载保存的文件的名称
var fileName = Path.GetFileName(_httpFileDownloadBuilder.DestinationPath); var fileName = Path.GetFileName(_httpFileDownloadBuilder.DestinationPath);

View File

@@ -88,7 +88,7 @@ internal sealed class FileUploadManager
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
internal HttpResponseMessage Start(CancellationToken cancellationToken = default) internal HttpResponseMessage? Start(CancellationToken cancellationToken = default)
{ {
// 创建进度报告任务取消标识 // 创建进度报告任务取消标识
using var progressCancellationTokenSource = new CancellationTokenSource(); using var progressCancellationTokenSource = new CancellationTokenSource();
@@ -102,7 +102,7 @@ internal sealed class FileUploadManager
// 初始化 Stopwatch 实例并开启计时操作 // 初始化 Stopwatch 实例并开启计时操作
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
HttpResponseMessage httpResponseMessage; HttpResponseMessage? httpResponseMessage;
try try
{ {
@@ -147,7 +147,7 @@ internal sealed class FileUploadManager
/// <returns> /// <returns>
/// <see cref="Task{TResult}" /> /// <see cref="Task{TResult}" />
/// </returns> /// </returns>
internal async Task<HttpResponseMessage> StartAsync(CancellationToken cancellationToken = default) internal async Task<HttpResponseMessage?> StartAsync(CancellationToken cancellationToken = default)
{ {
// 创建进度报告任务取消标识 // 创建进度报告任务取消标识
using var progressCancellationTokenSource = new CancellationTokenSource(); using var progressCancellationTokenSource = new CancellationTokenSource();
@@ -161,7 +161,7 @@ internal sealed class FileUploadManager
// 初始化 Stopwatch 实例并开启计时操作 // 初始化 Stopwatch 实例并开启计时操作
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
HttpResponseMessage httpResponseMessage; HttpResponseMessage? httpResponseMessage;
try try
{ {

View File

@@ -97,6 +97,15 @@ internal sealed class LongPollingManager
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, cancellationToken); var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, cancellationToken);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
continue;
}
// 发送响应数据对象到通道 // 发送响应数据对象到通道
dataChannel.Writer.TryWrite(httpResponseMessage); dataChannel.Writer.TryWrite(httpResponseMessage);
@@ -167,6 +176,15 @@ internal sealed class LongPollingManager
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder, cancellationToken).ConfigureAwait(false); var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder, cancellationToken).ConfigureAwait(false);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
continue;
}
// 发送响应数据对象到通道 // 发送响应数据对象到通道
await dataChannel.Writer.WriteAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false); await dataChannel.Writer.WriteAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false);

View File

@@ -109,6 +109,15 @@ internal sealed class ServerSentEventsManager
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead, var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead,
cancellationToken); cancellationToken);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 获取 HTTP 响应体中的内容流 // 获取 HTTP 响应体中的内容流
using var contentStream = httpResponseMessage.Content.ReadAsStream(cancellationToken); using var contentStream = httpResponseMessage.Content.ReadAsStream(cancellationToken);
@@ -203,9 +212,17 @@ internal sealed class ServerSentEventsManager
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder, var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder,
HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
// 获取 HTTP 响应体中的内容流 // 空检查
using var contentStream = (await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)); if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 获取 HTTP 响应体中的内容流
using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
// 初始化 StreamReader 实例 // 初始化 StreamReader 实例
using var streamReader = new StreamReader(contentStream, Encoding.UTF8); using var streamReader = new StreamReader(contentStream, Encoding.UTF8);
@@ -382,10 +399,10 @@ internal sealed class ServerSentEventsManager
? retryInterval ? retryInterval
: _httpServerSentEventsBuilder.DefaultRetryInterval; : _httpServerSentEventsBuilder.DefaultRetryInterval;
break; break;
// 所有其他的字段名都会被忽略 // 其他的字段名存储在 CustomFields 属性中
default: default:
// 保持数据不变 serverSentEventsData.AddCustomField(key, value);
return true; break;
} }
return true; return true;

View File

@@ -143,6 +143,15 @@ internal sealed class StressTestHarnessManager
var httpResponseMessage = var httpResponseMessage =
await _httpRemoteService.SendAsync(RequestBuilder, completionOption, cancellationToken).ConfigureAwait(false); await _httpRemoteService.SendAsync(RequestBuilder, completionOption, cancellationToken).ConfigureAwait(false);
// 空检查
if (httpResponseMessage is null)
{
// 输出调试信息
Debugging.Error("The response content was not read, as it was empty.");
return;
}
// 检查响应状态码是否是成功状态 // 检查响应状态码是否是成功状态
if (httpResponseMessage.IsSuccessStatusCode) if (httpResponseMessage.IsSuccessStatusCode)
{ {

View File

@@ -36,7 +36,7 @@ public sealed class HttpRemoteAnalyzer
/// <summary> /// <summary>
/// 分析数据 /// 分析数据
/// </summary> /// </summary>
public string Data => _cachedData ??= _dataBuffer.ToString(); public string Data => _cachedData ??= _dataBuffer.ToString().TrimEnd(Environment.NewLine.ToCharArray());
/// <summary> /// <summary>
/// 追加分析数据 /// 追加分析数据
@@ -44,7 +44,7 @@ public sealed class HttpRemoteAnalyzer
/// <param name="value">分析数据</param> /// <param name="value">分析数据</param>
internal void AppendData(string? value) internal void AppendData(string? value)
{ {
_dataBuffer.Append(value); _dataBuffer.AppendLine(value);
_cachedData = null; _cachedData = null;
} }

View File

@@ -0,0 +1,186 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.HttpRemote;
/// <summary>
/// 提供静态访问 <see cref="IHttpRemoteService" /> 服务的方式
/// </summary>
/// <remarks>支持服务的延迟初始化、配置更新以及资源释放。</remarks>
#pragma warning disable CA1513
public static class HttpRemoteClient
{
/// <inheritdoc cref="IServiceProvider" />
internal static IServiceProvider? _serviceProvider;
/// <summary>
/// 延迟加载的 <see cref="IHttpRemoteService" /> 实例
/// </summary>
internal static Lazy<IHttpRemoteService> _lazyService;
/// <summary>
/// 并发锁对象
/// </summary>
internal static readonly object _lock = new();
/// <summary>
/// 标记服务是否已释放
/// </summary>
internal static bool _isDisposed;
/// <summary>
/// 自定义服务注册逻辑的委托
/// </summary>
internal static Action<IServiceCollection> _configure = services => services.AddHttpRemote();
/// <summary>
/// <inheritdoc cref="HttpRemoteClient" />
/// </summary>
static HttpRemoteClient() => _lazyService = new Lazy<IHttpRemoteService>(CreateService);
/// <summary>
/// 获取当前配置下的 <see cref="IHttpRemoteService" /> 实例
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
public static IHttpRemoteService Service
{
get
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(HttpRemoteClient));
}
return _lazyService.Value;
}
}
/// <summary>
/// 自定义服务注册逻辑
/// </summary>
public static void Configure(Action<IServiceCollection> configure)
{
// 空检查
ArgumentNullException.ThrowIfNull(configure);
lock (_lock)
{
_configure = configure;
// 重新初始化服务
Reinitialize();
}
}
/// <summary>
/// 释放服务提供器及相关资源
/// </summary>
/// <remarks>通常在应用程序关闭或不再需要 HTTP 远程请求服务时调用。</remarks>
public static void Dispose()
{
lock (_lock)
{
if (_isDisposed)
{
return;
}
// 释放服务提供器
ReleaseServiceProvider();
_isDisposed = true;
}
}
/// <summary>
/// 创建 <see cref="IHttpRemoteService" /> 实例
/// </summary>
/// <returns>
/// <see cref="IHttpRemoteService" />
/// </returns>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="InvalidOperationException"></exception>
internal static IHttpRemoteService CreateService()
{
lock (_lock)
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(HttpRemoteClient));
}
// 如果值已创建,直接返回
if (_lazyService.IsValueCreated)
{
return _lazyService.Value;
}
try
{
// 初始化 ServiceCollection 实例
var services = new ServiceCollection();
// 调用自定义服务注册逻辑的委托
_configure(services);
// 构建服务提供器
_serviceProvider = services.BuildServiceProvider();
// 解析 IHttpRemoteService 实例
var service = _serviceProvider.GetRequiredService<IHttpRemoteService>();
return service;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to initialize IHttpRemoteService.", ex);
}
}
}
/// <summary>
/// 使用最新的 <see cref="Configure" /> 配置重新初始化服务
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
internal static void Reinitialize()
{
lock (_lock)
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(HttpRemoteClient));
}
// 释放当前的服务提供器
ReleaseServiceProvider();
// 重新创建延迟加载实例
_lazyService = new Lazy<IHttpRemoteService>(CreateService, LazyThreadSafetyMode.ExecutionAndPublication);
}
}
/// <summary>
/// 释放服务提供器
/// </summary>
internal static void ReleaseServiceProvider()
{
// 如果服务提供器支持释放资源,则执行释放操作
if (_serviceProvider is IDisposable disposable)
{
disposable.Dispose();
}
_serviceProvider = null;
}
}
#pragma warning restore CA1513

View File

@@ -108,6 +108,80 @@ public sealed class HttpRemoteResult<TResult>
/// </summary> /// </summary>
public HttpContentHeaders ContentHeaders { get; private set; } = null!; public HttpContentHeaders ContentHeaders { get; private set; } = null!;
/// <summary>
/// HTTP 版本
/// </summary>
public Version Version { get; private set; } = null!;
/// <summary>
/// <see cref="HttpClient" /> 实例的配置名称
/// </summary>
public string? HttpClientName { get; private set; }
// /// <summary>
// /// 解构函数(至少包含两个 out 参数!!!)
// /// </summary>
// /// <param name="result">
// /// <typeparamref name="TResult" />
// /// </param>
// public void Deconstruct(out TResult? result)
// {
// result = Result;
// }
/// <summary>
/// 解构函数
/// </summary>
/// <param name="result">
/// <typeparamref name="TResult" />
/// </param>
/// <param name="httpResponseMessage">
/// <inheritdoc cref="HttpResponseMessage" />
/// </param>
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage)
{
result = Result;
httpResponseMessage = ResponseMessage;
}
/// <summary>
/// 解构函数
/// </summary>
/// <param name="result">
/// <typeparamref name="TResult" />
/// </param>
/// <param name="httpResponseMessage">
/// <inheritdoc cref="HttpResponseMessage" />
/// </param>
/// <param name="isSuccessStatusCode">是否请求成功</param>
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage,
out bool isSuccessStatusCode)
{
result = Result;
httpResponseMessage = ResponseMessage;
isSuccessStatusCode = IsSuccessStatusCode;
}
/// <summary>
/// 解构函数
/// </summary>
/// <param name="result">
/// <typeparamref name="TResult" />
/// </param>
/// <param name="httpResponseMessage">
/// <inheritdoc cref="HttpResponseMessage" />
/// </param>
/// <param name="isSuccessStatusCode">是否请求成功</param>
/// <param name="statusCode">响应状态码</param>
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage,
out bool isSuccessStatusCode, out HttpStatusCode statusCode)
{
result = Result;
httpResponseMessage = ResponseMessage;
isSuccessStatusCode = IsSuccessStatusCode;
statusCode = StatusCode;
}
/// <summary> /// <summary>
/// 初始化 /// 初始化
/// </summary> /// </summary>
@@ -124,6 +198,16 @@ public sealed class HttpRemoteResult<TResult>
// 解析响应标头 Set-Cookie 集合 // 解析响应标头 Set-Cookie 集合
ParseSetCookies(ResponseMessage.Headers); ParseSetCookies(ResponseMessage.Headers);
// 获取 HTTP 版本
Version = ResponseMessage.Version;
// 获取 HttpClient 实例的配置名称
if (ResponseMessage.RequestMessage?.Options.TryGetValue(
new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME), out var httpClientName) == true)
{
HttpClientName = httpClientName;
}
} }
/// <summary> /// <summary>

View File

@@ -9,6 +9,7 @@
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
using System.Collections.ObjectModel;
using System.Text; using System.Text;
namespace ThingsGateway.HttpRemote; namespace ThingsGateway.HttpRemote;
@@ -19,6 +20,11 @@ namespace ThingsGateway.HttpRemote;
/// <remarks>参考文献https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events#%E5%AD%97%E6%AE%B5。</remarks> /// <remarks>参考文献https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events#%E5%AD%97%E6%AE%B5。</remarks>
public sealed class ServerSentEventsData public sealed class ServerSentEventsData
{ {
/// <summary>
/// 用于存储自定义的字段数据
/// </summary>
internal readonly List<KeyValuePair<string, string>> _customFields;
/// <summary> /// <summary>
/// 消息数据构建器 /// 消息数据构建器
/// </summary> /// </summary>
@@ -32,7 +38,11 @@ public sealed class ServerSentEventsData
/// <summary> /// <summary>
/// <inheritdoc cref="ServerSentEventsData" /> /// <inheritdoc cref="ServerSentEventsData" />
/// </summary> /// </summary>
internal ServerSentEventsData() => _dataBuffer = new StringBuilder(); internal ServerSentEventsData()
{
_dataBuffer = new StringBuilder();
_customFields = [];
}
/// <summary> /// <summary>
/// 事件类型 /// 事件类型
@@ -61,6 +71,12 @@ public sealed class ServerSentEventsData
/// <remarks>重新连接的时间。如果与服务器的连接丢失,浏览器将等待指定的时间,然后尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接的时间。如果指定了一个非整数值,该字段将被忽略。</remarks> /// <remarks>重新连接的时间。如果与服务器的连接丢失,浏览器将等待指定的时间,然后尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接的时间。如果指定了一个非整数值,该字段将被忽略。</remarks>
public int Retry { get; internal set; } public int Retry { get; internal set; }
/// <summary>
/// 自定义的字段数据
/// </summary>
public IReadOnlyCollection<KeyValuePair<string, string>> CustomFields =>
new ReadOnlyCollection<KeyValuePair<string, string>>(_customFields);
/// <summary> /// <summary>
/// 追加消息数据 /// 追加消息数据
/// </summary> /// </summary>
@@ -70,4 +86,12 @@ public sealed class ServerSentEventsData
_dataBuffer.Append(value); _dataBuffer.Append(value);
_cachedData = null; _cachedData = null;
} }
/// <summary>
/// 追加自定义字段数据
/// </summary>
/// <param name="name">字段名</param>
/// <param name="value">字段数据</param>
internal void AddCustomField(string name, string value) =>
_customFields.Add(new KeyValuePair<string, string>(name, value));
} }

View File

@@ -0,0 +1,34 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.Extensions.Options;
using System.Text.Json;
namespace ThingsGateway.HttpRemote;
/// <summary>
/// <see cref="HttpClient" /> 配置选项
/// </summary>
public sealed class HttpClientOptions
{
/// <summary>
/// JSON 序列化配置
/// </summary>
public JsonSerializerOptions JsonSerializerOptions { get; set; } =
new(HttpRemoteOptions.JsonSerializerOptionsDefault);
/// <summary>
/// 标识选项是否配置为默认值(未配置)
/// </summary>
/// <remarks>用于避免通过 <see cref="IOptionsSnapshot{TOptions}" /> 获取选项时无法确定是否已配置该选项。默认值为:<c>true</c>。</remarks>
internal bool IsDefault { get; set; } = true;
}

View File

@@ -12,9 +12,12 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ThingsGateway.Converters.Json;
namespace ThingsGateway.HttpRemote; namespace ThingsGateway.HttpRemote;
/// <summary> /// <summary>
@@ -30,7 +33,18 @@ public sealed class HttpRemoteOptions
{ {
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString // 允许 String 转 Number
NumberHandling = JsonNumberHandling.AllowReadingFromString,
// 解决中文乱码问题
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
AllowTrailingCommas = true,
Converters =
{
new DateTimeConverterUsingDateTimeParseAsFallback(),
new DateTimeOffsetConverterUsingDateTimeOffsetParseAsFallback(),
// 允许 Number 或 Boolean 转 String
new StringJsonConverter()
}
}; };
/// <summary> /// <summary>

View File

@@ -18,6 +18,7 @@ using System.Net.Http.Json;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
using ThingsGateway.Extensions; using ThingsGateway.Extensions;
@@ -52,7 +53,7 @@ public class StringContentProcessor : HttpContentProcessorBase
} }
// 将原始请求内容转换为字符串 // 将原始请求内容转换为字符串
var content = rawContent.GetType().IsBasicType() || rawContent is JsonElement var content = rawContent.GetType().IsBasicType() || rawContent is JsonElement or JsonNode
? rawContent.ToCultureString(CultureInfo.InvariantCulture) ? rawContent.ToCultureString(CultureInfo.InvariantCulture)
: JsonSerializer.Serialize(rawContent, : JsonSerializer.Serialize(rawContent,
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ?? ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??

View File

@@ -51,7 +51,7 @@ internal sealed partial class HttpRemoteService
new FileDownloadManager(this, httpFileDownloadBuilder, configure).StartAsync(cancellationToken); new FileDownloadManager(this, httpFileDownloadBuilder, configure).StartAsync(cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage UploadFile(string? requestUri, string filePath, string name = "file", public HttpResponseMessage? UploadFile(string? requestUri, string filePath, string name = "file",
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
@@ -60,7 +60,7 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> UploadFileAsync(string? requestUri, string filePath, string name = "file", public Task<HttpResponseMessage?> UploadFileAsync(string? requestUri, string filePath, string name = "file",
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
@@ -69,12 +69,12 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Send(HttpFileUploadBuilder httpFileUploadBuilder, public HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
new FileUploadManager(this, httpFileUploadBuilder, configure).Start(cancellationToken); new FileUploadManager(this, httpFileUploadBuilder, configure).Start(cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, public Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
new FileUploadManager(this, httpFileUploadBuilder, configure).StartAsync(cancellationToken); new FileUploadManager(this, httpFileUploadBuilder, configure).StartAsync(cancellationToken);

View File

@@ -17,22 +17,22 @@ namespace ThingsGateway.HttpRemote;
internal sealed partial class HttpRemoteService internal sealed partial class HttpRemoteService
{ {
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Get(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Get(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Get(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Get(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Get(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => GetAsync(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> GetAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> GetAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
@@ -58,22 +58,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Get<TResult>(requestUri, CancellationToken cancellationToken = default) => Get<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Get<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
GetAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); GetAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, public Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption,
cancellationToken); cancellationToken);
@@ -133,22 +133,22 @@ internal sealed partial class HttpRemoteService
GetAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); GetAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Put(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Put(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Put(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Put(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Put(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => PutAsync(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => PutAsync(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PutAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> PutAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
@@ -174,22 +174,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Put<TResult>(requestUri, CancellationToken cancellationToken = default) => Put<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Put<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
PutAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); PutAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, public Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption,
cancellationToken); cancellationToken);
@@ -249,23 +249,23 @@ internal sealed partial class HttpRemoteService
PutAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); PutAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Post(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Post(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Post(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Post(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Post(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => PostAsync(requestUri, CancellationToken cancellationToken = default) => PostAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PostAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> PostAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
@@ -291,22 +291,23 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Post<TResult>(requestUri, CancellationToken cancellationToken = default) => Post<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Post<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
PostAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); PostAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, public Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption,
cancellationToken); cancellationToken);
@@ -366,23 +367,23 @@ internal sealed partial class HttpRemoteService
PostAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); PostAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Delete(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Delete(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Delete(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Delete(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => DeleteAsync(requestUri, CancellationToken cancellationToken = default) => DeleteAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> DeleteAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
@@ -408,22 +409,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Delete<TResult>(requestUri, CancellationToken cancellationToken = default) => Delete<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
DeleteAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); DeleteAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption,
@@ -487,23 +488,23 @@ internal sealed partial class HttpRemoteService
DeleteAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); DeleteAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Head(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Head(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Head(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Head(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Head(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => HeadAsync(requestUri, CancellationToken cancellationToken = default) => HeadAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> HeadAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
@@ -529,22 +530,23 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Head<TResult>(requestUri, CancellationToken cancellationToken = default) => Head<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Head<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
HeadAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HeadAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, public Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption,
cancellationToken); cancellationToken);
@@ -604,23 +606,23 @@ internal sealed partial class HttpRemoteService
HeadAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); HeadAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Options(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Options(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Options(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Options(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Options(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => OptionsAsync(requestUri, CancellationToken cancellationToken = default) => OptionsAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> OptionsAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
@@ -646,22 +648,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Options<TResult>(requestUri, CancellationToken cancellationToken = default) => Options<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Options<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
OptionsAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); OptionsAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption,
@@ -725,23 +727,23 @@ internal sealed partial class HttpRemoteService
OptionsAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); OptionsAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Trace(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Trace(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Trace(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Trace(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => TraceAsync(requestUri, CancellationToken cancellationToken = default) => TraceAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> TraceAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
@@ -767,22 +769,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Trace<TResult>(requestUri, CancellationToken cancellationToken = default) => Trace<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
TraceAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); TraceAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption,
@@ -846,23 +848,23 @@ internal sealed partial class HttpRemoteService
TraceAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken); TraceAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpResponseMessage? Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Patch(requestUri, HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) => Patch(requestUri, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Patch(string? requestUri, HttpCompletionOption completionOption, public HttpResponseMessage? Patch(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, public Task<HttpResponseMessage?> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => PatchAsync(requestUri, CancellationToken cancellationToken = default) => PatchAsync(requestUri,
HttpCompletionOption.ResponseContentRead, HttpCompletionOption.ResponseContentRead,
configure, cancellationToken); configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> PatchAsync(string? requestUri, HttpCompletionOption completionOption, public Task<HttpResponseMessage?> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
@@ -888,22 +890,22 @@ internal sealed partial class HttpRemoteService
cancellationToken); cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, public HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default) => Patch<TResult>(requestUri, CancellationToken cancellationToken = default) => Patch<TResult>(requestUri,
HttpCompletionOption.ResponseContentRead, configure, cancellationToken); HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, HttpCompletionOption completionOption, public HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>( Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken); HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
PatchAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken); PatchAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri, public Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
HttpCompletionOption completionOption, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption,

View File

@@ -90,16 +90,16 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, public HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
Send(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken); Send(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption, public HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, _) = SendCoreAsync(httpRequestBuilder, completionOption, default, var (httpResponseMessage, _) = SendCoreAsync(httpRequestBuilder, completionOption, null,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult(); httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
@@ -107,18 +107,18 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder, public Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
SendAsync(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken); SendAsync(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public async Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder, public async Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
HttpCompletionOption completionOption, CancellationToken cancellationToken = default) HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, _) = await SendCoreAsync(httpRequestBuilder, completionOption, var (httpResponseMessage, _) = await SendCoreAsync(httpRequestBuilder, completionOption,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false); httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
return httpResponseMessage; return httpResponseMessage;
} }
@@ -133,7 +133,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default, var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult(); httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
@@ -156,7 +156,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
cancellationToken); cancellationToken);
// 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例 // 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例
return (TResult)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration); return (TResult?)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -199,7 +199,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption, var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false); httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
// 获取结果类型 // 获取结果类型
var resultType = typeof(TResult); var resultType = typeof(TResult);
@@ -220,7 +220,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
// 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例 // 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例
return (TResult)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration); return (TResult?)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -263,7 +263,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default, var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult(); httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
@@ -298,7 +298,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption, var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false); httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
// 检查类型是否是 HttpRemoteResult<TResult> 类型 // 检查类型是否是 HttpRemoteResult<TResult> 类型
if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(resultType)) if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(resultType))
@@ -320,19 +320,25 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
} }
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder, public HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
Send<TResult>(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken); Send<TResult>(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder, public HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
HttpCompletionOption completionOption, CancellationToken cancellationToken = default) HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default, var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult(); httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
// 空检查
if (httpResponseMessage is null)
{
return null;
}
// 将 HttpResponseMessage 转换为 TResult 实例 // 将 HttpResponseMessage 转换为 TResult 实例
var result = _httpContentConverterFactory.Read<TResult>(httpResponseMessage, var result = _httpContentConverterFactory.Read<TResult>(httpResponseMessage,
httpRequestBuilder.HttpContentConverterProviders?.SelectMany(u => u.Invoke()).ToArray(), cancellationToken); httpRequestBuilder.HttpContentConverterProviders?.SelectMany(u => u.Invoke()).ToArray(), cancellationToken);
@@ -348,18 +354,24 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder, public Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default) => SendAsync<TResult>(httpRequestBuilder, CancellationToken cancellationToken = default) => SendAsync<TResult>(httpRequestBuilder,
HttpCompletionOption.ResponseContentRead, cancellationToken); HttpCompletionOption.ResponseContentRead, cancellationToken);
/// <inheritdoc /> /// <inheritdoc />
public async Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder, public async Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
HttpCompletionOption completionOption, CancellationToken cancellationToken = default) HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
{ {
// 发送 HTTP 远程请求 // 发送 HTTP 远程请求
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption, var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
(httpClient, httpRequestMessage, option, token) => (httpClient, httpRequestMessage, option, token) =>
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false); httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
// 空检查
if (httpResponseMessage is null)
{
return null;
}
// 将 HttpResponseMessage 转换为 TResult 实例 // 将 HttpResponseMessage 转换为 TResult 实例
var result = await _httpContentConverterFactory.ReadAsync<TResult>(httpResponseMessage, var result = await _httpContentConverterFactory.ReadAsync<TResult>(httpResponseMessage,
@@ -392,7 +404,8 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="Tuple{T1, T2}" /> /// <see cref="Tuple{T1, T2}" />
/// </returns> /// </returns>
internal async Task<(HttpResponseMessage ResponseMessage, long RequestDuration)> SendCoreAsync( /// <exception cref="InvalidOperationException"></exception>
internal async Task<(HttpResponseMessage? ResponseMessage, long RequestDuration)> SendCoreAsync(
HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption, HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
Func<HttpClient, HttpRequestMessage, HttpCompletionOption, CancellationToken, Task<HttpResponseMessage>>? Func<HttpClient, HttpRequestMessage, HttpCompletionOption, CancellationToken, Task<HttpResponseMessage>>?
sendAsyncMethod, sendAsyncMethod,
@@ -445,6 +458,20 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
// 设置单次请求超时时间控制 // 设置单次请求超时时间控制
if (httpRequestBuilder.Timeout is not null && httpRequestBuilder.Timeout.Value != TimeSpan.Zero) if (httpRequestBuilder.Timeout is not null && httpRequestBuilder.Timeout.Value != TimeSpan.Zero)
{ {
// 确保 HttpRequestBuilder 的 Timeout 属性值小于 HttpClient 的 Timeout 属性值(默认 100秒
if (httpRequestBuilder.Timeout.Value > httpClient.Timeout)
{
throw new InvalidOperationException(
"HttpRequestBuilder's Timeout cannot be greater than HttpClient's Timeout, which defaults to 100 seconds.");
}
// 调用超时发生时要执行的操作
if (httpRequestBuilder.TimeoutAction is not null)
{
timeoutCancellationTokenSource.Token.Register(httpRequestBuilder.TimeoutAction.TryInvoke);
}
// 延迟指定时间后取消任务
timeoutCancellationTokenSource.CancelAfter(httpRequestBuilder.Timeout.Value); timeoutCancellationTokenSource.CancelAfter(httpRequestBuilder.Timeout.Value);
} }
@@ -534,7 +561,13 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
// 处理发送 HTTP 请求发生异常 // 处理发送 HTTP 请求发生异常
HandleRequestFailed(httpRequestBuilder, requestEventHandler, e, httpResponseMessage); HandleRequestFailed(httpRequestBuilder, requestEventHandler, e, httpResponseMessage);
throw; // 检查是否启用异常抑制机制
if (!ShouldSuppressException(httpRequestBuilder.SuppressExceptionTypes, e))
{
throw;
}
return (httpResponseMessage, requestDuration);
} }
finally finally
{ {
@@ -706,7 +739,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
ArgumentNullException.ThrowIfNull(httpClient); ArgumentNullException.ThrowIfNull(httpClient);
// 添加默认的 User-Agent 标头 // 添加默认的 User-Agent 标头
AddDefaultUserAgentHeader(httpClient); AddDefaultUserAgentHeader(httpClient, httpRequestBuilder);
// 存储 HttpClientPooling 实例并返回 // 存储 HttpClientPooling 实例并返回
return httpRequestBuilder.HttpClientPooling = new HttpClientPooling(httpClient, release); return httpRequestBuilder.HttpClientPooling = new HttpClientPooling(httpClient, release);
@@ -719,10 +752,15 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
/// <param name="httpClient"> /// <param name="httpClient">
/// <see cref="HttpClient" /> /// <see cref="HttpClient" />
/// </param> /// </param>
internal static void AddDefaultUserAgentHeader(HttpClient httpClient) /// <param name="httpRequestBuilder">
/// <see cref="HttpRequestBuilder" />
/// </param>
internal static void AddDefaultUserAgentHeader(HttpClient httpClient, HttpRequestBuilder httpRequestBuilder)
{ {
// 空检查 // 空检查
if (httpClient.DefaultRequestHeaders.UserAgent.Count != 0) if (httpClient.DefaultRequestHeaders.UserAgent.Count != 0 ||
httpRequestBuilder.HeadersToRemove?.Contains(HeaderNames.UserAgent) == true ||
httpRequestBuilder.Headers?.ContainsKey(HeaderNames.UserAgent) == true)
{ {
return; return;
} }
@@ -854,10 +892,10 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
int.TryParse(stringStatusCode, out var intStatusCodeResult) && int.TryParse(stringStatusCode, out var intStatusCodeResult) &&
intStatusCodeResult == statusCode: intStatusCodeResult == statusCode:
return true; return true;
// 处理字符串区间类型,如 200-500 // 处理字符串区间类型,如 200-500 或 200~500
case string stringStatusCode when StatusCodeRangeRegex().IsMatch(stringStatusCode): case string stringStatusCode when StatusCodeRangeRegex().IsMatch(stringStatusCode):
// 根据 - 符号切割 // 根据 - 或 ~ 符号切割
var parts = stringStatusCode.Split('-', StringSplitOptions.RemoveEmptyEntries); var parts = stringStatusCode.Split(['-', '~'], StringSplitOptions.RemoveEmptyEntries);
// 比较状态码区间 // 比较状态码区间
if (parts.Length == 2 && int.TryParse(parts[0], out var start) && int.TryParse(parts[1], out var end)) if (parts.Length == 2 && int.TryParse(parts[0], out var start) && int.TryParse(parts[1], out var end))
@@ -888,8 +926,6 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
"=" => statusCode == number, "=" => statusCode == number,
_ => false _ => false
}; };
default:
return false;
} }
return false; return false;
@@ -908,9 +944,8 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
/// <see cref="object" /> /// <see cref="object" />
/// </returns> /// </returns>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
internal static object DynamicCreateHttpRemoteResult(Type httpRemoteResultType, internal static object? DynamicCreateHttpRemoteResult(Type httpRemoteResultType,
HttpResponseMessage httpResponseMessage, HttpResponseMessage? httpResponseMessage, object? result, long requestDuration)
object? result, long requestDuration)
{ {
// 检查类型是否是 HttpRemoteResult<TResult> 类型 // 检查类型是否是 HttpRemoteResult<TResult> 类型
if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(httpRemoteResultType)) if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(httpRemoteResultType))
@@ -920,6 +955,12 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
nameof(httpRemoteResultType)); nameof(httpRemoteResultType));
} }
// 空检查
if (httpResponseMessage is null)
{
return null;
}
// 反射创建 HttpRemoteResult<TResult> 实例 // 反射创建 HttpRemoteResult<TResult> 实例
var httpRemoteResult = Activator.CreateInstance(httpRemoteResultType, httpResponseMessage); var httpRemoteResult = Activator.CreateInstance(httpRemoteResultType, httpResponseMessage);
@@ -946,11 +987,32 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
return httpRemoteResult; return httpRemoteResult;
} }
/// <summary>
/// 检查是否启用异常抑制机制
/// </summary>
/// <param name="suppressExceptionTypes">受抑制的异常类型列表</param>
/// <param name="exception">
/// <see cref="Exception" />
/// </param>
/// <returns>
/// <see cref="bool" />
/// </returns>
internal static bool ShouldSuppressException(HashSet<Type>? suppressExceptionTypes, Exception? exception)
{
// 空检查
if (suppressExceptionTypes is null or { Count: 0 } || exception is null)
{
return false;
}
return suppressExceptionTypes.Any(u => u.IsInstanceOfType(exception));
}
/// <summary> /// <summary>
/// 状态码区间正则表达式 /// 状态码区间正则表达式
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[GeneratedRegex(@"^\d+-\d+$")] [GeneratedRegex(@"^\d+[-~]\d+$")]
private static partial Regex StatusCodeRangeRegex(); private static partial Regex StatusCodeRangeRegex();
/// <summary> /// <summary>

View File

@@ -106,7 +106,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage UploadFile(string? requestUri, string filePath, string name = "file", HttpResponseMessage? UploadFile(string? requestUri, string filePath, string name = "file",
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -127,7 +127,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="Task{TResult}" /> /// <see cref="Task{TResult}" />
/// </returns> /// </returns>
Task<HttpResponseMessage> UploadFileAsync(string? requestUri, string filePath, string name = "file", Task<HttpResponseMessage?> UploadFileAsync(string? requestUri, string filePath, string name = "file",
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -145,7 +145,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Send(HttpFileUploadBuilder httpFileUploadBuilder, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -161,7 +161,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="Task{TResult}" /> /// <see cref="Task{TResult}" />
/// </returns> /// </returns>
Task<HttpResponseMessage> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>

View File

@@ -27,7 +27,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Get(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -44,7 +44,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Get(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Get(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -58,7 +58,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -75,7 +75,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> GetAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> GetAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -156,7 +156,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -174,7 +174,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Get<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -189,7 +189,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -207,7 +207,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -407,7 +407,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Put(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -424,7 +424,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Put(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Put(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -438,7 +438,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -455,7 +455,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PutAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> PutAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -536,7 +536,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -554,7 +554,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Put<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -569,7 +569,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -587,7 +587,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -787,7 +787,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Post(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -804,7 +804,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Post(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Post(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -818,7 +818,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -835,7 +835,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PostAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> PostAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -916,7 +916,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -934,7 +934,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Post<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -949,7 +949,8 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -967,7 +968,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1167,7 +1168,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1184,7 +1185,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Delete(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Delete(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1198,7 +1199,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1215,7 +1216,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> DeleteAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1296,7 +1297,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1314,7 +1315,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1329,7 +1330,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri, Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -1348,7 +1349,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1548,7 +1549,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Head(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1565,7 +1566,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Head(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Head(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1579,7 +1580,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1596,7 +1597,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> HeadAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1677,7 +1678,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1695,7 +1696,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Head<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1710,7 +1711,8 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1728,7 +1730,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1928,7 +1930,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Options(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1945,7 +1947,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Options(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Options(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1959,7 +1961,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -1976,7 +1978,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> OptionsAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2057,7 +2059,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2075,7 +2077,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Options<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2090,7 +2092,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri, Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -2109,7 +2111,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2309,7 +2311,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2326,7 +2328,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Trace(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Trace(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2340,7 +2342,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2357,7 +2359,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> TraceAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2438,7 +2440,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2456,7 +2458,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2471,7 +2473,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri, Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -2490,7 +2492,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2690,7 +2692,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpResponseMessage? Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2707,7 +2709,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Patch(string? requestUri, HttpCompletionOption completionOption, HttpResponseMessage? Patch(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2721,7 +2723,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null, Task<HttpResponseMessage?> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2738,7 +2740,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> PatchAsync(string? requestUri, HttpCompletionOption completionOption, Task<HttpResponseMessage?> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2819,7 +2821,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null, HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2837,7 +2839,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, HttpCompletionOption completionOption, HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -2852,7 +2854,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri, Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
Action<HttpRequestBuilder>? configure = null, Action<HttpRequestBuilder>? configure = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@@ -2871,7 +2873,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri, HttpCompletionOption completionOption, Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
/// <summary> /// <summary>

View File

@@ -33,7 +33,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, CancellationToken cancellationToken = default); HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// 发送 HTTP 远程请求 /// 发送 HTTP 远程请求
@@ -50,7 +50,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption, HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -65,7 +65,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder, Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -83,7 +83,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpResponseMessage" /> /// <see cref="HttpResponseMessage" />
/// </returns> /// </returns>
Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption, Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -433,7 +433,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder, HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -452,7 +452,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder, HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
HttpCompletionOption completionOption, CancellationToken cancellationToken = default); HttpCompletionOption completionOption, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -468,7 +468,7 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder, Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -487,6 +487,6 @@ public partial interface IHttpRemoteService
/// <returns> /// <returns>
/// <see cref="HttpRemoteResult{TResult}" /> /// <see cref="HttpRemoteResult{TResult}" />
/// </returns> /// </returns>
Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder, Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
HttpCompletionOption completionOption, CancellationToken cancellationToken = default); HttpCompletionOption completionOption, CancellationToken cancellationToken = default);
} }

View File

@@ -41,7 +41,7 @@ public static class HttpRemoteUtility
/// 忽略 SSL 证书验证 /// 忽略 SSL 证书验证
/// </summary> /// </summary>
public static Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> IgnoreSslErrors => public static Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> IgnoreSslErrors =>
(message, cert, chain, errors) => true; (_, _, _, _) => true;
/// <summary> /// <summary>
/// 获取使用 IPv4 连接到服务器的回调 /// 获取使用 IPv4 连接到服务器的回调
@@ -118,8 +118,20 @@ public static class HttpRemoteUtility
// - IPv4: AddressFamily.InterNetwork // - IPv4: AddressFamily.InterNetwork
// - IPv6: AddressFamily.InterNetworkV6 // - IPv6: AddressFamily.InterNetworkV6
// - IPv4 或 IPv6: AddressFamily.Unspecified // - IPv4 或 IPv6: AddressFamily.Unspecified
// 注意:当主机没有 IP 地址时,此方法会抛出一个 SocketException 异常
var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, addressFamily, cancellationToken).ConfigureAwait(false); IPAddress[] addresses;
// 当主机是一个 IP 地址,无需进一步解析
if (IPAddress.TryParse(context.DnsEndPoint.Host, out var ipAddress))
{
addresses = [ipAddress];
}
else
{
// 注意:当主机没有 IP 地址时,此方法会抛出一个 SocketException 异常
var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, addressFamily, cancellationToken).ConfigureAwait(false);
addresses = entry.AddressList;
}
// 打开与目标主机/端口的连接 // 打开与目标主机/端口的连接
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
@@ -129,7 +141,7 @@ public static class HttpRemoteUtility
try try
{ {
await socket.ConnectAsync(entry.AddressList, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false);
// 如果你想选择特定的 IP 地址来连接服务器 // 如果你想选择特定的 IP 地址来连接服务器
// await socket.ConnectAsync( // await socket.ConnectAsync(

View File

@@ -34,5 +34,5 @@ public sealed class WebSocketBinaryReceiveResult : WebSocketReceiveResult
/// <summary> /// <summary>
/// 二进制消息 /// 二进制消息
/// </summary> /// </summary>
public byte[] Message { get; internal init; } = default!; public byte[] Message { get; internal init; } = null!;
} }

View File

@@ -34,5 +34,5 @@ public sealed class WebSocketTextReceiveResult : WebSocketReceiveResult
/// <summary> /// <summary>
/// 文本消息 /// 文本消息
/// </summary> /// </summary>
public string Message { get; internal init; } = default!; public string Message { get; internal init; } = null!;
} }

View File

@@ -329,6 +329,7 @@ public class FallbackPolicy<TResult> : PolicyBase<TResult>
{ {
// 获取操作方法执行结果 // 获取操作方法执行结果
context.Result = await operation(cancellationToken).ConfigureAwait(false); context.Result = await operation(cancellationToken).ConfigureAwait(false);
context.Exception = null;
} }
catch (System.Exception exception) catch (System.Exception exception)
{ {

View File

@@ -391,6 +391,7 @@ public class RetryPolicy<TResult> : PolicyBase<TResult>
{ {
// 获取操作方法执行结果 // 获取操作方法执行结果
context.Result = await operation(cancellationToken).ConfigureAwait(false); context.Result = await operation(cancellationToken).ConfigureAwait(false);
context.Exception = null;
} }
catch (System.Exception exception) catch (System.Exception exception)
{ {

View File

@@ -0,0 +1,19 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
namespace ThingsGateway.Shapeless;
/// <summary>
/// 流变对象模型绑定特性
/// </summary>
/// <remarks>示例代码:<c>[Clay] dynamic input</c>。</remarks>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class ClayAttribute : Attribute;

Some files were not shown because too many files have changed in this diff Show More