diff --git a/src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
similarity index 50%
rename from src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs
rename to src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
index 506b38df2..6026f10b0 100644
--- a/src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs
+++ b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
@@ -11,102 +11,112 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
-using Squidex.Infrastructure.Assets;
-namespace Squidex.Infrastructure.Azure.Storage
+namespace Squidex.Infrastructure.Assets
{
public class AzureBlobAssetStore : IAssetStore, IExternalSystem
{
- private readonly IStorageAccountManager azureStorageAccount;
- private readonly string containerName;
- private CloudBlobContainer blobContainer;
private const string AssetVersion = "AssetVersion";
private const string AssetId = "AssetId";
+ private readonly string containerName;
+ private readonly string connectionString;
+ private CloudBlobContainer blobContainer;
- public AzureBlobAssetStore(IStorageAccountManager azureStorageAccount, string containerName)
+ public AzureBlobAssetStore(string connectionString, string containerName)
{
Guard.NotNullOrEmpty(containerName, nameof(containerName));
- Guard.NotNull(azureStorageAccount, nameof(azureStorageAccount));
+ Guard.NotNullOrEmpty(connectionString, nameof(connectionString));
- this.azureStorageAccount = azureStorageAccount;
+ this.connectionString = connectionString;
this.containerName = containerName;
}
+ public void Connect()
+ {
+ try
+ {
+ var storageAccount = CloudStorageAccount.Parse(connectionString);
+
+ var blobClient = storageAccount.CreateCloudBlobClient();
+ var blobReference = blobClient.GetContainerReference(containerName);
+
+ blobReference.CreateIfNotExistsAsync().Wait();
+
+ blobContainer = blobReference;
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigurationException($"Cannot connect to blob container '{containerName}'.", ex);
+ }
+ }
+
public async Task CopyTemporaryAsync(string name, string id, long version, string suffix)
{
var blobName = GetObjectName(id, version, suffix);
- var blobSource = blobContainer.GetBlockBlobReference(name);
- var targetTemporaryBlob = blobContainer.GetBlobReference(blobName);
+ var blobRef = blobContainer.GetBlobReference(blobName);
+
+ var tempBlob = blobContainer.GetBlockBlobReference(name);
try
{
- await targetTemporaryBlob.StartCopyAsync(blobSource.Uri);
- while (targetTemporaryBlob.CopyState.Status == CopyStatus.Pending)
+ await blobRef.StartCopyAsync(tempBlob.Uri);
+
+ while (blobRef.CopyState.Status == CopyStatus.Pending)
{
- await Task.Delay(500);
- await targetTemporaryBlob.FetchAttributesAsync();
+ await Task.Delay(50);
+ await blobRef.FetchAttributesAsync();
}
- if (targetTemporaryBlob.CopyState.Status != CopyStatus.Success)
- throw new Exception($"Copy of temporary file failed: {targetTemporaryBlob.CopyState.Status}");
+ if (blobRef.CopyState.Status != CopyStatus.Success)
+ {
+ throw new StorageException($"Copy of temporary file failed: {blobRef.CopyState.Status}");
+ }
}
- catch (StorageException ex)
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404)
{
- var requestInformation = ex.RequestInformation;
- if (requestInformation.HttpStatusCode == 404)
- throw new AssetNotFoundException($"Asset {name} not found.", ex);
- throw;
+ throw new AssetNotFoundException($"Asset {name} not found.", ex);
}
}
public async Task DownloadAsync(string id, long version, string suffix, Stream stream)
{
var blobName = GetObjectName(id, version, suffix);
- var blob = blobContainer.GetBlockBlobReference(blobName);
+ var blobRef = blobContainer.GetBlockBlobReference(blobName);
- if (!await blob.ExistsAsync())
- throw new AssetNotFoundException($"Asset {blobName} not found.");
-
- await blob.DownloadToStreamAsync(stream);
- }
-
- public async Task UploadTemporaryAsync(string name, Stream stream)
- {
- var blob = blobContainer.GetBlockBlobReference(name);
- await blob.UploadFromStreamAsync(stream);
+ try
+ {
+ await blobRef.DownloadToStreamAsync(stream);
+ }
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404)
+ {
+ throw new AssetNotFoundException($"Asset {id}, {version} not found.", ex);
+ }
}
public async Task UploadAsync(string id, long version, string suffix, Stream stream)
{
var blobName = GetObjectName(id, version, suffix);
- var blob = blobContainer.GetBlockBlobReference(blobName);
+ var blobRef = blobContainer.GetBlockBlobReference(blobName);
- blob.Metadata[AssetVersion] = version.ToString();
- blob.Metadata[AssetId] = id;
+ blobRef.Metadata[AssetVersion] = version.ToString();
+ blobRef.Metadata[AssetId] = id;
- await blob.UploadFromStreamAsync(stream);
- await blob.SetMetadataAsync();
+ await blobRef.UploadFromStreamAsync(stream);
+ await blobRef.SetMetadataAsync();
}
- public async Task DeleteTemporaryAsync(string name)
+ public async Task UploadTemporaryAsync(string name, Stream stream)
{
- var blob = blobContainer.GetBlockBlobReference(name);
- await blob.DeleteIfExistsAsync();
+ var tempBlob = blobContainer.GetBlockBlobReference(name);
+
+ await tempBlob.UploadFromStreamAsync(stream);
}
- public void Connect()
+ public async Task DeleteTemporaryAsync(string name)
{
- try
- {
- var task = azureStorageAccount.GetContainerAsync(containerName);
- task.Wait();
+ var tempBlob = blobContainer.GetBlockBlobReference(name);
- blobContainer = task.Result;
- }
- catch (Exception)
- {
- throw new ConfigurationException($"Cannot connect to blob container '{containerName}'.");
- }
+ await tempBlob.DeleteIfExistsAsync();
}
private string GetObjectName(string id, long version, string suffix)
diff --git a/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj b/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
index a1aa5eb3a..a2d331b7d 100644
--- a/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
+++ b/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
@@ -1,15 +1,12 @@
-
netstandard1.6
+ Squidex.Infrastructure
-
-
-
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs b/src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs
deleted file mode 100644
index 43240316d..000000000
--- a/src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// ==========================================================================
-// IStorageAccountManager.cs
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex Group
-// All rights reserved.
-// ==========================================================================
-
-using System.Threading.Tasks;
-using Microsoft.WindowsAzure.Storage.Blob;
-
-namespace Squidex.Infrastructure.Azure.Storage
-{
- public interface IStorageAccountManager
- {
- CloudBlobClient CreateCloudBlobClient();
-
- string GetSharedAccessSignature();
-
- Task GetContainerAsync(string name);
- }
-}
diff --git a/src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs b/src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs
deleted file mode 100644
index f09f105b8..000000000
--- a/src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-// ==========================================================================
-// IStorageAccountManager.cs
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex Group
-// All rights reserved.
-// ==========================================================================
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.WindowsAzure.Storage;
-using Microsoft.WindowsAzure.Storage.Blob;
-
-namespace Squidex.Infrastructure.Azure.Storage
-{
- public class StorageAccountManager : IStorageAccountManager
- {
- private readonly CloudStorageAccount storageAccount;
-
- public StorageAccountManager(string storageAccountConnectionString)
- {
- try
- {
- storageAccount = CloudStorageAccount.Parse(storageAccountConnectionString);
- }
- catch (Exception ex)
- when (ex is FormatException || ex is ArgumentException)
- {
- throw new ConfigurationException("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app settings file.");
- }
- }
-
- public CloudBlobClient CreateCloudBlobClient()
- {
- return storageAccount.CreateCloudBlobClient();
- }
-
- public string GetSharedAccessSignature()
- {
- return storageAccount.GetSharedAccessSignature(new SharedAccessAccountPolicy()
- {
- SharedAccessStartTime = DateTimeOffset.UtcNow,
- SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(1),
- Permissions = SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.List
- });
- }
-
- public async Task GetContainerAsync(string name)
- {
- var blobClient = CreateCloudBlobClient();
- var container = blobClient.GetContainerReference(name);
- await container.CreateIfNotExistsAsync();
-
- return container;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
index b6a080044..da0edad51 100644
--- a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
+++ b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
@@ -62,13 +62,9 @@ namespace Squidex.Infrastructure.Assets
{
await storageClient.CopyObjectAsync(bucketName, name, bucketName, objectName);
}
- catch (GoogleApiException ex)
+ catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
{
- if (ex.HttpStatusCode == HttpStatusCode.NotFound)
- {
- throw new AssetNotFoundException($"Asset {name} not found.", ex);
- }
- throw;
+ throw new AssetNotFoundException($"Asset {name} not found.", ex);
}
}
@@ -80,13 +76,9 @@ namespace Squidex.Infrastructure.Assets
{
await storageClient.DownloadObjectAsync(bucketName, objectName, stream);
}
- catch (GoogleApiException ex)
+ catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
{
- if (ex.HttpStatusCode == HttpStatusCode.NotFound)
- {
- throw new AssetNotFoundException($"Asset {id}, {version} not found.", ex);
- }
- throw;
+ throw new AssetNotFoundException($"Asset {id}, {version} not found.", ex);
}
}
@@ -96,12 +88,9 @@ namespace Squidex.Infrastructure.Assets
{
await storageClient.DeleteObjectAsync(bucketName, name);
}
- catch (GoogleApiException ex)
+ catch (GoogleApiException ex) when (ex.HttpStatusCode != HttpStatusCode.NotFound)
{
- if (ex.HttpStatusCode != HttpStatusCode.NotFound)
- {
- throw;
- }
+ throw;
}
}
diff --git a/src/Squidex/Config/Domain/AssetStoreModule.cs b/src/Squidex/Config/Domain/AssetStoreModule.cs
index 789bd8c6d..ca6dad138 100644
--- a/src/Squidex/Config/Domain/AssetStoreModule.cs
+++ b/src/Squidex/Config/Domain/AssetStoreModule.cs
@@ -12,7 +12,6 @@ using Microsoft.Extensions.Configuration;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Log;
-using Squidex.Infrastructure.Azure.Storage;
// ReSharper disable InvertIf
@@ -64,36 +63,30 @@ namespace Squidex.Config.Domain
.As()
.SingleInstance();
}
- else if (string.Equals(assetStoreType, "AzureBlobStorage", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(assetStoreType, "AzureBlob", StringComparison.OrdinalIgnoreCase))
{
- var containerName = Configuration.GetValue("assetStore:azureStorage:containerName");
- // NOTE: here it can be improved - if we use keyvault the secret key won't be in the app settings, rather the app has to run
- // in the same active directory as the keyvault.
- var connectionString = Configuration.GetValue("assetStore:azureStorage:connectionString");
+ var connectionString = Configuration.GetValue("assetStore:azureBlob:connectionString");
- if (string.IsNullOrWhiteSpace(containerName))
+ if (string.IsNullOrWhiteSpace(connectionString))
{
- throw new ConfigurationException("Configure AssetStore AzureStorage container with 'assetStore:azureStorage:containerName'.");
+ throw new ConfigurationException("Configure AssetStore AzureBlob connection string with 'assetStore:azureBlob:connectionString'.");
}
- if (string.IsNullOrWhiteSpace(connectionString))
+ var containerName = Configuration.GetValue("assetStore:azureBlob:containerName");
+
+ if (string.IsNullOrWhiteSpace(containerName))
{
- throw new ConfigurationException(
- "Configure AssetStore AzureStorage connection string with 'assetStore:azureStorage:connectionString'.");
+ throw new ConfigurationException("Configure AssetStore AzureBlob container with 'assetStore:azureBlob:containerName'.");
}
- builder.Register(c => new StorageAccountManager(connectionString))
- .As()
- .SingleInstance();
-
- builder.Register(c => new AzureBlobAssetStore(c.Resolve(), containerName))
+ builder.Register(c => new AzureBlobAssetStore(connectionString, containerName))
.As()
.As()
.SingleInstance();
}
else
{
- throw new ConfigurationException($"Unsupported value '{assetStoreType}' for 'assetStore:type', supported: Folder, GoogleCloud.");
+ throw new ConfigurationException($"Unsupported value '{assetStoreType}' for 'assetStore:type', supported: AzureBlob, Folder, GoogleCloud.");
}
}
}
diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json
index bf7b6da81..674986e38 100644
--- a/src/Squidex/appsettings.json
+++ b/src/Squidex/appsettings.json
@@ -37,9 +37,9 @@
/*
* Define the type of the read store.
*
- * Supported: Folder (local folder), GoogleCloud (hosted in Google Cloud only)
+ * Supported: Folder (local folder), GoogleCloud (hosted in Google Cloud only), AzureBlob.
*/
- "type": "Folder",
+ "type": "AzureBlob",
"folder": {
/*
* The relative or absolute path to the folder to store the assets.
@@ -52,7 +52,7 @@
*/
"bucket": "squidex-assets"
},
- "azureStorage": {
+ "azureBlob": {
/*
* The name of the container in the Azure Blob Storage
*/
@@ -60,7 +60,7 @@
/*
* The connection string to the azure storage service.
*/
- "connectionString": ""
+ "connectionString": "UseDevelopmentStorage=true"
}
},
diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
index 517f80bf0..36d23af38 100644
--- a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
+++ b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
@@ -6,18 +6,13 @@
// All rights reserved.
// ==========================================================================
-using Squidex.Infrastructure.Azure.Storage;
-
namespace Squidex.Infrastructure.Assets
{
public class AzureBlobAssetStoreTests : AssetStoreTests
{
public override AzureBlobAssetStore CreateStore()
{
- var azureStorageAccount =
- new StorageAccountManager("UseDevelopmentStorage=true");
-
- return new AzureBlobAssetStore(azureStorageAccount, "squidex-test-container"); ;
+ return new AzureBlobAssetStore("UseDevelopmentStorage=true", "squidex-test-container");
}
public override void Dispose()