我正在处理一个 .NET Core 项目,我需要在其中实现一个干净的架构模式。任务是:
为了解决这个问题,我在一个基础设施层创建了两个类:
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);
}
}
}
}
public async Task CreateFileAsync(string path, Stream content)
{
using (FileStream fileStream = new(path, FileMode.Create))
{
await content.CopyToAsync(fileStream);
}
}
Core Layer中还有第三种方法将以上两种方法结合处理:
public async Task DownloadData(string url, string path)
{
await apiService.GetDataAsync(url, async stream =>
{
await fileStorageService.CreateFileAsync(path, stream);
});
}
尽管上面的代码有效,但文件存储服务明显直接依赖于 API 服务。我的期望是将文件和 API 服务完全分开,并且其中两个不需要相互了解。我希望能够选择在将来(如果需要)实现管道模式,并能够以某种方式定义上述代码:
string url = "<some_url>";
Stream result = await apiService.GetDataAsync(url);
string path ="<some_path>";
await fileStorageService.CreateFileAsync(path, result);
我试图用 API 服务方法中的 Stream 返回类型替换 Func<> 参数,但我在正确处理 HttpResponseMessage 和 Stream 中的 using 语句方面面临挑战。
如果您对如何解决上述问题有一些建议,我将不胜感激,以便:
提前致谢。
我不确定你是否考虑过类路线,但我更喜欢处理这个问题的方法是有一个
StorageFile
和 StorageFolder
框架来模仿 UWP/Metro/等。从一开始就有。事实上,dotnet/runtime Github repo 中有一个非常古老但仍然悬而未决的问题。
这个想法只是简单地拥有抽象基类——
StorageFile
、StorageFolder
和它们的父类 StorageObject
——为你想要使用的每个存储平台实现,从本地文件系统到 Azure blob 到任何其他东西。
这些是基本定义:
public abstract class StorageObject
{
public abstract string Name { get; }
public abstract Task RenameAsync();
public abstract Task<StorageProperties> GetPropertiesAsync(); // would retrieve metadata like creation/access dates, size, etc.
public abstract Task DeleteAsync();
public abstract Task CopyAsync (StorageFolder destination);
public abstract Task<IEnumerable<StorageObject>> GetChildrenAsync();
}
public abstract class StorageFolder : StorageObject
{
public abstract int Files { get; }
public abstract int Folders { get; }
public abstract Task<StorageFile> GetFileAsync (string filename, bool createIfNotExists);
public abstract Task<StorageFolder> GetSubfolderAsync (string subfolderName, bool createIfNotExists);
public virtual Task<IEnumerable<StorageFile>> FindFilesAsync (string criteria, bool includeSubfolders) { ... } // default implementation would use GetChildrenAsync but be overridable for more efficient implementations
public virtual Task<IEnumerable<StorageFolder>> FindSubfoldersAsync (string criteria, bool includeSubfolders) { ... } // same
}
public abstract class StorageFile : StorageObject
{
public abstract Task<Stream> OpenReadAsync(bool shared = false);
public abstract Task<Stream> OpenWriteAsync(bool shared = false);
}
所以
LocalStorageFile
的基本(不完整)实现,例如,看起来像这样:
public class LocalStorageFile : StorageFile
{
private readonly string _path;
public LocalStorageFile (string path)
{
_path = path;
}
public override Task<Stream> OpenReadAsync(bool shared = false)
{
var str = File.Open(_path, FileMode.Open, FileAccess.Read, shared ? FileShare.Read : FileShare.None);
return Task.FromResult(str);
}
// ...
}
然后你会这样消费它:
LocalStorageFile f = new LocalStorageFile("c:\\mypath\\file.bin");
using (var str = await f.OpenStreamForReadAsync())
{
// do your thing
}
最重要的是,您的所有其余代码都引用基类 -
StorageFile
等,因此对实现完全不可知。通过处理 Stream
- 另一个高度抽象和可扩展的类 - 除了 Dispose
它之外,您不需要做任何特别的事情。当然,如果你有一些高度专业化的东西,你也可以子类化 Stream
并从 OpenReadAsync
和 OpenWriteAsync
返回任何你想要的东西。
在评论中讨论后,我认为问题在于流由
apiService
处理,然后 fileStorageService
抛出已处理的对象异常。
这个问题的一个可能的解决方案是删除
using
中的apiService
语句并返回流。
public async Task<Stream> GetDataAsync(string url)
{
using (HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(url))
{
if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsStreamAsync();
}
}
}
然后你可以通过以下方式使用它:
string url = "<some_url>";
Stream result = await apiService.GetDataAsync(url);
using(result)
{
string path ="<some_path>";
await fileStorageService.CreateFileAsync(path, result);
}