Netcore 2.1 Распределенный кэш с хранилищем Scoped - PullRequest
0 голосов
/ 18 января 2019

Нужна помощь.

У меня есть API .netcore 2.1, который защищен с помощью токена Azure Bearer от его клиентов. Я хочу собрать пользовательскую информацию из токена на предъявителя клиентов и сохранить ее в базе данных SQL, чтобы я мог пометить записи в базе данных, если они добавляются / удаляются / редактируются и т. Д. Поэтому для присоединения таблицы SQL мне нужен пользователь информация в SQL.

Ниже приведена моя реализация службы кэширования с использованием IDistributedCache. При инициализации я пытаюсь загрузить всех текущих сохраненных пользователей из базы данных SQL в кэш, а затем добавлен в него при подключении новых пользователей.

Для захвата соединений по всему API я использовал TypeFilterAttribute для выполнения OnActionExecuting.

Проблема заключается в том, что CacheService является одноэлементным и вызывает UserRepository, который находится в области видимости. Это не разрешено.

Есть мысли?

startup.cs

public void ConfigureServices(IServiceCollection services)
        {
...
            // Context
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<CacheService>();

            // Repositories
            services.TryAddScoped<IUserRepository, UserRepository>();                    

            services.AddDistributedMemoryCache();

            services.AddMvc(
               opts => opts.Filters.Add(new HttpInterceptor())
                )
...

CacheService.cs

public class CacheService
    {
        private readonly IDistributedCache _cache;
        private readonly IUserRepository _userRepository;

        public CacheService(
            IDistributedCache cache,
          [FromServices]  IUserRepository userRepository
            )
        {
            _cache = cache;
            _userRepository = userRepository;

            // Populate cache from DB
            var users = _userRepository.GetAll().Result;
            foreach (var u in users)
            {
                if (_cache.GetAsync(u.Username).Result == null)
                {
                    var profileSerialised = JsonConvert.SerializeObject(UserToUserProfile(u));
                    var entry = Encoding.UTF8.GetBytes(profileSerialised);
                    _cache.SetAsync(u.Username, entry, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
                }
            }
        }

HttpInterceptor.cs

 public class HttpInterceptor : TypeFilterAttribute
    {
        public HttpInterceptor() : base(typeof(IHttpInterceptor))
        {

        }

        private class IHttpInterceptor : IActionFilter
        {
            private readonly CacheService _cache;
            private readonly IUserRepository _userRepository;

            public IHttpInterceptor(
                CacheService cache,
                IUserRepository userRepository)
            {
                _cache = cache;
                _userRepository = userRepository;
            }


            public void OnActionExecuting(ActionExecutingContext context)
            {
                if (context.HttpContext.User.Identity.IsAuthenticated)
                {
                  this._cache.GetUserProfile(context.HttpContext.User.Identity.Name);
                }
            }

1 Ответ

0 голосов
/ 18 января 2019

Во-первых, вы смотрите на это с ног на голову и назад. Наличие некоторого сервиса, добавляющего вещи в кеш, а затем имеющего другой код просто , предполагает , что вещи в кеше готовы к работе, - это путь к катастрофе. Вместо этого пусть ваш зависимый код буквально запрашивает необходимые данные, а затем, если вы хотите его кэшировать, сделайте это методом, который извлекает данные. Таким образом, код вашего приложения не зависит от того, откуда поступают данные; он просто вызывает метод и получает нужные данные. Под капотом он либо извлекается из базы данных, либо из кэша, в зависимости от того, какой из них доступен / предпочтителен.

В любом случае, ваша служба кэширования имеет серьезные проблемы. Во-первых, это не должен быть синглтон, во-первых. Нет причин для этого, и, поскольку вы имеете дело с внутренними службами, вы только усложняете вещи, которые им необходимы. Во-вторых, вы никогда не должны использовать ввод / вывод в конструкторе. Там должна быть сделана только простая инициализация переменной / проп. Все, что требует реальной работы, должно входить в метод. Если вы действительно хотите что-то сделать при инициализации, вам следует реализовать фабричный шаблон. Например, у вас может быть что-то вроде CacheServiceFactory с методом Create, который возвращает полностью экземплярный CacheService, включая вызов любых методов, выполняющих реальную работу.

Как правило, если не принимать во внимание заявления об отказе от ответственности, чтобы использовать сервис с областью действия в одиночном коде, вы должны создать область действия. Это должно быть сделано каждый раз, когда вы хотите использовать сервис; вы не можете сохранить службу ивару в своем синглтон-классе. Просто вы вводите IServiceProvider в свой класс, который сам по себе имеет одинарную область видимости, поэтому у вас не возникнет проблем с этим. Затем, когда вам нужно использовать сервис с определенной областью:

using (var scope = provider.CreateScope())
{
    var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
    // do something with repo
}

Это называется анти-паттерном поиска сервисов. Это называется так, потому что это то, что вы действительно должны избегать. Иногда это не всегда возможно. Однако чаще всего вы можете просто разрабатывать вещи по-другому: например, чтобы сама служба работала с областью действия.

...