Skip to content

Commit

Permalink
Exception when configure. Return null if file doesn't exist (#22)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dtymakhov authored Mar 29, 2022
1 parent 5f48443 commit f72a025
Show file tree
Hide file tree
Showing 22 changed files with 640 additions and 167 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>Managed Code - Storage</Product>
<Version>1.0.0</Version>
<PackageVersion>1.0.0</PackageVersion>
<Version>1.1.0</Version>
<PackageVersion>1.1.0</PackageVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
Expand Down
15 changes: 12 additions & 3 deletions ManagedCode.Storage.AspNetExtensions/StorageExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -59,21 +58,31 @@ public static async IAsyncEnumerable<BlobMetadata> UploadToStorageAsync(this ISt
}
}

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

if (localFile is null)
{
return null;
}

return new FileStreamResult(localFile.FileStream, MimeHelper.GetMimeType(localFile.FileInfo.Extension))
{
FileDownloadName = localFile.FileName
};
}

public static async Task<FileResult> DownloadAsFileResult(this IStorage storage, BlobMetadata blobMetadata,
public static async Task<FileResult?> DownloadAsFileResult(this IStorage storage, BlobMetadata blobMetadata,
CancellationToken cancellationToken = default)
{
var localFile = await storage.DownloadAsync(blobMetadata, cancellationToken);

if (localFile is null)
{
return null;
}

return new FileStreamResult(localFile.FileStream, MimeHelper.GetMimeType(localFile.FileInfo.Extension))
{
FileDownloadName = localFile.FileName
Expand Down
75 changes: 45 additions & 30 deletions ManagedCode.Storage.Aws/AWSStorage.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
Expand All @@ -22,7 +22,7 @@ public class AWSStorage : IAWSStorage

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

#region Download

public async Task<Stream> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<Stream?> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
{
return await _s3Client.GetObjectStreamAsync(_bucket, blobName, null, cancellationToken);
try
{
return await _s3Client.GetObjectStreamAsync(_bucket, blobName, null, cancellationToken);
}
catch (AmazonS3Exception ex) when (ex.StatusCode is HttpStatusCode.NotFound)
{
return null;
}
}

public async Task<Stream> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
public async Task<Stream?> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
{
return await DownloadAsStreamAsync(blobMetadata.Name, cancellationToken);
}

public async Task<LocalFile> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<LocalFile?> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
{
var localFile = new LocalFile();

using (var stream = await DownloadAsStreamAsync(blobName, cancellationToken))
{
if (stream is null)
{
return null;
}

await stream.CopyToAsync(localFile.FileStream, 81920, cancellationToken);
}

return localFile;
}

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

#region Get

public async Task<BlobMetadata> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<BlobMetadata?> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
{
var objectMetaRequest = new GetObjectMetadataRequest
{
BucketName = _bucket,
Key = blobName
};

var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest);

return new BlobMetadata
try
{
Name = blobName,
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{blobName}"),
ContentType = objectMetaResponse.Headers.ContentType
};
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest, cancellationToken);
return new BlobMetadata
{
Name = blobName,
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{blobName}"),
ContentType = objectMetaResponse.Headers.ContentType
};
}
catch (AmazonS3Exception ex) when (ex.StatusCode is HttpStatusCode.NotFound)
{
return null;
}
}

public async IAsyncEnumerable<BlobMetadata> GetBlobListAsync(
Expand All @@ -170,7 +188,7 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobListAsync(

do
{
var objectsResponse = await _s3Client.ListObjectsAsync(objectsRequest);
var objectsResponse = await _s3Client.ListObjectsAsync(objectsRequest, cancellationToken);

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

var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest);

var objectAclRequest = new GetACLRequest
{
BucketName = _bucket,
Key = entry.Key
};

var objectAclResponse = await _s3Client.GetACLAsync(objectAclRequest);
var isPublic = objectAclResponse.AccessControlList.Grants.Any(x =>
x.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers");
var objectMetaResponse = await _s3Client.GetObjectMetadataAsync(objectMetaRequest, cancellationToken);

yield return new BlobMetadata
{
Name = entry.Key
Name = entry.Key,
Uri = new Uri($"https://s3.amazonaws.com/{_bucket}/{entry.Key}"),
ContentType = objectMetaResponse.Headers.ContentType
};
}

Expand All @@ -215,7 +225,12 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobsAsync(IEnumerable<string> bl
{
foreach (var blob in blobNames)
{
yield return await GetBlobAsync(blob, cancellationToken);
var blobMetadata = await GetBlobAsync(blob, cancellationToken);

if (blobMetadata is not null)
{
yield return blobMetadata;
}
}
}

Expand Down Expand Up @@ -313,6 +328,6 @@ public async Task CreateContainerAsync(CancellationToken cancellationToken = def
{
await _s3Client.EnsureBucketExistsAsync(_bucket);
}

#endregion
}
43 changes: 36 additions & 7 deletions ManagedCode.Storage.Aws/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using ManagedCode.Storage.Aws.Options;
using ManagedCode.Storage.Core;
using ManagedCode.Storage.Core.Exceptions;
using Microsoft.Extensions.DependencyInjection;

namespace ManagedCode.Storage.Aws.Extensions;
Expand All @@ -9,25 +10,53 @@ public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, Action<AWSStorageOptions> action)
{
var awsStorageOptions = new AWSStorageOptions();
action.Invoke(awsStorageOptions);
return serviceCollection.AddAWSStorage(awsStorageOptions);
var options = new AWSStorageOptions();
action.Invoke(options);

CheckConfiguration(options);

return serviceCollection.AddAWSStorage(options);
}

public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, Action<AWSStorageOptions> action)
{
var awsStorageOptions = new AWSStorageOptions();
action.Invoke(awsStorageOptions);
return serviceCollection.AddAWSStorageAsDefault(awsStorageOptions);
var options = new AWSStorageOptions();
action.Invoke(options);

CheckConfiguration(options);

return serviceCollection.AddAWSStorageAsDefault(options);
}

public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, AWSStorageOptions options)
{
CheckConfiguration(options);

return serviceCollection.AddScoped<IAWSStorage>(_ => new AWSStorage(options));
}

