Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update BlobStorage to use Azure.Storage.Blobs #1564

Merged
merged 21 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 94 additions & 62 deletions core/Piranha.Azure.BlobStorage/BlobStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,22 @@
*
*/

using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Auth;
using Microsoft.Azure.Storage.Blob;
using System;
using System.IO;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Piranha.Models;

namespace Piranha.Azure
{
public class BlobStorage : IStorage
public class BlobStorage : IStorage, IStorageSession, IInitializable
{
/// <summary>
/// The private storage account.
/// </summary>
private readonly CloudStorageAccount _storage;

/// <summary>
/// The name of the container to use.
/// </summary>
private readonly string _containerName;

/// <summary>
/// The container url.
/// </summary>
private string _containerUrl;
private readonly BlobContainerClient _blobContainerClient;

/// <summary>
/// How uploaded files should be named to
Expand All @@ -42,16 +34,18 @@ public class BlobStorage : IStorage
/// <summary>
/// Creates a new Blog Storage service from the given credentials.
/// </summary>
/// <param name="credentials">The connection credentials</param>
/// <param name="containerName">The container name</param>
/// <param name="blobContainerUri">
/// A <see cref="Uri"/> referencing the blob service.
/// This is likely to be similar to "https://{account_name}.blob.core.windows.net".
/// </param>
/// <param name="tokenCredential">The connection credentials</param>
/// <param name="naming">How uploaded media files should be named</param>
public BlobStorage(
StorageCredentials credentials,
string containerName = "uploads",
Uri blobContainerUri,
TokenCredential tokenCredential,
BlobStorageNaming naming = BlobStorageNaming.UniqueFileNames)
{
_storage = new CloudStorageAccount(credentials, true);
_containerName = containerName;
_blobContainerClient = new BlobContainerClient(blobContainerUri, tokenCredential);
_naming = naming;
}

Expand All @@ -66,31 +60,13 @@ public BlobStorage(
string containerName = "uploads",
BlobStorageNaming naming = BlobStorageNaming.UniqueFileNames)
{
_storage = CloudStorageAccount.Parse(connectionString);
_containerName = containerName;
_blobContainerClient = new BlobContainerClient(connectionString, containerName);
_naming = naming;
}

/// <summary>
/// Opens a new storage session.
/// </summary>
/// <returns>A new open session</returns>
public async Task<IStorageSession> OpenAsync()
public Task<IStorageSession> OpenAsync()
{
var session = _storage.CreateCloudBlobClient();
var container = session.GetContainerReference(_containerName);

if (!await container.ExistsAsync())
{
await container.CreateAsync();
await container.SetPermissionsAsync(new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
});
}
_containerUrl = container.Uri.AbsoluteUri;

return new BlobStorageSession(this, container, _naming);
return Task.FromResult<IStorageSession>(this);
}

/// <summary>
Expand All @@ -101,18 +77,7 @@ await container.SetPermissionsAsync(new BlobContainerPermissions
/// <returns>The public url</returns>
public string GetPublicUrl(Media media, string id)
{
if (media != null && !string.IsNullOrWhiteSpace(id))
{
if (string.IsNullOrEmpty(_containerUrl))
{
var session = _storage.CreateCloudBlobClient();
var container = session.GetContainerReference(_containerName);

_containerUrl = container.Uri.AbsoluteUri;
}
return $"{ _containerUrl }/{ GetResourceName(media, id, true) }";
}
return null;
return string.IsNullOrWhiteSpace(id) ? default : $"{_blobContainerClient.Uri.AbsoluteUri}/{GetResourceName(media, id, true)}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is no longer checking that media != null, and in GetResourceName() there is an access to media.Id without checking for null values either. But I can fix this after merge.

}

/// <summary>
Expand All @@ -135,14 +100,81 @@ public string GetResourceName(Media media, string filename)
/// <returns>The public url</returns>
public string GetResourceName(Media media, string filename, bool encode)
{
if (_naming == BlobStorageNaming.UniqueFileNames)
{
return $"{ media.Id }-{ (encode ? System.Web.HttpUtility.UrlPathEncode(filename) : filename) }";
}
else
{
return $"{ media.Id }/{ (encode ? System.Web.HttpUtility.UrlPathEncode(filename) : filename) }";
}
return _naming == BlobStorageNaming.UniqueFileNames ? $"{media.Id}-{(encode ? System.Web.HttpUtility.UrlPathEncode(filename) : filename)}" : $"{media.Id}/{(encode ? System.Web.HttpUtility.UrlPathEncode(filename) : filename)}";
}

/// <summary>
/// Writes the content for the specified media content to the given stream.
/// </summary>
/// <param name="media">The media file</param>
/// <param name="filename">The file name</param>
/// <param name="stream">The output stream</param>
/// <returns>If the media was found</returns>
public async Task<bool> GetAsync(Media media, string filename, Stream stream)
{
var blob = _blobContainerClient.GetBlobClient(GetResourceName(media, filename));

return await blob.ExistsAsync() && (await blob.DownloadToAsync(stream)).Status.IsSuccessStatusCode();
}

/// <summary>
/// Stores the given media content.
/// </summary>
/// <param name="media">The media file</param>
/// <param name="filename">The file name</param>
/// <param name="contentType">The content type</param>
/// <param name="stream">The input stream</param>
/// <returns>The public URL</returns>
public async Task<string> PutAsync(Media media, string filename, string contentType, Stream stream)
{
var blob = _blobContainerClient.GetBlobClient(GetResourceName(media, filename));

var blobHttpHeader = new BlobHttpHeaders {ContentType = contentType};

await blob.UploadAsync(stream, blobHttpHeader);

return blob.Uri.AbsoluteUri;
}

/// <summary>
/// Stores the given media content.
/// </summary>
/// <param name="media">The media file</param>
/// <param name="filename">The file name</param>
/// <param name="contentType">The content type</param>
/// <param name="bytes">The binary data</param>
/// <returns>The public URL</returns>
public async Task<string> PutAsync(Media media, string filename, string contentType, byte[] bytes)
{
return await PutAsync(media, filename, contentType, new MemoryStream(bytes));
}

/// <summary>
/// Deletes the content for the specified media.
/// </summary>
/// <param name="media">The media file</param>
/// <param name="filename">The file name</param>
public async Task<bool> DeleteAsync(Media media, string filename)
{
var blob = _blobContainerClient.GetBlobClient(GetResourceName(media, filename));

return await blob.DeleteIfExistsAsync();
}

/// <summary>
/// Initialize the Blob Storage service by ensuring that the Blob Container exists.
/// </summary>
public void Init()
{
_blobContainerClient.CreateIfNotExists(PublicAccessType.Blob);
}

/// <summary>
/// Disposes the session.
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}
35 changes: 23 additions & 12 deletions core/Piranha.Azure.BlobStorage/BlobStorageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
*
*/

using System;
using Azure.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Storage.Auth;
using Piranha;
using Piranha.Azure;

Expand All @@ -19,23 +20,28 @@ public static class BlobStorageExtensions
/// Adds the Azure BlobStorage module.
/// </summary>
/// <param name="serviceBuilder">The service builder</param>
/// <param name="credentials">The auth credentials</param>
/// <param name="containerName">The optional container name</param>
/// <param name="credential">
/// The token credential used to sign requests.
/// </param>
/// <param name="blobContainerUri">
/// A <see cref="Uri"/> referencing the blob service.
/// This is likely to be similar to "https://{account_name}.blob.core.windows.net".
/// </param>
/// <param name="naming">How uploaded media files should be named</param>
/// <param name="scope">The optional service scope. Default is singleton</param>
/// <returns>The service collection</returns>
public static PiranhaServiceBuilder UseBlobStorage(
this PiranhaServiceBuilder serviceBuilder,
StorageCredentials credentials,
string containerName = "uploads",
Uri blobContainerUri,
TokenCredential credential,
BlobStorageNaming naming = BlobStorageNaming.UniqueFileNames,
ServiceLifetime scope = ServiceLifetime.Singleton)
{
serviceBuilder.Services.AddPiranhaBlobStorage(credentials, containerName, naming, scope);
serviceBuilder.Services.AddPiranhaBlobStorage(blobContainerUri, credential, naming, scope);

return serviceBuilder;
}

/// <summary>
/// Adds the Azure BlobStorage module.
/// </summary>
Expand All @@ -61,22 +67,27 @@ public static PiranhaServiceBuilder UseBlobStorage(
/// Adds the services for the Azure BlobStorage service.
/// </summary>
/// <param name="services">The current service collection</param>
/// <param name="credentials">The auth credentials</param>
/// <param name="containerName">The optional container name</param>
/// <param name="credential">
/// The token credential used to sign requests.
/// </param>
/// <param name="blobContainerUri">
/// A <see cref="Uri"/> referencing the blob service.
/// This is likely to be similar to "https://{account_name}.blob.core.windows.net".
/// </param>
/// <param name="naming">How uploaded media files should be named</param>
/// <param name="scope">The optional service scope. Default is singleton</param>
/// <returns>The service collection</returns>
public static IServiceCollection AddPiranhaBlobStorage(
this IServiceCollection services,
StorageCredentials credentials,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constitutes the main breaking change when moving to the v.12 Azure SDK, as the StorageCredentials class is no longer available.

string containerName = "uploads",
Uri blobContainerUri,
TokenCredential credential,
BlobStorageNaming naming = BlobStorageNaming.UniqueFileNames,
ServiceLifetime scope = ServiceLifetime.Singleton)
{
App.Modules.Register<BlobStorageModule>();

services.Add(new ServiceDescriptor(typeof(IStorage), sp =>
new BlobStorage(credentials, containerName, naming), scope));
new BlobStorage(blobContainerUri, credential, naming), scope));

return services;
}
Expand Down
Loading