Skip to content

Commit f72a025

Browse files
authored
Exception when configure. Return null if file doesn't exist (#22)
* Added check configuration when register service * Return null if blob doesn't exist when download and get blob for Azure and AWS * Returning null if blob doesn't exist when download for AspNetExtensions * Returning null if blob doesn't exist when download and get blob for GCP * Returning null if blob doesn't exist when download and get blob for FileSystemStorage * Added test cases if file doesn't exist * Added test cases if file doesn't exist for extensions * Fixes * Fixed GetBlobAsync and upgraded package version * Added tests for ServiceCollectionExtensions
1 parent 5f48443 commit f72a025

File tree

22 files changed

+640
-167
lines changed

22 files changed

+640
-167
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1414
<PackageReadmeFile>README.md</PackageReadmeFile>
1515
<Product>Managed Code - Storage</Product>
16-
<Version>1.0.0</Version>
17-
<PackageVersion>1.0.0</PackageVersion>
16+
<Version>1.1.0</Version>
17+
<PackageVersion>1.1.0</PackageVersion>
1818
</PropertyGroup>
1919
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
2020
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>

ManagedCode.Storage.AspNetExtensions/StorageExtensions.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Linq;
54
using System.Runtime.CompilerServices;
65
using System.Threading;
76
using System.Threading.Tasks;
@@ -59,21 +58,31 @@ public static async IAsyncEnumerable<BlobMetadata> UploadToStorageAsync(this ISt
5958
}
6059
}
6160

62-
public static async Task<FileResult> DownloadAsFileResult(this IStorage storage, string blobName, CancellationToken cancellationToken = default)
61+
public static async Task<FileResult?> DownloadAsFileResult(this IStorage storage, string blobName, CancellationToken cancellationToken = default)
6362
{
6463
var localFile = await storage.DownloadAsync(blobName, cancellationToken);
6564

65+
if (localFile is null)
66+
{
67+
return null;
68+
}
69+
6670
return new FileStreamResult(localFile.FileStream, MimeHelper.GetMimeType(localFile.FileInfo.Extension))
6771
{
6872
FileDownloadName = localFile.FileName
6973
};
7074
}
7175