public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, AWSStorageOptions options)
{
CheckConfiguration(options);

return serviceCollection.AddScoped<IStorage>(_ => new AWSStorage(options));
}

private static void CheckConfiguration(AWSStorageOptions options)
{
if (string.IsNullOrEmpty(options.PublicKey))
{
throw new BadConfigurationException($"{nameof(options.PublicKey)} cannot be empty");
}

if (string.IsNullOrEmpty(options.SecretKey))
{
throw new BadConfigurationException($"{nameof(options.SecretKey)} cannot be empty");
}

if (string.IsNullOrEmpty(options.Bucket))
{
throw new BadConfigurationException($"{nameof(options.Bucket)} cannot be empty");
}
}
}
6 changes: 3 additions & 3 deletions ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace ManagedCode.Storage.Aws.Options;

public class AWSStorageOptions
{
public string PublicKey { get; set; } = null!;
public string SecretKey { get; set; } = null!;
public string Bucket { get; set; } = null!;
public string? PublicKey { get; set; }
public string? SecretKey { get; set; }
public string? Bucket { get; set; }
public AmazonS3Config? OriginalOptions { get; set; }
}
48 changes: 36 additions & 12 deletions ManagedCode.Storage.Azure/AzureStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class AzureStorage : IAzureStorage
public AzureStorage(AzureStorageOptions options)
{
_options = options;

_blobContainerClient = new BlobContainerClient(
options.ConnectionString,
options.Container,
Expand Down Expand Up @@ -66,30 +67,43 @@ public async Task DeleteAsync(IEnumerable<BlobMetadata> blobNames, CancellationT

#region Download

public async Task<Stream> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<Stream?> DownloadAsStreamAsync(string blobName, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blobName);
var res = await blobClient.DownloadStreamingAsync(cancellationToken: cancellationToken);

return res.Value.Content;
try
{
var response = await blobClient.DownloadAsync(cancellationToken);
return response.Value.Content;
}
catch (RequestFailedException ex) when (ex.ErrorCode == BlobErrorCode.BlobNotFound)
{
return null;
}
}

public async Task<Stream> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
public async Task<Stream?> DownloadAsStreamAsync(BlobMetadata blobMetadata, CancellationToken cancellationToken = default)
{
return await DownloadAsStreamAsync(blobMetadata.Name, cancellationToken);
}

public async Task<LocalFile> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<LocalFile?> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blobName);
var localFile = new LocalFile();

await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);
try
{
var localFile = new LocalFile();
await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);

return localFile;
return localFile;
}
catch (RequestFailedException ex) when (ex.ErrorCode == BlobErrorCode.BlobNotFound)
{
return null;
}
}

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

#region Get

public async Task<BlobMetadata> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
public async Task<BlobMetadata?> GetBlobAsync(string blobName, CancellationToken cancellationToken = default)
{
await Task.Yield();

var blobClient = _blobContainerClient.GetBlobClient(blobName);

if (!await blobClient.ExistsAsync(cancellationToken))
{
return null;
}

return new BlobMetadata
{
Expand All @@ -155,7 +174,12 @@ public async IAsyncEnumerable<BlobMetadata> GetBlobsAsync(IEnumerable<string> bl
{
foreach (var blob in blobNames)
{
yield return await GetBlobAsync(blob, cancellationToken);
var blobMetadata = await GetBlobAsync(blob, cancellationToken);

if (blobMetadata is not null)
{
yield return blobMetadata;
}
}
}

Expand Down
Loading

0 comments on commit f72a025

Please sign in to comment.