.NET Core Clean Architecture下如何合理分离API与文件存储服务?
.NET Core Clean Architecture:解耦API服务与文件存储服务并正确管理资源
问题背景
在采用Clean Architecture的.NET Core项目中,需完成以下任务:
- 向第三方API发起HTTP请求
- 将响应内容读取为Stream
- 将Stream保存至文件存储
当前实现通过Func<Stream, Task>回调将API服务与文件存储服务耦合,希望实现两者完全解耦,支持独立调用(如下示例),同时确保HttpResponseMessage和Stream等资源被正确释放:
string url = "<some_url>"; Stream result = await apiService.GetDataAsync(url); string path ="<some_path>"; await fileStorageService.CreateFileAsync(path, result);
当前耦合实现代码
ApiService.cs
public async Task GetDataAsync(string url, Func<Stream, Task> func) { using (HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(url)) { if (httpResponseMessage.IsSuccessStatusCode) { using (Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync()) { await func(stream); } } } }
FileStorageService.cs
public async Task CreateFileAsync(string path, Stream content) { using (FileStream fileStream = new(path, FileMode.Create)) { await content.CopyToAsync(fileStream); } }
核心层组合方法
public async Task DownloadData(string url, string path) { await apiService.GetDataAsync(url, async stream => { await fileStorageService.CreateFileAsync(path, stream); }); }
解决方案
核心思路是通过自定义Stream包装类,将HttpResponseMessage和底层Stream的资源释放逻辑绑定,让ApiService直接返回Stream,调用方通过using语句管理资源,实现两个服务完全解耦。
1. 实现自定义Stream包装类
创建继承自Stream的包装类,在Dispose时同时释放底层Stream和HttpResponseMessage:
public class HttpResponseStreamWrapper : Stream { private readonly Stream _innerStream; private readonly HttpResponseMessage _response; public HttpResponseStreamWrapper(Stream innerStream, HttpResponseMessage response) { _innerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); _response = response ?? throw new ArgumentNullException(nameof(response)); } // 委托所有Stream抽象方法到内部流 public override bool CanRead => _innerStream.CanRead; public override bool CanSeek => _innerStream.CanSeek; public override bool CanWrite => _innerStream.CanWrite; public override long Length => _innerStream.Length; public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } public override void Flush() => _innerStream.Flush(); public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); public override void SetLength(long value) => _innerStream.SetLength(value); public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count); // 重写Dispose,同时释放内部流和响应对象 protected override void Dispose(bool disposing) { if (disposing) { _innerStream.Dispose(); _response.Dispose(); } base.Dispose(disposing); } // 实现异步Dispose(.NET Core 3.0+推荐) public override ValueTask DisposeAsync() { var innerDispose = _innerStream.DisposeAsync(); var responseDispose = _response.DisposeAsync(); return new ValueTask(Task.WhenAll(innerDispose.AsTask(), responseDispose.AsTask())); } }
2. 修改ApiService返回Stream
移除Func参数,返回包装后的Stream,同时使用HttpCompletionOption.ResponseHeadersRead提升大文件场景性能:
public async Task<Stream> GetDataAsync(string url) { var httpResponse = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); httpResponse.EnsureSuccessStatusCode(); // 可根据业务需求调整失败处理逻辑(如返回空Stream或自定义异常) var stream = await httpResponse.Content.ReadAsStreamAsync(); return new HttpResponseStreamWrapper(stream, httpResponse); }
3. 调用方解耦代码
调用方通过using语句管理Stream生命周期,完全独立调用两个服务:
string url = "<some_url>"; using (Stream apiStream = await apiService.GetDataAsync(url)) { string savePath = "<some_path>"; await fileStorageService.CreateFileAsync(savePath, apiStream); }
关键说明
- 资源释放:using块结束时,
HttpResponseStreamWrapper的Dispose方法会自动释放底层Stream和HttpResponseMessage,避免资源泄漏。 - 服务解耦:ApiService和FileStorageService彼此无依赖,可独立替换或扩展(如替换文件存储为云存储,无需修改ApiService)。
- Pipeline扩展:未来可在Stream传递过程中加入中间处理(如压缩、加密、校验),只需在调用链中插入对应的Stream包装类即可。
内容的提问来源于stack exchange,提问作者Vedran Knezevic
相关产品推荐
相关产品推荐