72-
public static async Task<FileResult> DownloadAsFileResult(this IStorage storage, BlobMetadata blobMetadata,
76+
public static async Task<FileResult?> DownloadAsFileResult(this IStorage storage, BlobMetadata blobMetadata,
7377
CancellationToken cancellationToken = default)
7478
{
7579
var localFile = await storage.DownloadAsync(blobMetadata, cancellationToken);
7680

81+
if (localFile is null)
82+
{
83+
return null;
84+
}
85+
7786
return new FileStreamResult(localFile.FileStream, MimeHelper.GetMimeType(localFile.FileInfo.Extension))
7887
{
7988
FileDownloadName = localFile.FileName

ManagedCode.Storage.Aws/AWSStorage.cs

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Linq;
4+
using System.Net;
55
using System.Runtime.CompilerServices;
66
using System.Text;
77
using System.Threading;
@@ -22,7 +22,7 @@ public class AWSStorage : IAWSStorage
2222

2323
public AWSStorage(AWSStorageOptions options)
2424
{
25-
_bucket = options.Bucket;
25+
_bucket = options.Bucket!;
2626
var config = options.OriginalOptions ?? new AmazonS3Config();
2727
_s3Client = new AmazonS3Client(new BasicAWSCredentials(options.PublicKey, options.SecretKey), config);
2828
}
@@ -68,29 +68,41 @@ public async Task DeleteAsync(IEnumerable<BlobMetadata> blobNames, CancellationT
6868

6969
#region Download
7070

71-
public async Task<Stream> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
71+
public async Task<Stream?> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
7272
{
73-
return await _s3Client.GetObjectStreamAsync(_bucket, blobName, null, cancellationToken);
73+
try
74+
{
75+
return await _s3Client.GetObjectStreamAsync(_bucket, blobName, null, cancellationToken);
76+
}
77+
catch (AmazonS3Exception ex) when (ex.StatusCode is HttpStatusCode.NotFound)
78+
{
79+
return null;
80+
}
7481
}
7582

76-
public async Task<Stream> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
83+
public async Task<Stream?> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
7784
{
7885
return await DownloadAsStreamAsync(blobMetadata.Name, cancellationToken);
7986
}
8087

81-
public async Task<LocalFile> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
88+
public async Task<LocalFile?> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
8289
{
8390
var localFile = new LocalFile();
8491

8592
using (var stream = await DownloadAsStreamAsync(blobName, cancellationToken))
8693
{
94+
if (stream is null)
95+
{
96+
return null;
97+
}
98+
8799
await stream.CopyToAsync(localFile.FileStream, 81920, cancellationToken);
88100
}
89101

90102
return localFile;
91103
}
92104

93-
public async Task<LocalFile> DownloadAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
105+
public async Task<LocalFile?> DownloadAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
94106
{
95107
return await DownloadAsync(blobMetadata.Name, cancellationToken);
96108
}
@@ -140,22 +152,28 @@ public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<BlobMetadata> blobs,
140152

141153
#region Get
142154

143-
public async Task<BlobMetadata> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
155+
public async Task<BlobMetadata?> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
144156
{
145157
var objectMetaRequest = new GetObjectMetadataRequest
146158
{
147159
BucketName = _bucket,
148160
Key = blobName
149161
};
150162

151-
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest);
152-
153-
return new BlobMetadata
163+
try
154164
{
155-
Name = blobName,
156-
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{blobName}"),
157-
ContentType = objectMetaResponse.Headers.ContentType
158-
};
165+
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest, cancellationToken);
166+
return new BlobMetadata
167+
{
168+
Name = blobName,
169+
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{blobName}"),
170+
ContentType = objectMetaResponse.Headers.ContentType
171+
};
172+
}
173+
catch (AmazonS3Exception ex) when (ex.StatusCode is HttpStatusCode.NotFound)
174+
{
175+
return null;
176+
}
159177
}
160178

161179
public async IAsyncEnumerable<BlobMetadata> GetBlobListAsync(
@@ -170,7 +188,7 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobListAsync(
170188

171189
do
172190
{
173-
var objectsResponse = await _s3Client.ListObjectsAsync(objectsRequest);
191+
var objectsResponse = await _s3Client.ListObjectsAsync(objectsRequest, cancellationToken);
174192

175193
foreach (var entry in objectsResponse.S3Objects)
176194
{
@@ -180,21 +198,13 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobListAsync(
180198
Key = entry.Key
181199
};
182200

183-
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest);
184-
185-
var objectAclRequest = new GetACLRequest
186-
{
187-
BucketName = _bucket,
188-
Key = entry.Key
189-
};
190-
191-
var objectAclResponse = await _s3Client.GetACLAsync(objectAclRequest);
192-
var isPublic = objectAclResponse.AccessControlList.Grants.Any(x =>
193-
x.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers");
201+
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest, cancellationToken);
194202

195203
yield return new BlobMetadata
196204
{
197-
Name = entry.Key
205+
Name = entry.Key,
206+
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{entry.Key}"),
207+
ContentType = objectMetaResponse.Headers.ContentType
198208
};
199209
}
200210

@@ -215,7 +225,12 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobsAsync(IEnumerable<string> bl
215225
{
216226
foreach (var blob in blobNames)
217227
{
218-
yield return await GetBlobAsync(blob, cancellationToken);
228+
var blobMetadata = await GetBlobAsync(blob, cancellationToken);
229+
230+
if (blobMetadata is not null)
231+
{
232+
yield return blobMetadata;
233+
}
219234
}
220235
}
221236

@@ -313,6 +328,6 @@ public async Task CreateContainerAsync(CancellationToken cancellationToken = def
313328
{
314329
await _s3Client.EnsureBucketExistsAsync(_bucket);
315330
}
316-
331+
317332
#endregion
318333
}
Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using ManagedCode.Storage.Aws.Options;
33
using ManagedCode.Storage.Core;
4+
using ManagedCode.Storage.Core.Exceptions;
45
using Microsoft.Extensions.DependencyInjection;
56

67
namespace ManagedCode.Storage.Aws.Extensions;
@@ -9,25 +10,53 @@ public static class ServiceCollectionExtensions
910
{
1011
public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, Action<AWSStorageOptions> action)
1112
{
12-
var awsStorageOptions = new AWSStorageOptions();
13-
action.Invoke(awsStorageOptions);
14-
return serviceCollection.AddAWSStorage(awsStorageOptions);
13+
var options = new AWSStorageOptions();
14+
action.Invoke(options);
15+
16+
CheckConfiguration(options);
17+
18+
return serviceCollection.AddAWSStorage(options);
1519
}
1620

1721
public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, Action<AWSStorageOptions> action)
1822
{
19-
var awsStorageOptions = new AWSStorageOptions();
20-
action.Invoke(awsStorageOptions);
21-
return serviceCollection.AddAWSStorageAsDefault(awsStorageOptions);
23+
var options = new AWSStorageOptions();
24+
action.Invoke(options);
25+
26+
CheckConfiguration(options);
27+
28+
return serviceCollection.AddAWSStorageAsDefault(options);
2229
}
23-
30+
2431
public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, AWSStorageOptions options)
2532
{
33+
CheckConfiguration(options);
34+
2635
return serviceCollection.AddScoped<IAWSStorage>(_ => new AWSStorage(options));
2736
}
2837

2938
public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, AWSStorageOptions options)
3039
{
40+
CheckConfiguration(options);
41+
3142
return serviceCollection.AddScoped<IStorage>(_ => new AWSStorage(options));
3243
}
44+
45+
private static void CheckConfiguration(AWSStorageOptions options)
46+
{
47+
if (string.IsNullOrEmpty(options.PublicKey))
48+
{
49+
throw new BadConfigurationException($"{nameof(options.PublicKey)} cannot be empty");
50+
}
51+
52+
if (string.IsNullOrEmpty(options.SecretKey))
53+
{
54+
throw new BadConfigurationException($"{nameof(options.SecretKey)} cannot be empty");
55+
}
56+
57+
if (string.IsNullOrEmpty(options.Bucket))
58+
{
59+
throw new BadConfigurationException($"{nameof(options.Bucket)} cannot be empty");
60+
}
61+
}
3362
}

ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace ManagedCode.Storage.Aws.Options;
44

