-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Azure Table storage and ComosDB table provider (#5)
* v1 * mend * fix sqlprovider issues * update build script * update build for signing * update build * update CosmosDBTable nupkg version * add iconUrl in nupkg * fix deadlock issue and format code * update module version * update package ref for table provider
- Loading branch information
Showing
41 changed files
with
1,397 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
.vs/ | ||
msbuild.* | ||
packages/ | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See the License.txt file in the project root for full license information. | ||
|
||
namespace Microsoft.AspNet.OutputCache.CosmosDBTableAsyncOutputCacheProvider { | ||
using Microsoft.Azure.CosmosDB.Table; | ||
using Microsoft.Azure.Storage; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Runtime.Serialization.Formatters.Binary; | ||
using System.Text; | ||
|
||
class CacheEntity : TableEntity { | ||
private static readonly char[] InvalidCharsInResource = { '/', '\\', '?', '#' }; | ||
private const char ReplacementOfInvalidChars = '_'; | ||
|
||
// This is required by TableQuery | ||
public CacheEntity() { } | ||
|
||
public CacheEntity(string cacheKey, object cacheItem, DateTime utcExpiry) { | ||
RowKey = SanitizeKey(cacheKey); | ||
PartitionKey = GeneratePartitionKey(cacheKey); | ||
CacheItem = cacheItem; | ||
UtcExpiry = utcExpiry; | ||
} | ||
|
||
public object CacheItem { get; set; } | ||
|
||
public DateTime UtcExpiry { get; set; } | ||
|
||
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext) { | ||
base.ReadEntity(properties, operationContext); | ||
CacheItem = Deserialize(properties[nameof(CacheItem)].BinaryValue); | ||
} | ||
|
||
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext) { | ||
var result = base.WriteEntity(operationContext); | ||
var cacheItemProperty = new EntityProperty(Serialize(CacheItem)); | ||
result.Add(nameof(CacheItem), cacheItemProperty); | ||
return result; | ||
} | ||
|
||
public static string GeneratePartitionKey(string cacheKey) { | ||
return (cacheKey.Length % 10).ToString(); | ||
} | ||
|
||
public static string SanitizeKey(string cacheKey) { | ||
// some chars are not allowed in rowkey | ||
// https://docs.microsoft.com/en-us/rest/api/storageservices/Understanding-the-Table-Service-Data-Model | ||
var sbKey = new StringBuilder(cacheKey); | ||
|
||
foreach (var c in InvalidCharsInResource) { | ||
sbKey.Replace(c, ReplacementOfInvalidChars); | ||
} | ||
return sbKey.ToString(); | ||
} | ||
|
||
private static byte[] Serialize(object data) { | ||
if (data == null) { | ||
data = new object(); | ||
} | ||
|
||
using (var memoryStream = new MemoryStream()) { | ||
var binaryFormatter = new BinaryFormatter(); | ||
binaryFormatter.Serialize(memoryStream, data); | ||
return memoryStream.ToArray(); | ||
} | ||
} | ||
|
||
private static object Deserialize(byte[] data) { | ||
if (data == null) { | ||
return null; | ||
} | ||
|
||
using (var memoryStream = new MemoryStream(data, 0, data.Length)) { | ||
var binaryFormatter = new BinaryFormatter(); | ||
return binaryFormatter.Deserialize(memoryStream); | ||
} | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
src/CosmosDBTableAsyncOutputCacheProvider/CosmosDBTableAsyncOutputCacheProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See the License.txt file in the project root for full license information. | ||
|
||
namespace Microsoft.AspNet.OutputCache.CosmosDBTableAsyncOutputCacheProvider { | ||
using System; | ||
using System.Collections.Specialized; | ||
using System.Threading.Tasks; | ||
using System.Web.Caching; | ||
using System.Web.Configuration; | ||
|
||
/// <summary> | ||
/// Async CosmosDB table OutputCache provider | ||
/// </summary> | ||
public class CosmosDBTableAsyncOutputCacheProvider : OutputCacheProviderAsync { | ||
private ITableOutputCacheRepository _tableRepo; | ||
|
||
/// <inheritdoc /> | ||
public override void Initialize(string name, NameValueCollection config) { | ||
Initialize(name, config, new CosmosDBTableOutputCacheRepository(config, WebConfigurationManager.AppSettings)); | ||
} | ||
|
||
internal void Initialize(string name, NameValueCollection providerConfig, ITableOutputCacheRepository _repo) { | ||
_tableRepo = _repo; | ||
base.Initialize(name, providerConfig); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override object Add(string key, object entry, DateTime utcExpiry) { | ||
return _tableRepo.Add(key, entry, utcExpiry); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task<object> AddAsync(string key, object entry, DateTime utcExpiry) { | ||
return _tableRepo.AddAsync(key, entry, utcExpiry); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override object Get(string key) { | ||
return _tableRepo.Get(key); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task<object> GetAsync(string key) { | ||
return _tableRepo.GetAsync(key); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Remove(string key) { | ||
_tableRepo.Remove(key); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task RemoveAsync(string key) { | ||
return _tableRepo.RemoveAsync(key); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Set(string key, object entry, DateTime utcExpiry) { | ||
_tableRepo.Set(key, entry, utcExpiry); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task SetAsync(string key, object entry, DateTime utcExpiry) { | ||
return _tableRepo.SetAsync(key, entry, utcExpiry); | ||
} | ||
} | ||
} |
155 changes: 155 additions & 0 deletions
155
src/CosmosDBTableAsyncOutputCacheProvider/CosmosDBTableOutputCacheRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See the License.txt file in the project root for full license information. | ||
|
||
namespace Microsoft.AspNet.OutputCache.CosmosDBTableAsyncOutputCacheProvider { | ||
using System; | ||
using System.Collections.Specialized; | ||
using System.Configuration; | ||
using System.Threading.Tasks; | ||
using System.Web; | ||
using Microsoft.Azure.CosmosDB.Table; | ||
using Microsoft.Azure.Storage; | ||
using Resource; | ||
|
||
class CosmosDBTableOutputCacheRepository : ITableOutputCacheRepository { | ||
private const string TableNameKey = "tableName"; | ||
private const string ConnectionStringKey = "connectionStringName"; | ||
private const string FixedPartitionKey = "P"; | ||
|
||
private CloudTable _table; | ||
private string _connectionString; | ||
private string _tableName; | ||
private object _lock = new object(); | ||
|
||
public CosmosDBTableOutputCacheRepository(NameValueCollection providerConfig, NameValueCollection appSettings) { | ||
var connectionStringName = providerConfig[ConnectionStringKey]; | ||
if (string.IsNullOrEmpty(connectionStringName)) { | ||
throw new ConfigurationErrorsException(SR.Cant_find_connectionStringName); | ||
} | ||
|
||
_connectionString = appSettings[connectionStringName]; | ||
if (string.IsNullOrEmpty(_connectionString)) { | ||
throw new ConfigurationErrorsException(string.Format(SR.Cant_find_connectionString, connectionStringName)); | ||
} | ||
|
||
_tableName = providerConfig[TableNameKey]; | ||
if (string.IsNullOrEmpty(_tableName)) { | ||
throw new ConfigurationErrorsException(SR.TableName_cant_be_empty); | ||
} | ||
} | ||
|
||
public object Add(string key, object entry, DateTime utcExpiry) { | ||
var retrieveOp = TableOperationHelper.Retrieve(key); | ||
var retrieveResult = _table.Execute(retrieveOp); | ||
var existingCacheEntry = retrieveResult.Result as CacheEntity; | ||
|
||
if (existingCacheEntry != null && existingCacheEntry.UtcExpiry > DateTime.UtcNow) { | ||
return existingCacheEntry.CacheItem; | ||
} else { | ||
Set(key, entry, utcExpiry); | ||
return entry; | ||
} | ||
} | ||
|
||
public async Task<object> AddAsync(string key, object entry, DateTime utcExpiry) { | ||
// If there is already a value in the cache for the specified key, the provider must return that value if not expired | ||
// and must not store the data passed by using the Add method parameters. | ||
var retrieveOp = TableOperationHelper.Retrieve(key); | ||
var retrieveResult = await _table.ExecuteAsync(retrieveOp); | ||
var existingCacheEntry = retrieveResult.Result as CacheEntity; | ||
|
||
if (existingCacheEntry != null && existingCacheEntry.UtcExpiry > DateTime.UtcNow) { | ||
return existingCacheEntry.CacheItem; | ||
} else { | ||
await SetAsync(key, entry, utcExpiry); | ||
return entry; | ||
} | ||
} | ||
|
||
public object Get(string key) { | ||
var retrieveOp = TableOperationHelper.Retrieve(key); | ||
var retrieveResult = _table.Execute(retrieveOp); | ||
var existingCacheEntry = retrieveResult.Result as CacheEntity; | ||
|
||
if (existingCacheEntry != null && existingCacheEntry.UtcExpiry < DateTime.UtcNow) { | ||
Remove(key); | ||
return null; | ||
} else { | ||
return existingCacheEntry?.CacheItem; | ||
} | ||
} | ||
|
||
public async Task<object> GetAsync(string key) { | ||
// Outputcache module will always first call GetAsync | ||
// so only calling EnsureTableInitializedAsync here is good enough | ||
await EnsureTableInitializedAsync(); | ||
|
||
var retrieveOp = TableOperationHelper.Retrieve(key); | ||
var retrieveResult = await _table.ExecuteAsync(retrieveOp); | ||
var existingCacheEntry = retrieveResult.Result as CacheEntity; | ||
|
||
if (existingCacheEntry != null && existingCacheEntry.UtcExpiry < DateTime.UtcNow) { | ||
await RemoveAsync(key); | ||
return null; | ||
} else { | ||
return existingCacheEntry?.CacheItem; | ||
} | ||
} | ||
|
||
public void Remove(string key) { | ||
var removeOp = TableOperationHelper.Delete(key); | ||
_table.Execute(removeOp); | ||
} | ||
|
||
public async Task RemoveAsync(string key) { | ||
var removeOp = TableOperationHelper.Delete(key); | ||
await _table.ExecuteAsync(removeOp); | ||
} | ||
|
||
public void Set(string key, object entry, DateTime utcExpiry) { | ||
var insertOp = TableOperationHelper.InsertOrReplace(key, entry, utcExpiry); | ||
_table.Execute(insertOp); | ||
} | ||
|
||
public async Task SetAsync(string key, object entry, DateTime utcExpiry) { | ||
//Check if the key is already in database | ||
//If there is already a value in the cache for the specified key, the Set method will update it. | ||
//Otherwise it will insert the entry. | ||
var insertOp = TableOperationHelper.InsertOrReplace(key, entry, utcExpiry); | ||
await _table.ExecuteAsync(insertOp); | ||
} | ||
|
||
private async Task EnsureTableInitializedAsync() { | ||
if (_table != null) { | ||
return; | ||
} | ||
|
||
try { | ||
lock (_lock) { | ||
if (_table != null) { | ||
return; | ||
} | ||
|
||
var storageAccount = CreateStorageAccount(); | ||
var tableClient = storageAccount.CreateCloudTableClient(); | ||
_table = tableClient.GetTableReference(_tableName); | ||
} | ||
|
||
// The sync version API causes deadlock when using CosmosDB table. | ||
await _table.CreateIfNotExistsAsync(); | ||
} catch (StorageException ex) { | ||
throw new HttpException(SR.Fail_to_create_table, ex); | ||
} | ||
} | ||
|
||
private CloudStorageAccount CreateStorageAccount() { | ||
try { | ||
return CloudStorageAccount.Parse(_connectionString); | ||
} catch (FormatException) { | ||
throw new HttpException(SR.Invalid_storage_account_information); | ||
} catch (ArgumentException) { | ||
throw new HttpException(SR.Invalid_storage_account_information); | ||
} | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/CosmosDBTableAsyncOutputCacheProvider/ITableOutputCacheRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See the License.txt file in the project root for full license information. | ||
|
||
namespace Microsoft.AspNet.OutputCache.CosmosDBTableAsyncOutputCacheProvider { | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
interface ITableOutputCacheRepository { | ||
Task<object> AddAsync(string key, object entry, DateTime utcExpiry); | ||
|
||
Task<object> GetAsync(string key); | ||
|
||
Task RemoveAsync(string key); | ||
|
||
Task SetAsync(string key, object entry, DateTime utcExpiry); | ||
|
||
object Add(string key, object entry, DateTime utcExpiry); | ||
|
||
object Get(string key); | ||
|
||
void Remove(string key); | ||
|
||
void Set(string key, object entry, DateTime utcExpiry); | ||
} | ||
} |
Oops, something went wrong.