MINIO是提供.NET SDK的,但是这么老的版本没找到,于是使用http的方式直接调用,方便简单。
我这里需求不复杂,只需要上传下载删除即可,如果后续有需求再补充方法。

核心代码MinioHttpOperatorDemo如下:
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Specialized; 
using System.Security.Cryptography; 
using System.Globalization; 
using System.Collections.Generic; 
using System.Linq; 
using System.Xml.Linq;
namespace MinioHttpOperatorDemo
{
    
    
    
    
    
    public class MinioHttpOperator
    {
        private readonly string _minioEndpoint; 
        private readonly string _accessKey;     
        private readonly string _secretKey;     
        private readonly string _region;        
        private readonly string _service;       
        
        
        
        
        
        
        
        
        public MinioHttpOperator(string minioEndpoint, string accessKey, string secretKey, string region = "cn-north-1", string service = "s3")
        {
            
            _minioEndpoint = minioEndpoint.TrimEnd('/');
            _accessKey = accessKey;
            _secretKey = secretKey;
            _region = region;
            _service = service;
            if (string.IsNullOrEmpty(_accessKey) || string.IsNullOrEmpty(_secretKey))
            {
                
                throw new ArgumentNullException("AccessKey 和 SecretKey 不能为空,因为需要进行认证。");
            }
        }
        
        
        
        
        
        
        
        
        public bool UploadFile(string bucketName, string objectName, string filePath, string contentType = "application/octet-stream")
        {
            try
            {
                if (!File.Exists(filePath))
                {
                    Console.WriteLine($"错误:文件未找到,路径:{filePath}");
                    return false;
                }
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "PUT";
                request.ContentType = contentType;
                
                string contentHash;
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    using (SHA256 sha256 = new SHA256Managed())
                    {
                        byte[] hashBytes = sha256.ComputeHash(fileStream);
                        contentHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
                    }
                    fileStream.Position = 0; 
                    request.ContentLength = fileStream.Length; 
                    
                    SignRequest(request, bucketName, objectName, contentHash);
                    
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        byte[] buffer = new byte[4096]; 
                        int bytesRead;
                        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            requestStream.Write(buffer, 0, bytesRead);
                        }
                    }
                }
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        Console.WriteLine($"成功上传文件 {objectName} 到桶 {bucketName}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"上传文件 {objectName} 失败。状态码:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "上传");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"上传时发生未知错误:{ex.Message}");
                return false;
            }
        }
        
        
        
        
        
        
        
        public bool DownloadFile(string bucketName, string objectName, string savePath)
        {
            try
            {
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                
                string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; 
                SignRequest(request, bucketName, objectName, contentHash);
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        using (Stream responseStream = response.GetResponseStream())
                        using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
                        {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                fileStream.Write(buffer, 0, bytesRead);
                            }
                        }
                        Console.WriteLine($"成功下载文件 {objectName} 到 {savePath}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"下载文件 {objectName} 失败。状态码:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "下载");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"下载时发生未知错误:{ex.Message}");
                return false;
            }
        }
        
        
        
        
        
        
        public bool DeleteFile(string bucketName, string objectName)
        {
            try
            {
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "DELETE";
                
                string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; 
                SignRequest(request, bucketName, objectName, contentHash);
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    
                    if (response.StatusCode == HttpStatusCode.NoContent)
                    {
                        Console.WriteLine($"成功删除文件 {objectName} 从桶 {bucketName}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"删除文件 {objectName} 失败。状态码:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "删除");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"删除时发生未知错误:{ex.Message}");
                return false;
            }
        } 
        
        
        
        
        
        private void HandleWebException(WebException webEx, string operation)
        {
            if (webEx.Response != null)
            {
                using (StreamReader reader = new StreamReader(webEx.Response.GetResponseStream()))
                {
                    string responseText = reader.ReadToEnd();
                    Console.WriteLine($"{operation}时发生 Web 异常:{webEx.Message}。状态码:{(int)((HttpWebResponse)webEx.Response).StatusCode}。响应内容:{responseText}");
                }
            }
            else
            {
                Console.WriteLine($"{operation}时发生 Web 异常:{webEx.Message}");
            }
        }
        
        
        
        
        
        
        
        
        private void SignRequest(HttpWebRequest request, string bucketName, string objectName, string contentHash)
        {
            
            
            string httpRequestMethod = request.Method;
            
            
            
            
            string cleanedObjectName = objectName.StartsWith("/") ? objectName.Substring(1) : objectName;
            string encodedObjectName = Uri.EscapeDataString(cleanedObjectName).Replace("%2F", "/");
            string canonicalUri = $"/{bucketName}/{encodedObjectName}";
            
            string canonicalQueryString = "";
            
            
            
            
            var headersToSign = new SortedList<string, string>();
            
            string hostHeaderValue = request.RequestUri.Host;
            if (!request.RequestUri.IsDefaultPort)
            {
                hostHeaderValue += ":" + request.RequestUri.Port;
            }
            headersToSign.Add("host", hostHeaderValue);
            
            headersToSign.Add("x-amz-content-sha256", contentHash);
            
            DateTime requestDateTime = DateTime.UtcNow;
            string amzDate = requestDateTime.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture);
            headersToSign.Add("x-amz-date", amzDate);
            
            if (request.Method == "PUT" || request.Method == "POST")
            {
                string actualContentType = request.ContentType;
                if (string.IsNullOrEmpty(actualContentType))
                {
                    actualContentType = "application/octet-stream"; 
                }
                headersToSign.Add("content-type", actualContentType);
            }
            
            StringBuilder canonicalHeadersBuilder = new StringBuilder();
            foreach (var header in headersToSign)
            {
                canonicalHeadersBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}:{1}\n", header.Key, header.Value.Trim()); 
            }
            string canonicalHeaders = canonicalHeadersBuilder.ToString();
            
            
            string signedHeaders = string.Join(";", headersToSign.Keys.ToArray());
            
            
            string canonicalRequest = string.Format(CultureInfo.InvariantCulture,
                "{0}\n{1}\n{2}\n{3}\n{4}\n{5}",
                httpRequestMethod,
                canonicalUri,
                canonicalQueryString,
                canonicalHeaders, 
                signedHeaders,
                contentHash);
            
            
            string algorithm = "AWS4-HMAC-SHA256";
            
            string dateStamp = requestDateTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
            string credentialScope = string.Format(CultureInfo.InvariantCulture,
                "{0}/{1}/{2}/aws4_request",
                dateStamp,
                _region,
                _service);
            
            string hashedCanonicalRequest = ToHex(Hash(Encoding.UTF8.GetBytes(canonicalRequest)));
            
            string stringToSign = string.Format(CultureInfo.InvariantCulture,
                "{0}\n{1}\n{2}\n{3}",
                algorithm,
                amzDate,
                credentialScope,
                hashedCanonicalRequest);
            
            
            byte[] kSecret = Encoding.UTF8.GetBytes("AWS4" + _secretKey);
            byte[] kDate = HmacSha256(kSecret, dateStamp);
            byte[] kRegion = HmacSha256(kDate, _region);
            byte[] kService = HmacSha256(kRegion, _service);
            byte[] kSigning = HmacSha256(kService, "aws4_request");
            
            byte[] signatureBytes = HmacSha256(kSigning, stringToSign);
            string signature = ToHex(signatureBytes);
            
            string authorizationHeader = string.Format(CultureInfo.InvariantCulture,
                "{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}",
                algorithm,
                _accessKey,
                credentialScope,
                signedHeaders,
                signature);
            request.Headers["Authorization"] = authorizationHeader;
            
            request.Headers["x-amz-date"] = amzDate;
            
            request.Headers["x-amz-content-sha256"] = contentHash;
        }
        
        
        
        private static byte[] Hash(byte[] bytes)
        {
            using (SHA256 sha256 = new SHA256Managed())
            {
                return sha256.ComputeHash(bytes);
            }
        }
        
        
        
        private static byte[] HmacSha256(byte[] key, string data)
        {
            using (HMACSHA256 hmac = new HMACSHA256(key))
            {
                return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            }
        }
        
        
        
        private static string ToHex(byte[] bytes)
        {
            return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
        }
    }
}
测试代码如下:
using System;
using System.IO;
using System.Threading;
namespace MinioHttpOperatorDemo
{
    class Program
    {
        static void Main(string[] args)
        {   
            string minioEndpoint = "http://127.xxx.xxx.xxx:9000";
            
            
            string accessKey = "accessKey ";
            string secretKey = "secretKey ";
            MinioHttpOperator minioOperator = new MinioHttpOperator(minioEndpoint, accessKey, secretKey);
            
            
            
            string bucketName = "bucketName ";
            string testFolder = "test";
            
            string localFilePath1 = Path.Combine(Path.GetTempPath(), "testfile1.txt");
            string localFilePath2 = Path.Combine(Path.GetTempPath(), "testfile2.jpg"); 
            string minioObjectName1 = $"{testFolder}/document1.txt";
            string minioObjectName2 = $"{testFolder}/image.jpg";
            string content1 = "This is the content for document one.";
            byte[] content2 = new byte[1024]; 
            new Random().NextBytes(content2); 
            try
            {
                File.WriteAllText(localFilePath1, content1);
                File.WriteAllBytes(localFilePath2, content2);
                Console.WriteLine($"已在本地创建测试文件:{localFilePath1} 和 {localFilePath2}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"创建测试文件失败:{ex.Message}");
                Console.WriteLine("请检查文件路径和权限。程序将退出。");
                Console.ReadKey();
                return;
            }
            Console.WriteLine("\n--- 开始 MinIO 批量操作 ---");
            
            Console.WriteLine($"\n尝试上传文件:{localFilePath1} 到 {minioEndpoint}/{bucketName}/{minioObjectName1}...");
            if (minioOperator.UploadFile(bucketName, minioObjectName1, localFilePath1, "text/plain"))
            {
                Console.WriteLine("文件上传成功。");
            }
            else
            {
                Console.WriteLine("文件上传失败。");
            }
            Thread.Sleep(1000); 
            
            Console.WriteLine($"\n尝试上传文件:{localFilePath2} 到 {minioEndpoint}/{bucketName}/{minioObjectName2}...");
            if (minioOperator.UploadFile(bucketName, minioObjectName2, localFilePath2, "image/jpeg"))
            {
                Console.WriteLine("文件上传成功。");
            }
            else
            {
                Console.WriteLine("文件上传失败。");
            }
            Thread.Sleep(1000); 
            Console.WriteLine("\n----------------------------------------");
            
            string downloadSavePath1 = Path.Combine(Path.GetTempPath(), "downloaded_document1.txt");
            Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName}/{minioObjectName1} 下载文件到 {downloadSavePath1}...");
            if (minioOperator.DownloadFile(bucketName, minioObjectName1, downloadSavePath1))
            {
                Console.WriteLine("文件下载成功。");
                try
                {
                    Console.WriteLine($"下载文件的内容:{File.ReadAllText(downloadSavePath1)}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"读取下载文件内容失败:{ex.Message}");
                }
            }
            else
            {
                Console.WriteLine("文件下载失败。");
            }
            Thread.Sleep(1000); 
            
            string nonExistentObject = $"{testFolder}/nonexistent.pdf";
            string downloadNonExistentPath = Path.Combine(Path.GetTempPath(), "nonexistent.pdf");
            Console.WriteLine($"\n尝试下载不存在的文件:{nonExistentObject}...");
            if (!minioOperator.DownloadFile(bucketName, nonExistentObject, downloadNonExistentPath))
            {
                Console.WriteLine("下载不存在的文件失败(预期结果)。");
            }
            Thread.Sleep(1000); 
            Console.WriteLine("\n----------------------------------------");
            
            Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName} 删除文件 {minioObjectName1}...");
            if (minioOperator.DeleteFile(bucketName, minioObjectName1))
            {
                Console.WriteLine("文件删除成功。");
            }
            else
            {
                Console.WriteLine("文件删除失败。");
            }
            Thread.Sleep(1000); 
            
            Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName} 删除文件 {minioObjectName2}...");
            if (minioOperator.DeleteFile(bucketName, minioObjectName2))
            {
                Console.WriteLine("文件删除成功。");
            }
            else
            {
                Console.WriteLine("文件删除失败。");
            }
            Console.WriteLine("\n--- MinIO 批量操作结束 ---");
            
            try
            {
                if (File.Exists(localFilePath1))
                {
                    File.Delete(localFilePath1);
                    Console.WriteLine($"已清理本地测试文件:{localFilePath1}");
                }
                if (File.Exists(localFilePath2))
                {
                    File.Delete(localFilePath2);
                    Console.WriteLine($"已清理本地测试文件:{localFilePath2}");
                }
                if (File.Exists(downloadSavePath1))
                {
                    File.Delete(downloadSavePath1);
                    Console.WriteLine($"已清理本地下载文件:{downloadSavePath1}");
                }
                if (File.Exists(downloadNonExistentPath))
                {
                    File.Delete(downloadNonExistentPath);
                    Console.WriteLine($"已清理本地下载文件:{downloadNonExistentPath}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"清理本地文件失败:{ex.Message}");
            }
            Console.WriteLine("\n按任意键退出程序。");
            Console.ReadKey();
        }
    }
}
转自https://www.cnblogs.com/hanfan/p/19010378
该文章在 2025/8/1 9:41:45 编辑过