You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为可跨本地与Azure云部署的C#应用抽象存储层?

刚好我之前做过类似的跨环境存储适配需求,给你一套落地性很强的方案——核心就是用抽象层+依赖注入把存储逻辑和业务代码彻底解耦,完全不用让业务关心底层是本地SMB还是Azure文件共享:

1. 先定义统一的文件操作抽象接口

先写一个通用的IFileStorage接口,涵盖业务需要的所有文件操作(比如创建目录、上传下载、列举文件等),业务层只依赖这个接口,完全不碰具体实现:

public interface IFileStorage
{
    Task CreateDirectoryAsync(string directoryPath, CancellationToken cancellationToken = default);
    Task UploadFileAsync(string localFilePath, string remoteFilePath, CancellationToken cancellationToken = default);
    Task DownloadFileAsync(string remoteFilePath, string localFilePath, CancellationToken cancellationToken = default);
    Task<IEnumerable<string>> ListFilesAsync(string directoryPath, CancellationToken cancellationToken = default);
    Task DeleteFileAsync(string remoteFilePath, CancellationToken cancellationToken = default);
}
2. 分别实现两种存储的具体逻辑

针对本地SMB和Azure文件共享,各自写实现类,把复杂的配置和操作细节都封装在这里:

本地SMB实现

因为Linux容器挂载SMB共享后,路径和本地文件系统完全一致,直接用System.IO的API封装即可:

public class SmbFileStorage : IFileStorage
{
    private readonly string _baseSharePath;

    public SmbFileStorage(string baseSharePath)
    {
        _baseSharePath = baseSharePath;
    }

    public async Task CreateDirectoryAsync(string directoryPath, CancellationToken cancellationToken = default)
    {
        var fullPath = Path.Combine(_baseSharePath, directoryPath);
        await Task.Run(() => Directory.CreateDirectory(fullPath), cancellationToken);
    }

    public async Task UploadFileAsync(string localFilePath, string remoteFilePath, CancellationToken cancellationToken = default)
    {
        var fullRemotePath = Path.Combine(_baseSharePath, remoteFilePath);
        // 确保父目录存在
        await CreateDirectoryAsync(Path.GetDirectoryName(remoteFilePath)!, cancellationToken);
        await File.CopyAsync(localFilePath, fullRemotePath, overwrite: true, cancellationToken);
    }

    // 其他方法(下载、列举、删除)都类似,基于System.IO异步API封装即可
}

Azure文件共享实现

用官方的Azure.Storage.Files.Shares NuGet包,把Azure的存储操作封装成符合接口的实现:

public class AzureFileStorage : IFileStorage
{
    private readonly ShareClient _shareClient;

    public AzureFileStorage(string connectionString, string shareName)
    {
        _shareClient = new ShareClient(connectionString, shareName);
    }

    public async Task CreateDirectoryAsync(string directoryPath, CancellationToken cancellationToken = default)
    {
        var directoryClient = _shareClient.GetDirectoryClient(directoryPath);
        await directoryClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
    }

    public async Task UploadFileAsync(string localFilePath, string remoteFilePath, CancellationToken cancellationToken = default)
    {
        // 先确保父目录存在
        await CreateDirectoryAsync(Path.GetDirectoryName(remoteFilePath)!, cancellationToken);
        
        var fileClient = _shareClient.GetFileClient(remoteFilePath);
        await using var fileStream = File.OpenRead(localFilePath);
        await fileClient.CreateAsync(fileStream.Length, cancellationToken: cancellationToken);
        await fileClient.UploadAsync(fileStream, cancellationToken: cancellationToken);
    }

    // 其他方法同样基于Azure SDK封装,对应接口定义
}
3. 通过配置+依赖注入动态切换

在appsettings.json里配置当前环境的存储类型和参数:

"FileStorageSettings": {
    "StorageType": "Azure", // 可选值:Azure/Smb
    "Azure": {
        "ConnectionString": "your-azure-storage-connection-string",
        "ShareName": "your-share-name"
    },
    "Smb": {
        "BaseSharePath": "/mnt/smb-share" // Linux容器中挂载的SMB共享路径
    }
}

然后在Program.cs里根据配置注册对应的服务:

var storageSettings = builder.Configuration.GetSection("FileStorageSettings").Get<FileStorageSettings>();

switch (storageSettings.StorageType.ToLower())
{
    case "azure":
        builder.Services.AddSingleton<IFileStorage>(_ => 
            new AzureFileStorage(storageSettings.Azure.ConnectionString, storageSettings.Azure.ShareName));
        break;
    case "smb":
        builder.Services.AddSingleton<IFileStorage>(_ => 
            new SmbFileStorage(storageSettings.Smb.BaseSharePath));
        break;
    default:
        throw new InvalidOperationException($"Unsupported storage type: {storageSettings.StorageType}");
}
4. 业务层轻松调用

业务代码只需要注入IFileStorage,完全不用关心底层是哪种存储:

public class DocumentService
{
    private readonly IFileStorage _fileStorage;

    public DocumentService(IFileStorage fileStorage)
    {
        _fileStorage = fileStorage;
    }

    public async Task UploadUserDocumentAsync(string localFilePath, string userId)
    {
        await _fileStorage.CreateDirectoryAsync($"user-docs/{userId}");
        await _fileStorage.UploadFileAsync(localFilePath, $"user-docs/{userId}/{Path.GetFileName(localFilePath)}");
    }
}
额外注意点
  • Linux容器挂载SMB时,需要在容器启动配置里完成挂载(比如Docker的--mount参数,或者K8s的PersistentVolumeClaim),应用只需要用挂载后的路径即可。
  • 如果想简化Azure部署的配置,也可以把Azure文件共享挂载到容器里,复用SMB的实现,但直接用Azure SDK能更好地利用Azure的原生特性(比如快照、权限控制)。
  • 可以在实现类里统一处理异常,比如封装成自定义的FileStorageException,业务层只需要处理这一种异常即可。

内容的提问来源于stack exchange,提问作者Russ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:41:27