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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:25:10