Распределенное управление сеансами ASP.NET CORE 2.0 с помощью AWS DynamoDB - PullRequest
0 голосов
/ 05 октября 2018

Я ожидаю реализации распределенного управления сеансами в ASP.NET CORE 2.0 с помощью Amazon Dynamodb.Но не удалось найти какую-либо документацию или пример исходного кода.

Как реализовать распределенное управление сеансами с ASP.NET CORE 2.0 и DynamoDb?

1 Ответ

0 голосов
/ 11 октября 2018

Я реализовал 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>());

Надеюсь, это поможет!Это работает для моего приложения: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...