Я реализовал AWS DynamoDB для распределенного состояния сеанса ASP.NET Core, включая хранение ключей шифрования сеансового cookie в DynamoDB (вы должны хранить ключи где-нибудь, чтобы разные экземпляры вашего приложения могли декодировать cookie-файлы друг друга).
Обратите внимание, что это «пустая» реализация, я еще не сделал ее тестируемой, не использую DI и т. Д. Два класса - DynamoDbCache и DdbXmlRepository.
DynamoDBCache.cs:
using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Caching.DynamoDb
{
public class DynamoDbCache : IDistributedCache
{
private static IAmazonDynamoDB _client;
private static Table _table;
private string _tableName = "ASP.NET_SessionState";
private string _ttlfield = "TTL";
private int _sessionMinutes = 20;
private enum ExpiryType
{
Sliding,
Absolute
}
public DynamoDbCache(IOptions<DynamoDbCacheOptions> optionsAccessor, IAmazonDynamoDB dynamoDb)
{
_client = dynamoDb;
if (optionsAccessor != null)
{
_tableName = optionsAccessor.Value.TableName;
_ttlfield = optionsAccessor.Value.TtlAttribute;
_sessionMinutes = (int)optionsAccessor.Value.IdleTimeout.TotalMinutes;
}
if (_client == null)
{
_client = new AmazonDynamoDBClient();
}
if (_table == null)
{
_table = Table.LoadTable(_client, _tableName);
}
}
public byte[] Get(string key)
{
return GetAsync(key).Result;
}
public async Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
{
var value = await _table.GetItemAsync(key);
if (value == null || value["Session"] == null)
{
return null;
}
return value["Session"].AsByteArray();
}
public void Refresh(string key)
{
var value = _table.GetItemAsync(key).Result;
if (value == null || value["ExpiryType"] == null || value["ExpiryType"] != "Sliding")
{
return;
}
value[_ttlfield] = DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (_sessionMinutes * 60);
Task.Run(() => Set(key, value["Session"].AsByteArray(), new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(0, _sessionMinutes, 0) }));
}
public async Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
{
var value = _table.GetItemAsync(key).Result;
if (value == null || value["ExpiryType"] == null || value["ExpiryType"] != "Sliding")
{
return;
}
value[_ttlfield] = DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (_sessionMinutes * 60);
await SetAsync(key, value["Session"].AsByteArray(), new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(0, _sessionMinutes, 0) });
}
public void Remove(string key)
{
_table.DeleteItemAsync(key).Wait();
}
public async Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
{
await _table.DeleteItemAsync(key);
}
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
SetAsync(key, value, options).Wait();
}
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
{
ExpiryType expiryType;
var epoctime = GetEpochExpiry(options, out expiryType);
var _ssdoc = new Document();
_ssdoc.Add("SessionId", key);
_ssdoc.Add("Session", value);
_ssdoc.Add("CreateDate", DateTime.Now.ToUniversalTime().ToString("o"));
_ssdoc.Add("ExpiryType", expiryType.ToString());
_ssdoc.Add(_ttlfield, epoctime);
await _table.PutItemAsync(_ssdoc);
}
private long GetEpochExpiry(DistributedCacheEntryOptions options, out ExpiryType expiryType)
{
if (options.SlidingExpiration.HasValue)
{
expiryType = ExpiryType.Sliding;
return DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (long)options.SlidingExpiration.Value.TotalSeconds;
}
else if (options.AbsoluteExpiration.HasValue)
{
expiryType = ExpiryType.Absolute;
return options.AbsoluteExpiration.Value.ToUnixTimeSeconds();
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
expiryType = ExpiryType.Absolute;
return DateTimeOffset.Now.Add(options.AbsoluteExpirationRelativeToNow.Value).ToUniversalTime().ToUnixTimeSeconds();
}
else
{
throw new Exception("Cache expiry option must be set to Sliding, Absolute or Absolute relative to now");
}
}
}
}
и DdbXmlRepository:
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Microsoft.AspNetCore.DataProtection.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Website.Session
{
public class DdbXmlRepository : IXmlRepository
{
private static IAmazonDynamoDB _dynamoDb;
public DdbXmlRepository(IAmazonDynamoDB dynamoDb)
{
_dynamoDb = dynamoDb;
}
public IReadOnlyCollection<XElement> GetAllElements()
{
var context = new DynamoDBContext(_dynamoDb);
var search = context.ScanAsync<XmlKey>(new List<ScanCondition>());
var results = search.GetRemainingAsync().Result;
return results.Select(x => XElement.Parse(x.Xml)).ToList();
}
public void StoreElement(XElement element, string friendlyName)
{
var key = new XmlKey
{
Xml = element.ToString(SaveOptions.DisableFormatting),
FriendlyName = friendlyName
};
var context = new DynamoDBContext(_dynamoDb);
context.SaveAsync(key).Wait();
}
}
[DynamoDBTable("AspXmlKeys")]
public class XmlKey
{
[DynamoDBHashKey]
public string KeyId { get; set; } = Guid.NewGuid().ToString();
public string Xml { get; set; }
public string FriendlyName { get; set; }
}
}
Вам также необходимо расширение набора служб:
using System;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.DynamoDb;
namespace Microsoft.Extensions.DependencyInjection
{
public static class DynamoDbCacheServiceCollectionExtensions
{
/// <summary>
/// Adds Amazon DynamoDB caching services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="setupAction">An <see cref="Action{DynamoDbCacheOptions}"/> to configure the provided
/// <see cref="DynamoDbCacheOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddDistributedDynamoDbCache(this IServiceCollection services, Action<DynamoDbCacheOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
services.AddOptions();
services.Configure(setupAction);
services.Add(ServiceDescriptor.Singleton<IDistributedCache, DynamoDbCache>());
return services;
}
}
}
И, наконец, я использовал класс параметров для настройки вещейнапример, имя таблицы, срок действия по умолчанию и т. д .:
using Microsoft.Extensions.Options;
using System;
namespace Microsoft.Extensions.Caching.DynamoDb
{
public class DynamoDbCacheOptions : IOptions<DynamoDbCacheOptions>
{
public string TableName { get; set; } = "ASP.NET_SessionState";
public TimeSpan IdleTimeout { get; set; } = new TimeSpan(0, 20, 0);
public string TtlAttribute { get; set; } = "TTL";
DynamoDbCacheOptions IOptions<DynamoDbCacheOptions>.Value
{
get { return this; }
}
}
}
При запуске вам необходимо подключить все это в ConfigureServices с помощью следующего кода:
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonDynamoDB>();
services.AddSingleton<IXmlRepository, DdbXmlRepository>();
services.AddDistributedDynamoDbCache(o => {
o.TableName = "TechSummitSessionState";
o.IdleTimeout = TimeSpan.FromMinutes(30);
});
services.AddSession(o => {
o.IdleTimeout = TimeSpan.FromMinutes(30);
o.Cookie.HttpOnly = false;
});
services.AddDataProtection()
.AddKeyManagementOptions(o => o.XmlRepository = sp.GetService<IXmlRepository>());
Надеюсь, это поможет!Это работает для моего приложения: -)