I am working on a .NET Core project in which I need to implement a Clean Architecture Pattern. The Task is to:
- make an HTTP request to a third-party API service
- read the response content as a Stream
- save the Stream Content to File Storage
In order to solve this problem, I created two classes in an infrastructure layer:
- 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);
}
}
There is also a third method in Core Layer that combine and process the above two methods:
public async Task DownloadData(string url, string path)
{
await apiService.GetDataAsync(url, async stream =>
{
await fileStorageService.CreateFileAsync(path, stream);
});
}
Even though the above code works there is an obvious direct dependency of File Storage Service on API Service. My Expectations are to have a clean separation of File and API service in a way that two of those do not need to know about each other. I would like to be able to have the option to implement Pipeline pattern in the future (if needed) and to be able to define the above code somehow in a way:
string url = "<some_url>";
Stream result = await apiService.GetDataAsync(url);
string path ="<some_path>";
await fileStorageService.CreateFileAsync(path, result);
I was trying to replace Func<> parameter with the Stream return type in API Service Method, but I face a challenge in properly dealing with using statements in HttpResponseMessage and Stream.
I will appreciate it a lot if you have some suggestions on how to solve the above problem in order to:
- clearly separate API Service from File Service so that one does not know about another
- properly dispose of the Using statements in HttpResponseMessage and Stream from apiService.GetDataAsync method without (if possible) using Func<>
Thanks in advance.
I'm not sure if you've considered the class route, but my preferred way of dealing with this is to have a
StorageFileandStorageFolderframework that mimics what UWP/Metro/etc. have had from the beginning. In fact there's a very old but still open issue in the dotnet/runtime Github repo.The idea is simply to have abstract base classes -
StorageFile,StorageFolder, and their parentStorageObject- that get implemented for each storage platform you want to use, from the local file system to Azure blobs to anything else.These would be the basic definitions:
So a basic (incomplete) implementation of
LocalStorageFile, for example, would look like this:Then you would consume it as such:
Most importantly, all the rest of your code references the base classes -
StorageFile, etc., and so becomes completely agnostic to the implementation. And by dealing withStream- another highly abstract and extensible class - you don't have to do anything special other thanDisposeit. Of course if you have something highly specialized you can subclassStreamtoo and return whatever you want fromOpenReadAsyncandOpenWriteAsync.