Bitzsoft.Integrations.IManage
1.0.5
dotnet add package Bitzsoft.Integrations.IManage --version 1.0.5
NuGet\Install-Package Bitzsoft.Integrations.IManage -Version 1.0.5
<PackageReference Include="Bitzsoft.Integrations.IManage" Version="1.0.5" />
<PackageVersion Include="Bitzsoft.Integrations.IManage" Version="1.0.5" />
<PackageReference Include="Bitzsoft.Integrations.IManage" />
paket add Bitzsoft.Integrations.IManage --version 1.0.5
#r "nuget: Bitzsoft.Integrations.IManage, 1.0.5"
#:package Bitzsoft.Integrations.IManage@1.0.5
#addin nuget:?package=Bitzsoft.Integrations.IManage&version=1.0.5
#tool nuget:?package=Bitzsoft.Integrations.IManage&version=1.0.5
Bitzsoft.Integrations.IManage
iManage Work 文档管理集成客户端 — REST API v2(Universal API)封装、OAuth2 认证、多租户 Token 刷新。
路径、动词与请求体对齐 iManage 官方 OpenAPI 规范;每个公开方法的 XML 注释中都标注了对应端点地址与调用示例,IntelliSense 悬停即可查看。
功能特性
- 规范对齐 — 聚合库、工作区、文件夹、Tab、文档、用户、用户组、自定义字段、安全、分享共 10 个模块,81 个端点逐条核对官方规范
- 多租户支持 — 通过
IIManageOptionsProvider按租户动态解析配置,IIManageClientFactory.Create(tenantId)创建租户隔离的客户端 - 自动 Token 管理 —
IManageAuthHandler自动注入 Bearer Token,401 时刷新重试,Token 持久化由宿主实现IIManageTokenStore - 流式分页 —
Stream*Async系列方法基于IAsyncEnumerable,按规范的limit/offset自动翻页,内存占用恒定 - 作用域搜索 —
ScopedSearch*Async对应POST /{documents|workspaces|folders}/search,可指定返回字段以减小响应 - 文档全生命周期 — 上传、下载、版本管理、检出/检入/锁定、复制/移动、引用、关联、安全 ACL、Name-Value Pairs、邮件分享
- Resilience 管道 — 内置标准重试与熔断策略(
AddStandardResilienceHandler) - 第三方请求日志 — 内置 RequestLogging DelegatingHandler,记录所有出站 HTTP 请求与响应,便于问题排查
- 结构化异常 —
IManageException携带IManageErrorCode、HTTP 状态码、API 错误码和请求 URL
安装
dotnet add package Bitzsoft.Integrations.IManage
<PackageReference Include="Bitzsoft.Integrations.IManage" Version="1.0.5" />
配置
{
"IManage": {
"Host": "https://imanage.example.com",
"ClientId": "{{IMANAGE_CLIENT_ID}}",
"ClientSecret": "{{IMANAGE_CLIENT_SECRET}}",
"UserName": "{{IMANAGE_USER}}",
"Password": "{{IMANAGE_PASSWORD}}",
"DefaultCustomerId": "1",
"DefaultLibraryId": "ACTIVE",
"TimeoutMs": 300000,
"TokenEndpoint": "/auth/oauth2/token",
"ApiBasePath": "/work/api/v2",
"TokenCacheMinutes": 30,
"DefaultSecurity": "public",
"DefaultPageSize": 100,
"WorkspaceChildrenLimit": 300
}
}
DefaultLibraryId须为服务端真实库 ID(通常为小写,可先用GetLibrariesAsync()查看)。配置UserName/Password时走 Resource Owner Password Credentials 模式,否则走 Client Credentials。
TimeoutMs是单次操作(含文件上传/下载全程)的总上限,默认 5 分钟以容纳大文件;交互式场景可调低。net8+ 的弹性管道按"单次尝试 2 分钟 / 总计 5 分钟"配置,GET 等安全方法的瞬时故障会自动重试,POST/PATCH/DELETE 永不自动重试。
注册服务
using Bitzsoft.Integrations.IManage;
// 基本注册(单租户)
services.AddIManage(options =>
{
options.Host = "https://imanage.example.com";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.DefaultLibraryId = "ACTIVE";
});
// 必须注册 IIManageTokenStore 实现(如 Redis、Memory 等)
services.AddSingleton<IIManageTokenStore, RedisIManageTokenStore>();
// 多租户场景:不传 configure,注册自定义 IIManageOptionsProvider(先后顺序均可,默认实现以 TryAdd 注册不会覆盖)
services.AddIManage();
services.AddSingleton<IIManageOptionsProvider, TenantIManageOptionsProvider>();
services.AddSingleton<IIManageTokenStore, RedisIManageTokenStore>();
单租户直接 factory.Create():租户键会归一为 IManageOptions.DefaultTenantId("default"),IIManageOptionsProvider / IIManageTokenStore / 刷新锁全链路以该键工作,自定义实现对它返回默认配置即可。未传 configure 又未注册自定义解析器时,默认解析器在首次解析时以明确消息快速失败。
第三方请求日志
内置 Bitzsoft.Integrations.RequestLogging 出站请求记录管道,默认使用 NullRequestLogStore 不持久化。
// ① 默认:启用记录管道但不持久化(日志丢弃)
services.AddIManage(options => { /* ... */ });
// ② 持久化:宿主注册 IRequestLogStore 实现后,所有出站请求自动落库
services.AddRequestLogging<MyRequestLogStore>(opts =>
{
opts.MaxInMemoryBodyBytes = 64 * 1024; // 仅控制内存/加密临时文件切换,不截断正文
opts.SensitiveFields.Add("mySecret");
});
services.AddIManage(options => { /* ... */ });
使用示例
查询工作区与文档
using Bitzsoft.Integrations.IManage;
using var client = _factory.Create();
// 按客户别名列出工作区(GET /workspaces)
var workspaces = await client.SearchWorkspacesAsync(new SearchWorkspaceInput { Custom1 = "10001", Limit = 50 });
// 探测某工作区(含子树)是否有文档(GET /documents?container_id=…&include_subtree=true&limit=1)
var probe = await client.SearchDocumentsAsync(new SearchDocumentsInput
{
ContainerId = "ACTIVE!123456",
IncludeSubtree = true,
Limit = 1
});
var hasDocuments = probe.Data.Count > 0;
// 流式遍历工作区下全部文档(自动翻页)
await foreach (var doc in client.StreamDocumentsAsync(new SearchDocumentsInput { ContainerId = "ACTIVE!123456", Limit = 200 }))
Console.WriteLine($"{doc.Id} {doc.Name}.{doc.Extension}");
// 作用域搜索:全文检索并只取需要的字段(POST /documents/search)
var found = await client.ScopedSearchDocumentsAsync(new ScopedSearchRequest<DocumentSearchFilters>
{
Limit = 50,
ProfileFields = new() { ["document"] = ["name", "author", "edit_date"] },
Filters = new() { ContainerId = "ACTIVE!123456", IncludeSubtree = true, Anywhere = "保密协议" }
});
上传与下载
using var client = _factory.Create();
// 上传(POST /folders/{id}/documents,multipart:profile 在前、file 在后)
await using var fileStream = File.OpenRead("/path/to/contract.pdf");
var document = await client.UploadDocumentAsync("ACTIVE!157954", new UploadDocumentInput
{
Name = "合同草案",
Extension = "pdf",
Size = fileStream.Length,
DocProfile = new DocProfileInput { Author = "JOHN.DOE", Class = "DOC" }
}, fileStream);
// 下载指定版本(版本号编码进文档 ID)
await using var content = await client.DownloadDocumentAsync(document.Id, version: 1);
await using var output = File.Create("downloaded.pdf");
await content.CopyToAsync(output);
检出、检入与安全
using var client = _factory.Create();
// 检出并下载(POST /documents/{id}/checkout,appname 必填)
await using var checkedOut = await client.CheckOutDocumentAsync("ACTIVE!1096273.1", new CheckOutInput { AppName = "MyApp" });
// 带内容检入(PUT /documents/{id}/file);不带内容则仅释放锁
await using var revised = File.OpenRead("/path/to/revised.pdf");
await client.CheckInDocumentAsync("ACTIVE!1096273.1", new CheckInInput { Comments = "完成修订" }, revised);
// 更新 ACL(POST /documents/{id}/security)
await client.SetDocumentSecurityAsync("ACTIVE!1096273.1", new UpdateSecurityInput
{
Include = [new() { Id = "LEGAL-TEAM", AccessLevel = "read_write", Type = "group" }]
});
幂等同步:Ensure 系列(推荐用于定时同步 / 重跑)
iManage 不强制同级容器名称唯一,也未定义自定义字段重复创建的状态码——直接"检查→创建"在重跑或并发时会产生同名副本或重复写入。Ensure 系列方法以"读-比-写"实现幂等:已存在即复用且不修改既有对象,缺失才创建,创建被拒时复查而非重试。
using var client = _factory.Create();
// 自定义字段:先父后子,内容一致时零写入;子表(custom2 / custom30)的查找与更新自动携带规范要求的 ?parent_alias=
await client.EnsureCustomFieldAsync("custom1", "10001", "示例证券");
var matter = await client.EnsureCustomFieldAsync("custom2", "20099", "股权转让项目", parentAlias: "10001");
// 容器:同名文件夹/Tab 已存在则复用,不会再建一个副本
var tab = await client.EnsureTabAsync("ACTIVE!123456", new CreateTabInput { Name = "交易文件" });
var folder = await client.EnsureTabFolderAsync(tab.Value.Id, new CreateFolderInput { Name = "签署版", DefaultSecurity = "inherit" });
if (folder.Outcome == ContainerEnsureResult<FolderResponse>.OutcomeKind.Found)
Console.WriteLine($"复用既有文件夹 {folder.Value.Id},其中文档不受影响");
同名多个时 Ensure 抛出 InvalidOperationException 而不是任选其一;需要修改既有容器属性请显式调用对应的 Update*Async。
已存在容器的 custom 补充:新建(Created)时 profile 随创建请求完整提交;对认领的既有容器(Found),iManage 规范不返回文件夹的当前 profile——"哪些 custom 缺失"无法判定,因此 Ensure 不做静默回填。推荐策略:
var result = await client.EnsureSubfolderAsync(parentId, new CreateFolderInput { Name = "尽调材料", Profile = expected });
if (result.Outcome == ContainerEnsureResult<FolderResponse>.OutcomeKind.Found)
{
// 用 NVP 标签区分来源:系统创建的才补 profile,历史人工创建的不动
var nvp = await client.GetFolderNameValuePairsAsync(result.Value.Id);
if (nvp.TryGetValue("FolderTemplateId", out var tag) && tag.ValueKind == JsonValueKind.String)
await client.UpdateFolderAsync(result.Value.Id, new UpdateFolderInput { Profile = expected });
}
Tab 的当前 profile 可经 TabResponse.Profile 读回,可先比对再补缺失的键。
多租户使用
// 通过工厂创建指定租户的客户端
using var client = _factory.Create(tenantId: "tenant-001");
var workspace = await client.GetWorkspaceAsync("ACTIVE!123456");
能力边界与行为约定
以下能力在 iManage Universal API v2 规范中不存在,本包不提供:审计事件流、法律保留、扩展元数据(Insight+)、文件夹安全复制、分享链接撤销。iManage 的"分享"是由服务端发送带附件/链接的邮件(ShareDocumentByEmailAsync)。
- 取消语义:
Stream*Async/ 自动翻页的Get*Async在取消令牌触发时抛出OperationCanceledException,绝不把半截列表当作完整结果返回。 - 总条数:
IManageResponse<T>.TotalCount对应规范字段total_count,只有在搜索输入设置IncludeTotal = true(发出total=true)时服务端才返回,否则为 null。 - 子表自定义字段:对
custom2/custom30的单条查询与更新必须提供parentAlias(规范要求parent_alias),缺失时在发起请求前抛ArgumentException。 - 文件名:上传时中文等非 ASCII 文件名以 RFC 5987 的
filename*传输,filename仅放 ASCII 占位;库中文档名以 profile 的name为准。 - 数值 / 布尔槽位:
custom17–custom20为double?、custom25–custom28为bool?(规范类型);反序列化同时接受字符串形式的数字与"true"/"false"/0/1形式的布尔。 - 文档历史:
DocumentHistoryResponse字段名以规范为准(User/UserFullName/Activity/ActivityCode/ActivityDate/Comments)。 - 字段级错误:服务端
error.messages[]中的field与原因会拼入IManageException.Message,便于直接定位不合法的 profile 字段。
相关包
| 包名 | 说明 |
|---|---|
| Bitzsoft.Integrations.Ldap | LDAP 目录服务集成客户端 |
| Bitzsoft.Integrations.Beisen | 北森 HR 系统集成客户端 |
| Bitzsoft.Integrations.EkuaiBao | 易快报费用管理集成客户端 |
| Bitzsoft.Integrations.TencentCcc | 腾讯云呼叫中心集成客户端 |
| Bitzsoft.Integrations.FileStorage | 文件存储抽象层 |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Bitzsoft.Integrations.Core (>= 1.0.4)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.4)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Http.Resilience (>= 10.7.0)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.10)
-
net5.0
- Bitzsoft.Integrations.Core (>= 1.0.4)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.4)
- Microsoft.Extensions.Http (>= 5.0.0)
- Microsoft.Extensions.Options (>= 5.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 5.0.0)
-
net8.0
- Bitzsoft.Integrations.Core (>= 1.0.4)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.4)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Http.Resilience (>= 10.7.0)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.10)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Bitzsoft.Integrations.IManage:
| Package | Downloads |
|---|---|
|
Bitzsoft.Integrations.All
Bitzsoft 第三方集成聚合包 — net5.0 包含传统连接器,net8.0+ 额外包含受上游 TFM 限制的 AI 模块 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.5 | 191 | 10/20/2026 |
| 1.0.4 | 161 | 9/23/2026 |
| 1.0.3 | 107 | 9/22/2026 |
| 1.0.2 | 172 | 8/29/2026 |
| 1.0.1 | 188 | 8/3/2026 |
| 1.0.0 | 177 | 8/2/2026 |
| 1.0.0-alpha.10 | 109 | 7/26/2026 |
| 1.0.0-alpha.9 | 94 | 7/12/2026 |
| 1.0.0-alpha.8 | 300 | 7/1/2026 |
| 1.0.0-alpha.7 | 115 | 6/16/2026 |
| 1.0.0-alpha.6 | 106 | 6/16/2026 |
| 1.0.0-alpha.5 | 97 | 6/14/2026 |
| 1.0.0-alpha.3 | 94 | 6/7/2026 |
| 1.0.0-alpha.2 | 90 | 5/29/2026 |
| 1.0.0-alpha.1 | 86 | 5/28/2026 |
1.0.5 — 按官方 OpenAPI 规范(iManage Work Universal API v2,由服务器 /api-docs 提供)全量对齐,81 个动词+路径组合与规范逐条核对无偏差。
修复:分页统一为 limit/offset(原 start_index/max_results 被服务端忽略导致流式查询静默截断);安全 ACL 更新由 PATCH 改为 POST;检出改 POST /checkout(原 /check-out 不存在);提升版本改 /versions/promote;文档关联改 related-documents;文档引用改 /folders/{id}/documents;组成员增删改 PUT /members;上传/新建版本的 multipart 顺序与 doc_profile 结构修正;复制请求体字段修正为 folder_id。
新增:三个资源的 scoped search(POST /{documents|workspaces|folders}/search)、已检出文档列表、文档内容替换(PUT /file)、锁定/解锁、工作区模板(GET /templates)、工作区/Tab 根级建文件夹、文件夹移动、受托者查询(GET /trustees)、文档 NVP 读取、Tab 安全、custom2 子别名查询(GET /customs/custom2/{child}?parent_alias={parent})。
完善:工作区创建/更新支持 custom1–custom30 与改名(对齐规范全字段);文件夹/Tab 的 profile 支持任意附加键并提供 ContainerProfileInput.FromDictionary;UserResponse 补 user_num。
幂等:新增 EnsureCustomFieldAsync(自定义字段读-比-写 upsert,先父后子、零写入、竞态复查)、EnsureSubfolder/EnsureWorkspaceFolder/EnsureTab/EnsureTabFolder(容器 find-or-create,同名复用不修改、同名多个报错)与对应只读 TryFind 系列(预览哪些会复用/新建/冲突),供同步重跑与历史数据认领场景使用;修正 customs 的 parent 为规范要求的对象形状。
命名:DI 注册方法更名为 AddIManage(对齐全仓库 Add{产品}{类别} 惯例),旧名 AddTNTIManage 保留为 [Obsolete] 别名。
发版终审修复:子表 custom2/custom30 的单条 GET/PATCH 按规范携带 parent_alias(原 /custom1/{p}/custom2/{c} 为未文档化的遗留路由且不适用于 custom30);分页流在取消令牌触发时抛出而非静默返回截断数据;factory.Create() 的 null 租户归一为 IManageOptions.DefaultTenantId,修复单租户经 DI 管道必失败的问题;TimeoutMs 在 DI 路径下真正生效(默认提高为 5 分钟),弹性管道尝试/总超时放宽为 2/5 分钟以容纳大文件;非 ASCII 文件名改走 RFC 5987 filename*,含引号文件名不再崩溃;响应顶层带 warnings/default_security/total_count 等兄弟字段时仍正确解包 data;DocumentHistoryResponse 字段名修正为规范的 user/user_fullname/activity/activity_date/comments/activity_code;分页元数据改读规范字段 total_count(TotalCount,可经 IncludeTotal 请求);GroupResponse 显示名改读 full_name;custom17–20 改为 double?、custom25–28 改为 bool?;反序列化允许字符串形式数字与宽松布尔;error.messages[] 字段级错误进入异常消息;用户名/别名等路径段做 URL 转义;上传缺 Name、组成员为 null/空白等在发起请求前拒绝;DI 的默认 IIManageOptionsProvider 改为 TryAdd 且缺配置时快速失败。
移除(破坏性变更):审计、法律保留、扩展元数据(Insight+)、文件夹安全复制、分享链接撤销——规范中不存在对应端点。