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

基于整洁架构,如何传递HTTP请求体以向Azure File Storage上传文件

基于整洁架构实现HTTP请求体上传至Azure File Storage

按照整洁架构的分层原则,我们需要将表现层(API)、应用层、基础设施层职责分离,同时遵循依赖倒置原则,让高层模块不依赖底层实现,而是依赖抽象。以下是具体的调整方案:

分层职责定义

  • 表现层:仅负责处理HTTP请求,解析请求参数与请求体,调用应用层逻辑并返回响应。
  • 应用层:定义业务规则与抽象依赖(端口),不涉及任何外部服务的具体实现。
  • 基础设施层:实现应用层定义的抽象端口,处理与Azure File Storage的具体交互。

代码调整实现

1. 应用层:定义抽象存储端口

在应用层定义一个抽象接口,作为与外部存储服务交互的端口,让应用层无需关心具体存储实现:

public interface IFileStoragePort
{
    Task UploadFileAsync(string shareName, string directoryName, string fileName, Stream fileContentStream, CancellationToken cancellationToken = default);
}

2. 应用层:重构业务逻辑类

修改原AzureUploadApplication,通过构造注入依赖抽象端口,专注于业务逻辑(如参数校验),不再直接依赖Azure SDK:

public class AzureUploadApplication
{
    private readonly IFileStoragePort _fileStoragePort;

    public AzureUploadApplication(IFileStoragePort fileStoragePort)
    {
        _fileStoragePort = fileStoragePort ?? throw new ArgumentNullException(nameof(fileStoragePort));
    }

    public async Task UploadToAzureAsync(string fileName, string directory, string shareName, Stream fileContentStream, CancellationToken cancellationToken = default)
    {
        // 业务参数校验
        if (string.IsNullOrWhiteSpace(fileName))
            throw new ArgumentNullException(nameof(fileName));
        if (string.IsNullOrWhiteSpace(directory))
            throw new ArgumentNullException(nameof(directory));
        if (string.IsNullOrWhiteSpace(shareName))
            throw new ArgumentNullException(nameof(shareName));
        if (fileContentStream == null || !fileContentStream.CanRead)
            throw new ArgumentException("文件内容流不可读", nameof(fileContentStream));

        // 调用抽象端口执行上传
        await _fileStoragePort.UploadFileAsync(shareName, directory, fileName, fileContentStream, cancellationToken);
    }
}

3. 基础设施层:实现Azure存储适配器

新增适配器类,实现应用层定义的IFileStoragePort,处理Azure File Storage的具体操作:

public class AzureFileStorageAdapter : IFileStoragePort
{
    private readonly string _connectionString;

    public AzureFileStorageAdapter(string connectionString)
    {
        _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
    }

    public async Task UploadFileAsync(string shareName, string directoryName, string fileName, Stream fileContentStream, CancellationToken cancellationToken = default)
    {
        // 初始化Azure存储客户端
        ShareClient shareClient = new ShareClient(_connectionString, shareName);
        await shareClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

        // 获取或创建目录
        ShareDirectoryClient directoryClient = shareClient.GetDirectoryClient(directoryName);
        await directoryClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

        // 上传文件内容
        ShareFileClient fileClient = directoryClient.GetFileClient(fileName);
        fileContentStream.Position = 0; // 重置流位置,确保从开头读取
        await fileClient.UploadAsync(fileContentStream, cancellationToken: cancellationToken);
    }
}

4. 表现层:重构HTTP触发函数

修改原UploadToAzureApi,读取HTTP请求体的二进制内容,通过构造注入应用层实例,完成请求处理:

public class UploadToAzureApi
{
    private readonly ILogger<UploadToAzureApi> _log;
    private readonly AzureUploadApplication _application;

    public UploadToAzureApi(ILogger<UploadToAzureApi> log, AzureUploadApplication application)
    {
        _log = log;
        _application = application;
    }

    [FunctionName("UploadToAzureApi")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "upload/{shareName}/{directory}/{fileName}")] HttpRequest req,
        string shareName,
        string directory,
        string fileName,
        CancellationToken cancellationToken)
    {
        _log.LogInformation("C# HTTP触发函数已处理文件上传请求。");

        try
        {
            // 读取请求体的二进制流
            using var requestStream = req.Body;
            await _application.UploadToAzureAsync(fileName, directory, shareName, requestStream, cancellationToken);

            return new OkObjectResult("文件上传成功");
        }
        catch (ArgumentNullException ex)
        {
            _log.LogError(ex, "上传参数为空");
            return new BadRequestObjectResult($"参数错误:{ex.Message}");
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "文件上传失败");
            return new StatusCodeResult(StatusCodes.Status500InternalServerError);
        }
    }
}

5. 配置依赖注入

在函数应用的启动类中注册依赖,将基础设施层的实现注入到应用层:

[assembly: FunctionsStartup(typeof(Startup))]
public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        string connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage") 
            ?? throw new InvalidOperationException("未配置Azure存储连接字符串");

        // 注册存储适配器
        builder.Services.AddSingleton<IFileStoragePort>(new AzureFileStorageAdapter(connectionString));
        // 注册应用层服务
        builder.Services.AddScoped<AzureUploadApplication>();
    }
}

方案优势

  • 依赖倒置:应用层仅依赖抽象端口,更换存储服务(如AWS S3)只需新增对应适配器,无需修改核心业务逻辑。
  • 关注点分离:各层职责清晰,便于维护和扩展。
  • 可测试性:应用层可通过MockIFileStoragePort进行单元测试,无需依赖真实Azure存储服务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:20:38