diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs index 47a1614..8d02273 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs @@ -3,11 +3,15 @@ using Microsoft.WindowsAzure.Storage.Blob; using System; using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading.Tasks; public class AzureFileProperties : IFileProperties { private const string DefaultCacheControl = "max-age=300, must-revalidate"; private readonly ICloudBlob cloudBlob; + private readonly Dictionary decodedMetadata; public AzureFileProperties(ICloudBlob cloudBlob) { @@ -16,6 +20,15 @@ public AzureFileProperties(ICloudBlob cloudBlob) { this.cloudBlob.Properties.CacheControl = DefaultCacheControl; } + + if (this.cloudBlob.Metadata != null) + { + decodedMetadata = this.cloudBlob.Metadata.ToDictionary(m => m.Key, m => WebUtility.HtmlDecode(m.Value)); + } + else + { + decodedMetadata = new Dictionary(); + } } public DateTimeOffset? LastModified => this.cloudBlob.Properties.LastModified; @@ -36,6 +49,18 @@ public string CacheControl set { this.cloudBlob.Properties.CacheControl = value; } } - public IDictionary Metadata => this.cloudBlob.Metadata; + public IDictionary Metadata => this.decodedMetadata; + + internal async Task SaveAsync() + { + await this.cloudBlob.SetPropertiesAsync(); + + foreach (var meta in this.decodedMetadata) + { + this.cloudBlob.Metadata[meta.Key] = WebUtility.HtmlEncode(meta.Value); + } + + await this.cloudBlob.SetMetadataAsync(); + } } } diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index 65f6024..9a0bf70 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -8,13 +8,13 @@ public class AzureFileReference : IFileReference { - private Lazy propertiesLazy; + private Lazy propertiesLazy; public AzureFileReference(string path, ICloudBlob cloudBlob, bool withMetadata) { this.Path = path; this.CloudBlob = cloudBlob; - this.propertiesLazy = new Lazy(() => + this.propertiesLazy = new Lazy(() => { if (withMetadata && cloudBlob.Metadata != null && cloudBlob.Properties != null) { @@ -84,10 +84,9 @@ public async Task ReadAllBytesAsync() return (await this.ReadInMemoryAsync()).ToArray(); } - public async Task SavePropertiesAsync() + public Task SavePropertiesAsync() { - await this.CloudBlob.SetPropertiesAsync(); - await this.CloudBlob.SetMetadataAsync(); + return this.propertiesLazy.Value.SaveAsync(); } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 50958cb..c82e0dd 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -131,6 +131,30 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } + [Theory(DisplayName = nameof(SaveEncodedMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] + public async Task SaveEncodedMetatadaRoundtrip(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var testFile = "Metadata/TextFile.txt"; + + var file = await store.GetAsync(testFile, withMetadata: true); + + var name = "ï"; + + file.Properties.Metadata["name"] = name; + + await file.SavePropertiesAsync(); + + file = await store.GetAsync(testFile, withMetadata: true); + + var actualName = file.Properties.Metadata["name"]; + + Assert.Equal(name, actualName); + } + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListMetatadaRoundtrip(string storeName) {