XYS.MinIO.Client 3.0.0

dotnet add package XYS.MinIO.Client --version 3.0.0
                    
NuGet\Install-Package XYS.MinIO.Client -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XYS.MinIO.Client" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XYS.MinIO.Client" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="XYS.MinIO.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XYS.MinIO.Client --version 3.0.0
                    
#r "nuget: XYS.MinIO.Client, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package XYS.MinIO.Client@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XYS.MinIO.Client&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=XYS.MinIO.Client&version=3.0.0
                    
Install as a Cake Tool

XYS.MinIO.Client

MinIO 对象存储客户端。3.0.0 起为可注入的实例客户端,2.x 的静态 MinioHelper 已移除。 底层依赖 Minio .NET SDK 6.0.5

安装与配置

xys.config.json

{
  "minio": {
    "endpoint": "10.0.0.5",
    "port": 9000,
    "accessKey": "your-access-key",
    "secretKey": "<Crypto.Protect 后的密文>",
    "useSsl": false,
    "region": "",
    "timeoutSeconds": 60
  }
}

secretKeyXYS.Utils.SysCrypto.Unprotect 解密,与 connectionStrings 的约定一致。

timeoutSeconds 目前不生效(原因见"已知限制"第 1 条),配置它不会报错,也不会产生效果。

关于 ENC(...) 的强度Crypto.Protect 在未设置 XYS_CRYPTO_KEY 环境变量时使用内置默认密钥, 只是混淆级保护——持有程序集的人可以反解。要获得真正的加密,请在宿主环境设置 XYS_CRYPTO_KEY。 另外 Crypto.Unprotect 对非 ENC(...) 的值原样返回,所以直接写明文 secretKey 也能跑通, 且不会有任何提示——请自行确认配置文件里写的是密文。

用法

依赖注入:

services.AddMinioStorage();                                // 读 xys.config.json
services.AddMinioStorage(o => { o.Endpoint = "10.0.0.5"; o.Port = 9000;
                                o.AccessKey = "ak"; o.SecretKey = "sk"; });

不用容器:

using (var storage = new MinioStorage(MinioOptions.FromConfig()))
{
    await storage.MakeBucketAsync("reports");

    using (var fs = File.OpenRead(@"D:\a.pdf"))
    {
        await storage.PutObjectAsync("reports", "2026/a.pdf", fs, fs.Length, "application/pdf");
    }

    var meta = await storage.GetObjectToFileAsync("reports", "2026/a.pdf", @"D:\b.pdf");
    Console.WriteLine(meta.Size);

    var url = await storage.PresignedGetObjectAsync("reports", "2026/a.pdf", 600);
}

注册为单例。 IMinioClient 内部持有 HttpClient,按调用创建实例会耗尽套接字端口。 AddMinioStorage 已按单例注册。

异常

本库除参数校验(ArgumentNullException / ArgumentException / ObjectDisposedException)外 不包裹异常。Minio SDK 的异常原样上抛,可按类型分支:

try { await storage.StatObjectAsync("reports", "missing.pdf"); }
catch (ObjectNotFoundException) { /* 对象不存在 */ }
catch (BucketNotFoundException) { /* 桶不存在 */ }

catch (MinioException) 不是一张安全网。 凭据 / 签名错误抛的 AuthorizationException 直接派生自 Exception,并不是 MinioException 的子类;一部分操作在凭据错误时抛的 甚至是 NullReferenceException(见"已知限制"第 2 条)。所以只要凭据错误也需要走同一个 分支,兜底就必须写 catch (Exception)

try { await storage.PutObjectAsync(...); }
catch (BucketNotFoundException) { /* 业务可预期的错误 */ }
catch (Exception ex)            { /* 凭据错误会落到这里,而不是 MinioException 分支 */ }

已知限制(来自 Minio SDK 6.0.5,非本库)

以下均为实测确认的上游行为,测试环境:Minio .NET SDK 6.0.5 + MinIO 服务端 RELEASE.2025-09-07T16-13-09Z。本库不包裹它们,如实转达。