55
public class AWSStorageOptions
66
{
7-
public string PublicKey { get; set; } = null!;
8-
public string SecretKey { get; set; } = null!;
9-
public string Bucket { get; set; } = null!;
7+
public string? PublicKey { get; set; }
8+
public string? SecretKey { get; set; }
9+
public string? Bucket { get; set; }
1010
public AmazonS3Config? OriginalOptions { get; set; }
1111
}

ManagedCode.Storage.Azure/AzureStorage.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class AzureStorage : IAzureStorage
2121
public AzureStorage(AzureStorageOptions options)
2222
{
2323
_options = options;
24+
2425
_blobContainerClient = new BlobContainerClient(
2526
options.ConnectionString,
2627
options.Container,
@@ -66,30 +67,43 @@ public async Task DeleteAsync(IEnumerable<BlobMetadata> blobNames, CancellationT
6667

6768
#region Download
6869

69-
public async Task<Stream> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
70+
public async Task<Stream?> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
7071
{
7172
var blobClient = _blobContainerClient.GetBlobClient(blobName);
72-
var res = await blobClient.DownloadStreamingAsync(cancellationToken: cancellationToken);
7373

74-
return res.Value.Content;
74+
try
75+
{
76+
var response = await blobClient.DownloadAsync(cancellationToken);
77+
return response.Value.Content;
78+
}
79+
catch (RequestFailedException ex) when (ex.ErrorCode == BlobErrorCode.BlobNotFound)
80+
{
81+
return null;
82+
}
7583
}
7684

77-
public async Task<Stream> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
85+
public async Task<Stream?> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
7886
{
7987
return await DownloadAsStreamAsync(blobMetadata.Name, cancellationToken);
8088
}
8189

82-
public async Task<LocalFile> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
90+
public async Task<LocalFile?> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
8391
{
8492
var blobClient = _blobContainerClient.GetBlobClient(blobName);
85-
var localFile = new LocalFile();
86-
87-
await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);
93+
try
94+
{
95+
var localFile = new LocalFile();
96+
await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);
8897

89-
return localFile;
98+
return localFile;
99+
}
100+
catch (RequestFailedException ex) when (ex.ErrorCode == BlobErrorCode.BlobNotFound)
101+
{
102+
return null;
103+
}
90104
}
91105

92-
public async Task<LocalFile> DownloadAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
106+
public async Task<LocalFile?> DownloadAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
93107
{
94108
return await DownloadAsync(blobMetadata.Name, cancellationToken);
95109
}
@@ -136,11 +150,16 @@ public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<BlobMetadata> blobs,
136150

137151
#region Get
138152

139-
public async Task<BlobMetadata> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
153+
public async Task<BlobMetadata?> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
140154
{
141155
await Task.Yield();
142156

143157
var blobClient = _blobContainerClient.GetBlobClient(blobName);
158+
159+
if (!await blobClient.ExistsAsync(cancellationToken))
160+
{
161+
return null;
162+
}
144163

145164
return new BlobMetadata
146165
{
@@ -155,7 +174,12 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobsAsync(IEnumerable<string> bl
155174
{
156175
foreach (var blob in blobNames)
157176
{
158-
yield return await GetBlobAsync(blob, cancellationToken);
177+
var blobMetadata = await GetBlobAsync(blob, cancellationToken);
178+
179+
if (blobMetadata is not null)
180+
{
181+
yield return blobMetadata;
182+
}
159183
}
160184
}
161185

0 commit comments

Comments
 (0)