基于整洁架构,如何传递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)只需新增对应适配器,无需修改核心业务逻辑。
- 关注点分离:各层职责清晰,便于维护和扩展。
- 可测试性:应用层可通过Mock
IFileStoragePort进行单元测试,无需依赖真实Azure存储服务。
内容的提问来源于stack exchange,提问作者Deryn
相关产品推荐
相关产品推荐