1. timeoutSeconds 完全不生效

MinioOptions.Timeout 和配置里的 timeoutSeconds不起作用,两处原因叠加:

  • MinioClientExtensions.Build() 无条件执行 HttpClient.Timeout = TimeSpan.FromMinutes(30)
  • RequestExtensions.ExecuteTaskAsync 里用来做超时的两个 CancellationTokenSourceusing 声明、作用域只到 if 块结束,在真正 await 发出请求之前就已被 Dispose, 于是那个令牌永远不会触发。

这两处在 6.0.4 与 6.0.5 之间逐字未变。要控制超时只能自己传 CancellationToken

2. 凭据错误时的异常类型不统一,catch (MinioException) 兜不住

凭据 / 签名错误会可靠地抛异常(不会静默成功),但类型分两种:

操作 凭据错误时抛出
ListBucketsAsyncListObjectsAsyncPutObjectAsyncRemoveObjectAsync AuthorizationException
BucketExistsAsyncMakeBucketAsyncStatObjectAsyncGetObjectAsyncGetObjectToFileAsync NullReferenceException

两种都不是 MinioException 的子类——AuthorizationException 直接派生自 ExceptionNullReferenceExceptionSystemException

根因(ILSpy 反编译 Minio.dll 6.0.5 核实):错误响应带 S3 的 XML 响应体时走 MinioClient.ParseErrorFromContent,能正确造出 AuthorizationException;而 HEAD 类请求 的错误响应没有响应体,走 ParseErrorNoContentParseWellKnownErrorNoContent, 那里第一行判断写的是:

if (response.StatusCode.ToString().Contains("NotFound", StringComparison.Ordinal)
    || response.Exception.ToString().Contains("NotFound", StringComparison.Ordinal))

纯状态码错误(没有传输层异常)时 response.Exceptionnull,第二个操作数直接空引用。 6.0.4 该处写的是 HttpStatusCode.NotFound == response.StatusCode,没有这次解引用—— 这是 6.0.5 引入的回归。副作用是同一方法里那条本该产出 AccessDeniedExceptionForbidden 分支在这条路径上根本走不到。

第 2 类里的 MakeBucketAsync 是本库自己的连带:它为了实现幂等会先调 BucketExistsAsync

3. GetPolicyAsync 在桶没有策略时抛 UnexpectedMinioException

桶从未设过策略、或策略已被 RemovePolicyAsync 删除时,GetPolicyAsync 不返回 null, 而是抛出笼统的 UnexpectedMinioException——SDK 没有为这种情况准备专用异常类型。 要把"没有策略"和真正的故障区分开,只能看错误码:

try { policy = await storage.GetPolicyAsync("reports"); }
catch (MinioException ex) when (ex.Response?.Code == "NoSuchBucketPolicy") { policy = null; }

4. MinioException.Response 在无响应体的错误上为 null

上面第 3 条能读到 ex.Response.Code,是因为那条错误带 XML 响应体。 走 ParseWellKnownErrorNoContent 的错误(例如 RemoveBucketAsync 删不存在的桶时的 BucketNotFoundExceptionex.Responsenull——6.0.5 把 6.0.4 的 ex.Response = errorResponse 改成了 response.Exception = ex,构造好的 ErrorResponse 被丢弃了。所以读 ex.Response 一律要判空。

5. PutObjectFromFileAsync 上传后不立即释放本地文件句柄

要等垃圾回收才释放,6.0.5 未修复。若需要在上传后立刻删除或移动该文件, 请改用 PutObjectAsync 自行控制流的生命周期。

与 AWS S3 语义一致、无需特殊处理的部分

这些实测过,行为正常,列出来是为了免得被上面几条吓到:

场景 行为
RemoveBucketAsync 删不存在的桶 BucketNotFoundException
ListObjectsAsync 列不存在的桶 BucketNotFoundException
StatObjectAsync / GetObjectToFileAsync 读不存在的对象 ObjectNotFoundException
CopyObjectAsync 源对象不存在 ObjectNotFoundException
RemoveObjectAsync 删不存在的对象 不抛(S3 的 DELETE Object 本就幂等)
RemovePolicyAsync 删本来就没有的策略 不抛
RemoveIncompleteUploadAsync 目标不存在 不抛
已取消的 CancellationToken OperationCanceledException

目标框架

net462netcoreapp3.1net8.0

从 2.x 迁移

2.x 的所有方法是 MinioHelper 上的静态方法,首参为 MinioClient; 3.0 全部改为 IMinioStorage 上的实例方法。

2.x 3.0
MinioHelper.GetClient(ip, port, ak, sk) new MinioStorage(options) / AddMinioStorage()
MakeBucket MakeBucketAsync幂等:桶已存在返回 false 而非抛异常)
ListBuckets ListBucketsAsync
BucketExists BucketExistsAsync
RemoveBucket RemoveBucketAsync
ListObjects ListObjectsAsync
ListIncompleteUploads ListIncompleteUploadsAsync
GetPolicy / SetPolicy GetPolicyAsync / SetPolicyAsync(策略 JSON 改由调用方传入)、新增 RemovePolicyAsync
FGetObject(..., fileName, sse) GetObjectToFileAsync
FGetObject(..., callback, sse) GetObjectAsync(callback)
GetObjectAsync(..., fileName) GetObjectToFileAsync(与 FGetObject 合并)
GetObjectAsync(..., callback) GetObjectAsync(callback)(与 FGetObject 合并)
GetObjectAsync(..., offset, length, callback) GetObjectAsync(offset, length, callback)
FPutObject(..., fileName, contentType) PutObjectFromFileAsync
PutObjectAsync(..., filePath, ...) PutObjectFromFileAsync(与 FPutObject 合并)
PutObjectAsync(..., Stream, size, ...) PutObjectAsync
StatObject StatObjectAsync
CopyObject CopyObjectAsync
RemoveObject / RemoveObjects RemoveObjectAsync / RemoveObjectsAsync
RemoveIncompleteUpload RemoveIncompleteUploadAsync
PresignedGetObject / PresignedPutObject PresignedGetObjectAsync / PresignedPutObjectAsync
PresignedPostPolicy 已移除
桶通知相关三个 private 方法 已移除

静默的行为变化(对照表之外,容易漏)

方法改了名容易发现,下面这些不会报错但行为变了:

2.x 3.0
建桶区域默认值 loc = "us-east-1" region = null,不调用 WithLocation
预签名有效期默认值 expiresInt = 1000 expirySeconds = 3600
预签名有效期上限 无校验 超过 604800 秒(7 天)抛 ArgumentException
上传大小 原样透传给 SDK size < 0ArgumentException
桶名 / 对象键 无校验 空或纯空白抛 ArgumentException
下载到本地文件 目标文件已存在时先 File.Delete 直接覆盖;下载失败时原有目标文件不被破坏

表里"上传大小"那行有实际影响:Minio SDK 用 ObjectSize = -1 表示"长度未知的流式上传", 本库的校验封死了这条路。若你需要上传不可 seek 的流(网络流、压缩流)且不想先缓冲到内存, 当前版本做不到——请提 issue。

其他行为变化:

  • 返回值不再是 Task<bool> / Tuple<bool, T>。2.x 里那个 flag 恒为 true (所有 false 分支都是 throw)。3.0 直接返回数据。
  • 三个 Get 重载返回 ObjectMetadata——SDK 本就返回它,2.x 丢掉了。
  • 全部方法(两个预签名方法除外)接收 CancellationToken
  • 不再向控制台输出任何内容;需要日志时注入 ILogger<MinioStorage>。 (2.x 依赖的 SDK 6.0.4 自己会在 ExecuteTaskAsyncConsole.WriteLine 异常消息, 6.0.5 已删除该行,所以现在整条链路都不会往控制台写东西。)
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp3.1 is compatible. 
.NET Framework net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 90 9/8/2026
2.1.0.7 274 11/24/2023
2.1.0.6 197 11/24/2023
2.1.0.5 333 2/22/2023