From f8a251afb9a9549be185279bc5ecad4c100b2467 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 29 Jun 2020 16:20:24 +0800 Subject: [PATCH 01/38] Storage cache in the unit of work. Resolve #4040 --- .../Volo/Abp/Caching/AbpCachingModule.cs | 2 + .../Volo/Abp/Caching/DistributedCache.cs | 546 ++++++++++++++---- .../Volo/Abp/Caching/IDistributedCache.cs | 34 +- .../Volo/Abp/Caching/UnitOfWorkCacheItem.cs | 43 ++ .../Caching/UnitOfWorkCacheItemExtensions.cs | 11 + .../Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs | 4 +- .../Volo/Abp/Uow/UnitOfWorkExtensions.cs | 45 +- .../Abp/Caching/DistributedCache_Tests.cs | 244 +++++++- .../FeatureManagementStore.cs | 29 +- .../FeatureManagementStore_Tests.cs | 51 ++ .../SettingManagementStore.cs | 31 +- .../SettingManagementStore_Tests.cs | 34 ++ 12 files changed, 924 insertions(+), 150 deletions(-) create mode 100644 framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs create mode 100644 framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index ba59bf039e..d133ac110b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -5,12 +5,14 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { [DependsOn( typeof(AbpThreadingModule), typeof(AbpSerializationModule), + typeof(AbpUnitOfWorkModule), typeof(AbpMultiTenancyModule), typeof(AbpJsonModule))] public class AbpCachingModule : AbpModule diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 5bb1dd2481..28082832b0 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -14,6 +14,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { @@ -30,13 +31,15 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) : base( + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory, + unitOfWorkManager:unitOfWorkManager) { } } @@ -50,6 +53,8 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { + public const string DistributedCacheName = "AbpDistributedCache"; + public ILogger> Logger { get; set; } protected string CacheName { get; set; } @@ -66,6 +71,8 @@ namespace Volo.Abp.Caching protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; @@ -78,7 +85,8 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) { _distributedCacheOption = distributedCacheOption.Value; Cache = cache; @@ -87,6 +95,7 @@ namespace Volo.Abp.Caching Serializer = serializer; KeyNormalizer = keyNormalizer; ServiceScopeFactory = serviceScopeFactory; + UnitOfWorkManager = unitOfWorkManager; SyncSemaphore = new SemaphoreSlim(1, 1); @@ -133,14 +142,25 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. public virtual TCacheItem Get( TCacheKey key, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -163,6 +183,7 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null) { var keyArray = keys.ToArray(); @@ -172,33 +193,57 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, + considerUow, hideErrors ); } + var cachedValues = new List>(); + var notCachedKeys = new List(); + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; try { - cachedBytes = cacheSupportsMultipleItems.GetMany(keyArray.Select(NormalizeKey)); + cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(NormalizeKey)); } catch (Exception ex) { if (hideErrors == true) { HandleException(ex); - return ToCacheItemsWithDefaultValues(keyArray); + return ToCacheItemsWithDefaultValues(readKeys); } throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -208,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, hideErrors: false) + Get(key, considerUow, hideErrors: false) ) ).ToArray(); } @@ -226,6 +271,7 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -236,18 +282,42 @@ namespace Volo.Abp.Caching { return await GetManyFallbackAsync( keyArray, + considerUow, hideErrors, token ); } + var cachedValues = new List>(); + var notCachedKeys = new List(); + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; + try { cachedBytes = await cacheSupportsMultipleItems.GetManyAsync( - keyArray.Select(NormalizeKey), + readKeys.Select(NormalizeKey), CancellationTokenProvider.FallbackToProvider(token) ); } @@ -256,17 +326,18 @@ namespace Volo.Abp.Caching if (hideErrors == true) { await HandleExceptionAsync(ex); - return ToCacheItemsWithDefaultValues(keyArray); + return ToCacheItemsWithDefaultValues(readKeys); } throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -280,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, false, token)) + await GetAsync(key, considerUow, hideErrors: false, token: token)) ); } @@ -302,16 +373,27 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -347,15 +429,17 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null) { - var value = Get(key, hideErrors); + var value = Get(key, considerUow, hideErrors); if (value != null) { return value; @@ -363,14 +447,28 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, hideErrors); + value = Get(key, considerUow, hideErrors); if (value != null) { return value; } value = factory(); - Set(key, value, optionsFactory?.Invoke(), hideErrors); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + Set(key, value, optionsFactory?.Invoke(), considerUow, hideErrors); } return value; @@ -384,17 +482,19 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. public virtual async Task GetOrAddAsync( TCacheKey key, Func> factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, hideErrors, token); + var value = await GetAsync(key, considerUow, hideErrors, token); if (value != null) { return value; @@ -402,14 +502,28 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, hideErrors, token); + value = await GetAsync(key, considerUow, hideErrors, token); if (value != null) { return value; } value = await factory(); - await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + await SetAsync(key, value, optionsFactory?.Invoke(), considerUow, hideErrors, token); } return value; @@ -421,41 +535,70 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void SetRealCache() { - Cache.Set( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Set( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - HandleException(ex); - return; + cache[key].SetValue(value); + } + else + { + cache.Add(key, new UnitOfWorkCacheItem(value)); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - /// /// Sets the cache item value for the provided key. /// /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. @@ -463,35 +606,60 @@ namespace Volo.Abp.Caching TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task SetRealCache() { - await Cache.SetAsync( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try { - await HandleExceptionAsync(ex); - return; + await Cache.SetAsync( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } - throw; + throw; + } } + + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) + { + cache[key].SetValue(value); + } + else + { + cache.Add(key, new UnitOfWorkCacheItem(value)); + } + + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); + } } public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { var itemsArray = items.ToArray(); @@ -502,40 +670,73 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, + considerUow, hideErrors ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + void SetRealCache() { - cacheSupportsMultipleItems.SetMany( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - HandleException(ex); - return; + if (cache.TryGetValue(pair.Key, out _)) + { + cache[pair.Key].SetValue(pair.Value); + } + else + { + cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - + protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - + try { foreach (var item in items) @@ -543,7 +744,8 @@ namespace Volo.Abp.Caching Set( item.Key, item.Value, - options: options, + options, + considerUow, hideErrors: false ); } @@ -563,6 +765,7 @@ namespace Volo.Abp.Caching public virtual async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -574,38 +777,67 @@ namespace Volo.Abp.Caching await SetManyFallbackAsync( itemsArray, options, + considerUow, hideErrors, token ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + async Task SetRealCache() { - await cacheSupportsMultipleItems.SetManyAsync( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await cacheSupportsMultipleItems.SetManyAsync( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - await HandleExceptionAsync(ex); - return; + if (cache.TryGetValue(pair.Key, out _)) + { + cache[pair.Key].SetValue(pair.Value); + } + else + { + cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); } } - + protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -618,7 +850,8 @@ namespace Volo.Abp.Caching await SetAsync( item.Key, item.Value, - options: options, + options, + considerUow, hideErrors: false, token: token ); @@ -636,9 +869,14 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Refresh( - TCacheKey key, bool? - hideErrors = null) + TCacheKey key, + bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -658,6 +896,13 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -681,48 +926,106 @@ namespace Volo.Abp.Caching } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, + bool considerUow = false, bool? hideErrors = null) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void RemoveRealCache() { - Cache.Remove(NormalizeKey(key)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Remove(NormalizeKey(key)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - HandleException(ex); - return; + cache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + RemoveRealCache(); + return Task.CompletedTask; + }); + } + else + { + RemoveRealCache(); } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task RemoveRealCache() { - await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - await HandleExceptionAsync(ex); - return; + cache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(RemoveRealCache); + } + else + { + await RemoveRealCache(); } } @@ -730,7 +1033,7 @@ namespace Volo.Abp.Caching { AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); } - + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -742,7 +1045,7 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } - + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) { if (itemBytes.Length != itemKeys.Length) @@ -764,7 +1067,7 @@ namespace Volo.Abp.Caching return result.ToArray(); } - + [CanBeNull] protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) { @@ -786,12 +1089,33 @@ namespace Volo.Abp.Caching ) ).ToArray(); } - + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) { return keys .Select(key => new KeyValuePair(key, default)) .ToArray(); } + + protected virtual bool ShouldConsiderUow(bool considerUow) + { + return considerUow && UnitOfWorkManager.Current != null; + } + + protected virtual string GetUnitOfWorkCacheKey() + { + return DistributedCacheName + CacheName; + } + + protected virtual Dictionary> GetUnitOfWorkCache() + { + if (UnitOfWorkManager.Current == null) + { + throw new AbpException($"There is no unit of work in the current context, The {GetType().Name} can only be used in a unit of work."); + } + + return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), + key => new Dictionary>()); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 80fe124785..1b0e58815e 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -29,42 +29,48 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. TCacheItem Get( TCacheKey key, + bool considerUow = false, bool? hideErrors = null ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value - /// if the related key not found in the cache. + /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value /// if the related key not found in the cache. - /// + /// /// /// The keys of cached items to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// /// The for the task. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -74,10 +80,12 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. Task GetAsync( [NotNull] TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -89,12 +97,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null ); @@ -105,6 +115,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The cache item. @@ -112,6 +123,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheKey key, Func> factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -122,11 +134,13 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null ); @@ -136,6 +150,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. @@ -143,6 +158,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheKey key, [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -153,25 +169,29 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null ); - + /// /// Sets multiple cache items. /// Based on the implementation, this can be more efficient than setting multiple items individually. /// /// Items to set on the cache /// The cache options for the value. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -203,9 +223,11 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. void Remove( TCacheKey key, + bool considerUow = false, bool? hideErrors = null ); @@ -213,11 +235,13 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs new file mode 100644 index 0000000000..c37475d022 --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs @@ -0,0 +1,43 @@ +using System; + +namespace Volo.Abp.Caching +{ + [Serializable] + public class UnitOfWorkCacheItem + where TValue : class + { + public bool IsRemoved { get; set; } + + public TValue Value { get; set; } + + public UnitOfWorkCacheItem() + { + + } + + public UnitOfWorkCacheItem(TValue value) + { + Value = value; + } + + public UnitOfWorkCacheItem(TValue value, bool isRemoved) + { + Value = value; + IsRemoved = isRemoved; + } + + public UnitOfWorkCacheItem SetValue(TValue value) + { + Value = value; + IsRemoved = false; + return this; + } + + public UnitOfWorkCacheItem RemoveValue() + { + Value = null; + IsRemoved = true; + return this; + } + } +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs new file mode 100644 index 0000000000..46757f80cf --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Caching +{ + public static class UnitOfWorkCacheItemExtensions + { + public static TValue GetUnRemovedValueOrNull(this UnitOfWorkCacheItem item) + where TValue : class + { + return item != null && !item.IsRemoved ? item.Value : null; + } + } +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index b42b745fb4..b4158229fe 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -316,4 +316,4 @@ namespace Volo.Abp.Uow return $"[UnitOfWork {Id}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs index 5c01f54a99..c183a70390 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Uow { @@ -10,5 +13,43 @@ namespace Volo.Abp.Uow return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; } + + public static void AddItem([NotNull] this IUnitOfWork unitOfWork, string key, TValue value) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + if (!unitOfWork.Items.ContainsKey(key)) + { + unitOfWork.Items[key] = value; + } + else + { + unitOfWork.Items.Add(key, value); + } + } + + public static TValue GetItemOrDefault([NotNull] this IUnitOfWork unitOfWork, string key) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.FirstOrDefault(x => x.Key == key).As(); + } + + public static TValue GetOrAddItem([NotNull] this IUnitOfWork unitOfWork, string key, Func factory) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.GetOrAdd(key, factory).As(); + } + + public static void RemoveItem([NotNull] this IUnitOfWork unitOfWork, string key) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + unitOfWork.Items.RemoveAll(x => x.Key == key); + } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 533362c851..61a61ac2cd 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Caching @@ -30,7 +31,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -111,7 +112,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -145,7 +146,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -227,7 +228,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -261,7 +262,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -299,7 +300,7 @@ namespace Volo.Abp.Caching cacheItem2.ShouldNotBeNull(); cacheItem2.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cache1Key); await personCache.RemoveAsync(cache2Key); @@ -310,6 +311,235 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_RemoveAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -340,4 +570,4 @@ namespace Volo.Abp.Caching (await personCache.GetAsync("baris")).ShouldBeNull(); } } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs index 26388fdaf9..5b892a91bb 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; using Volo.Abp.Guids; +using Volo.Abp.Uow; namespace Volo.Abp.FeatureManagement { @@ -27,12 +28,14 @@ namespace Volo.Abp.FeatureManagement FeatureDefinitionManager = featureDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { var cacheItem = await GetCacheItemAsync(name, providerName, providerKey); return cacheItem.Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); @@ -46,21 +49,25 @@ namespace Volo.Abp.FeatureManagement featureValue.Value = value; await FeatureValueRepository.UpdateAsync(featureValue); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new FeatureValueCacheItem(featureValue?.Value), considerUow: true); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var featureValues = await FeatureValueRepository.FindAllAsync(name, providerName, providerKey); foreach (var featureValue in featureValues) { await FeatureValueRepository.DeleteAsync(featureValue); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -68,28 +75,28 @@ namespace Volo.Abp.FeatureManagement } cacheItem = new FeatureValueCacheItem(null); - + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); return cacheItem; } - + private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, FeatureValueCacheItem currentCacheItem) { var featureDefinitions = FeatureDefinitionManager.GetAll(); var featuresDictionary = (await FeatureValueRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var featureDefinition in featureDefinitions) { var featureValue = featuresDictionary.GetOrDefault(featureDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(featureDefinition.Name, providerName, providerKey), @@ -103,7 +110,7 @@ namespace Volo.Abp.FeatureManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs index a6f040dde3..a5250f1804 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Shouldly; using Volo.Abp.Features; using Volo.Abp.Modularity; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.FeatureManagement @@ -14,11 +15,13 @@ namespace Volo.Abp.FeatureManagement { private IFeatureManagementStore FeatureManagementStore { get; set; } private IFeatureValueRepository FeatureValueRepository { get; set; } + private IUnitOfWorkManager UnitOfWorkManager { get; set; } protected FeatureManagementStore_Tests() { FeatureManagementStore = GetRequiredService(); FeatureValueRepository = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); } [Fact] @@ -73,6 +76,30 @@ namespace Volo.Abp.FeatureManagement TestEditionIds.Regular.ToString())).Value.ShouldBe(false.ToString().ToUpperInvariant()); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + + // Act + await FeatureManagementStore.SetAsync(TestFeatureDefinitionProvider.SocialLogins, + false.ToString().ToUpperInvariant(), + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBe(false.ToString().ToUpperInvariant()); + } + } + [Fact] public async Task DeleteAsync() { @@ -94,5 +121,29 @@ namespace Volo.Abp.FeatureManagement } + + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + // Act + await FeatureManagementStore.DeleteAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + await uow.SaveChangesAsync(); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBeNull(); + } + } } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs index 617d360898..a568184f0e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.Settings; +using Volo.Abp.Uow; namespace Volo.Abp.SettingManagement { @@ -16,8 +17,8 @@ namespace Volo.Abp.SettingManagement protected IGuidGenerator GuidGenerator { get; } public SettingManagementStore( - ISettingRepository settingRepository, - IGuidGenerator guidGenerator, + ISettingRepository settingRepository, + IGuidGenerator guidGenerator, IDistributedCache cache, ISettingDefinitionManager settingDefinitionManager) { @@ -27,11 +28,13 @@ namespace Volo.Abp.SettingManagement SettingDefinitionManager = settingDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { return (await GetCacheItemAsync(name, providerName, providerKey)).Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); @@ -45,6 +48,8 @@ namespace Volo.Abp.SettingManagement setting.Value = value; await SettingRepository.UpdateAsync(setting); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new SettingCacheItem(setting?.Value), considerUow: true); } public virtual async Task> GetListAsync(string providerName, string providerKey) @@ -53,19 +58,21 @@ namespace Volo.Abp.SettingManagement return settings.Select(s => new SettingValue(s.Name, s.Value)).ToList(); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); if (setting != null) { await SettingRepository.DeleteAsync(setting); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -75,26 +82,26 @@ namespace Volo.Abp.SettingManagement cacheItem = new SettingCacheItem(null); await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); - + return cacheItem; } private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, SettingCacheItem currentCacheItem) { var settingDefinitions = SettingDefinitionManager.GetAll(); var settingsDictionary = (await SettingRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var settingDefinition in settingDefinitions) { var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(settingDefinition.Name, providerName, providerKey), @@ -108,7 +115,7 @@ namespace Volo.Abp.SettingManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs index 753ba15054..7a405faf2d 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Settings; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.SettingManagement @@ -13,12 +14,14 @@ namespace Volo.Abp.SettingManagement private readonly ISettingManagementStore _settingManagementStore; private readonly ISettingRepository _settingRepository; private readonly SettingTestData _testData; + private readonly IUnitOfWorkManager _unitOfWorkManager; public SettingManagementStore_Tests() { _settingManagementStore = GetRequiredService(); _settingRepository = GetRequiredService(); _testData = GetRequiredService(); + _unitOfWorkManager= GetRequiredService(); } [Fact] @@ -50,6 +53,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).Value.ShouldBe("43"); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (_unitOfWorkManager.Begin()) + { + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBe("42"); + + await _settingManagementStore.SetAsync("MySetting1", "43", GlobalSettingValueProvider.ProviderName, null); + + var valueAfterSet = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + valueAfterSet.ShouldBe("43"); + } + } + [Fact] public async Task DeleteAsync() { @@ -60,5 +78,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).ShouldBeNull(); } + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = _unitOfWorkManager.Begin()) + { + (await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null)).ShouldNotBeNull(); + + await _settingManagementStore.DeleteAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + + await uow.SaveChangesAsync(); + + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBeNull(); + } + } + } } From 6f1514da88931591a149a3b4b222f8f0ca7e6ab0 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 1 Jul 2020 11:53:04 +0800 Subject: [PATCH 02/38] Switch to the tenant before calling the handleevent method. Resolve #4508 --- .../RabbitMq/RabbitMqDistributedEventBus.cs | 14 ++-- .../Volo.Abp.EventBus.csproj | 1 + .../Volo/Abp/EventBus/AbpEventBusModule.cs | 2 + .../Volo/Abp/EventBus/EventBusBase.cs | 80 +++++++++++++------ .../Volo/Abp/EventBus/Local/LocalEventBus.cs | 8 +- .../LocalDistributedEventBus_Test.cs | 29 ++++++- ...leDistributedSingleInstanceEventHandler.cs | 32 ++++++++ .../Local/EventBus_MultiTenancy_Test.cs | 64 +++++++++++++++ .../Volo/Abp/EventBus/MySimpleEventData.cs | 12 ++- 9 files changed, 203 insertions(+), 39 deletions(-) create mode 100644 framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs create mode 100644 framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index ed7d37db20..e8c7fc6c5b 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -9,6 +9,7 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; @@ -26,7 +27,7 @@ namespace Volo.Abp.EventBus.RabbitMq protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } - + //TODO: Accessing to the List may not be thread-safe! protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } @@ -37,17 +38,18 @@ namespace Volo.Abp.EventBus.RabbitMq IOptions options, IConnectionPool connectionPool, IRabbitMqSerializer serializer, - IServiceScopeFactory serviceScopeFactory, + IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, - IRabbitMqMessageConsumerFactory messageConsumerFactory) - : base(serviceScopeFactory) + IRabbitMqMessageConsumerFactory messageConsumerFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; - + HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); } @@ -178,7 +180,7 @@ namespace Volo.Abp.EventBus.RabbitMq "direct", durable: true ); - + var properties = channel.CreateBasicProperties(); properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 566767e111..a1799c6673 100644 --- a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj +++ b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index c5a5022f77..81c3393a50 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus { + [DependsOn(typeof(AbpMultiTenancyModule))] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index a7883950d1..6c3f93814a 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Collections; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus @@ -16,9 +17,12 @@ namespace Volo.Abp.EventBus { protected IServiceScopeFactory ServiceScopeFactory { get; } - protected EventBusBase(IServiceScopeFactory serviceScopeFactory) + protected ICurrentTenant CurrentTenant { get; } + + protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) { ServiceScopeFactory = serviceScopeFactory; + CurrentTenant = currentTenant; } /// @@ -162,31 +166,34 @@ namespace Volo.Abp.EventBus { var handlerType = eventHandlerWrapper.EventHandler.GetType(); - if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) - { - var method = typeof(ILocalEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(ILocalEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) - { - var method = typeof(IDistributedEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(IDistributedEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else + using (CurrentTenant.Change(GetEventDataTenantId(eventData))) { - throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) + { + var method = typeof(ILocalEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(ILocalEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) + { + var method = typeof(IDistributedEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(IDistributedEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else + { + throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + } } } catch (TargetInvocationException ex) @@ -200,6 +207,27 @@ namespace Volo.Abp.EventBus } } + protected virtual Guid? GetEventDataTenantId(object eventData) + { + if (eventData is IMultiTenant multiTenantEventData) + { + return multiTenantEventData.TenantId; + } + + //TODO: Cache propertyInfo & Use interface or class to get Entity property. + var propertyInfo = eventData.GetType().GetProperty("Entity"); + if (propertyInfo != null && propertyInfo.GetGetMethod(true) != null) + { + var entity = propertyInfo.GetValue(eventData); + if (entity != null && entity is IMultiTenant multiTenantEntity) + { + return multiTenantEntity.TenantId; + } + } + + return CurrentTenant.Id; + } + protected class EventTypeWithEventHandlerFactories { public Type EventType { get; } @@ -246,4 +274,4 @@ namespace Volo.Abp.EventBus } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index 7db5c18f01..8c7bef6f2d 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; namespace Volo.Abp.EventBus.Local @@ -29,8 +30,9 @@ namespace Volo.Abp.EventBus.Local public LocalEventBus( IOptions options, - IServiceScopeFactory serviceScopeFactory) - : base(serviceScopeFactory) + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { Options = options.Value; Logger = NullLogger.Instance; @@ -166,4 +168,4 @@ namespace Volo.Abp.EventBus.Local return false; } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs index 47649ad3c3..d3371f383a 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; using Xunit; namespace Volo.Abp.EventBus.Distributed @@ -17,5 +20,29 @@ namespace Volo.Abp.EventBus.Distributed Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount); Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount); } + + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } + + [Fact] + public async Task Should_Change_TenantId_If_Generic_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe>(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs new file mode 100644 index 0000000000..53fe8e347b --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.EventBus.Distributed +{ + public class MySimpleDistributedSingleInstanceEventHandler : IDistributedEventHandler, IDistributedEventHandler>, ITransientDependency + { + private readonly ICurrentTenant _currentTenant; + + public MySimpleDistributedSingleInstanceEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public static Guid? TenantId { get; set; } + + public Task HandleEventAsync(MySimpleEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + + public Task HandleEventAsync(EntityCreatedEto eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs new file mode 100644 index 0000000000..62dcb33b42 --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.EventBus.Local +{ + public class EventBus_MultiTenancy_Test : EventBusTestBase + { + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + var handler = new MyEventHandler(GetRequiredService()); + + LocalEventBus.Subscribe>(handler); + + await LocalEventBus.PublishAsync(new EntityCreatedEventData(new MyEntity(tenantId))); + + handler.TenantId.ShouldBe(tenantId); + } + + public class MyEntity : Entity, IMultiTenant + { + public override object[] GetKeys() + { + return new object[0]; + } + + public MyEntity() + { + + } + + public MyEntity(Guid? tenantId) + { + TenantId = tenantId; + } + + public Guid? TenantId { get; } + } + + public class MyEventHandler : ILocalEventHandler> + { + private readonly ICurrentTenant _currentTenant; + + public MyEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public Guid? TenantId { get; set; } + + public Task HandleEventAsync(EntityChangedEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs index 08142b3355..45a3e7f237 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs @@ -1,12 +1,18 @@ +using System; +using Volo.Abp.MultiTenancy; + namespace Volo.Abp.EventBus { - public class MySimpleEventData + public class MySimpleEventData : IMultiTenant { public int Value { get; set; } - public MySimpleEventData(int value) + public Guid? TenantId { get; } + + public MySimpleEventData(int value, Guid? tenantId = null) { Value = value; + TenantId = tenantId; } } -} \ No newline at end of file +} From a874455eda5bf70326ab02654f4971e50f094ae3 Mon Sep 17 00:00:00 2001 From: wakuflair Date: Thu, 2 Jul 2020 18:19:06 +0800 Subject: [PATCH 03/38] TagHelper: Suppress label generation if "nolabel" specified --- .../TagHelpers/Form/AbpInputTagHelperService.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 4ddf44d2d6..b5c4b7ec37 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -247,7 +247,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) { - if (IsOutputHidden(inputTag)) + if (IsOutputHidden(inputTag) || SuppressLabel(inputTag)) { return ""; } @@ -433,6 +433,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; } + protected virtual bool SuppressLabel(TagHelperOutput inputTag) + { + return inputTag.AllAttributes.ContainsName("nolabel"); + } + protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) { var list = context.GetValue>(FormGroupContents) ?? new List(); From 667e52c142861cb1b5b2b479d80a98470a4282be Mon Sep 17 00:00:00 2001 From: wakuflair Date: Thu, 2 Jul 2020 18:27:47 +0800 Subject: [PATCH 04/38] Fix wrong property usage --- .../TagHelpers/Form/AbpInputTagHelperService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index b5c4b7ec37..f6abd15d6b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -435,7 +435,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual bool SuppressLabel(TagHelperOutput inputTag) { - return inputTag.AllAttributes.ContainsName("nolabel"); + return inputTag.Attributes.ContainsName("nolabel"); } protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) From f219d9bf9346d8650c2d5b38b3c72a18ba59e148 Mon Sep 17 00:00:00 2001 From: wakuflair Date: Thu, 2 Jul 2020 18:33:33 +0800 Subject: [PATCH 05/38] Rename `SuppressLabel` to `ShouldSuppressLabel` --- .../TagHelpers/Form/AbpInputTagHelperService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index f6abd15d6b..55aeafc4ea 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -247,7 +247,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) { - if (IsOutputHidden(inputTag) || SuppressLabel(inputTag)) + if (IsOutputHidden(inputTag) || ShouldSuppressLabel(inputTag)) { return ""; } @@ -433,7 +433,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; } - protected virtual bool SuppressLabel(TagHelperOutput inputTag) + protected virtual bool ShouldSuppressLabel(TagHelperOutput inputTag) { return inputTag.Attributes.ContainsName("nolabel"); } From 300bbb39badbd18a6818b1af172a3cbea0c6f4be Mon Sep 17 00:00:00 2001 From: wakuflair Date: Mon, 6 Jul 2020 10:34:37 +0800 Subject: [PATCH 06/38] Introduced `SuppressLabel` property instead of `nolabel` attribute --- .../TagHelpers/Form/AbpInputTagHelper.cs | 2 ++ .../TagHelpers/Form/AbpInputTagHelperService.cs | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs index efd3af2d2c..711e8dfb6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs @@ -36,6 +36,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form public string Value { get; set; } + public bool SuppressLabel { get; set; } + public AbpInputTagHelper(AbpInputTagHelperService tagHelperService) : base(tagHelperService) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 55aeafc4ea..58a88c676f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -247,7 +247,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) { - if (IsOutputHidden(inputTag) || ShouldSuppressLabel(inputTag)) + if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) { return ""; } @@ -433,11 +433,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; } - protected virtual bool ShouldSuppressLabel(TagHelperOutput inputTag) - { - return inputTag.Attributes.ContainsName("nolabel"); - } - protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) { var list = context.GetValue>(FormGroupContents) ?? new List(); From e7e1f3fe73c51f382a67fca72f27e2f2a2ba76de Mon Sep 17 00:00:00 2001 From: wakuflair Date: Mon, 6 Jul 2020 10:35:02 +0800 Subject: [PATCH 07/38] Added "Suppress Label Generation" demo --- .../Pages/Components/FormElements.cshtml | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml index 889e798c8a..6fa9cabb37 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml @@ -108,7 +108,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List<SelectListItem> CityList { get; set; } = new List<SelectListItem> { new SelectListItem { Value = "NY", Text = "New York"}, @@ -260,7 +260,7 @@ public class SampleModel { public string SampleInput0 { get; set; } - + public string SampleInput1 { get; set; } public string SampleInput2 { get; set; } @@ -354,7 +354,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List"SelectListItem" CityList { get; set; } = new List"SelectListItem" { new SelectListItem { Value = "NY", Text = "New York"}, @@ -458,6 +458,51 @@ <option value="3">Coupe</option> </select> </div> + + + + + + +

Suppress Label Generation

+ +
+
+ +
+
+ + +

+ public class FormElementsModel : PageModel
+ {
+     public SampleModel MyModel { get; set; }
+
+     public void OnGet()
+     {
+         MyModel = new SampleModel();
+     }
+
+     public class SampleModel
+     {
+         [Required]
+         public string Name { get; set; }
+     }
+ }
+
+
+
+ +

+<abp-input asp-for="@@Model.MyModel.Name" suppress-label="true"/>
+                
+
+ +

+<div class="form-group">
+    <input type="text" id="MyModel_Name" name="MyModel.Name" value="" class="form-control ">
+    <span class="text-danger field-validation-valid" data-valmsg-for="MyModel.Name" data-valmsg-replace="true"></span>
+</div>
 
From c6cdc8d628a2ea87a4473e86cd5bdd85888d5f8d Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 10 Jul 2020 16:02:54 +0800 Subject: [PATCH 08/38] Add TestMemoryDistributedCache for better test. --- .../Volo/Abp/Caching/AbpCachingTestModule.cs | 6 +- .../Abp/Caching/TestMemoryDistributedCache.cs | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index be003431f2..9da6725fa5 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Modularity; namespace Volo.Abp.Caching @@ -26,6 +28,8 @@ namespace Volo.Abp.Caching option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); }); + + context.Services.Replace(ServiceDescriptor.Singleton()); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs new file mode 100644 index 0000000000..69589d97cd --- /dev/null +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Caching +{ + [DisableConventionalRegistration] + public class TestMemoryDistributedCache : MemoryDistributedCache, ICacheSupportsMultipleItems + { + public TestMemoryDistributedCache(IOptions optionsAccessor) + : base(optionsAccessor) + { + } + + public TestMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory) + : base(optionsAccessor, loggerFactory) + { + } + + public byte[][] GetMany(IEnumerable keys) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(Get(key)); + } + return values.ToArray(); + } + + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(await GetAsync(key, token)); + } + return values.ToArray(); + } + + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) + { + foreach (var item in items) + { + Set(item.Key, item.Value, options); + } + } + + public async Task SetManyAsync(IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) + { + foreach (var item in items) + { + await SetAsync(item.Key, item.Value, options, token); + } + } + } +} From 3f975b8b3e3b1770e02b9adf37a3d3b190cfc520 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 09:32:59 +0800 Subject: [PATCH 09/38] Get event data tenant id from IEventDataMayHaveTenantId. --- .../Volo/Abp/EventBus/EventBusBase.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index 6c3f93814a..80d4134db2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -209,23 +209,12 @@ namespace Volo.Abp.EventBus protected virtual Guid? GetEventDataTenantId(object eventData) { - if (eventData is IMultiTenant multiTenantEventData) + return eventData switch { - return multiTenantEventData.TenantId; - } - - //TODO: Cache propertyInfo & Use interface or class to get Entity property. - var propertyInfo = eventData.GetType().GetProperty("Entity"); - if (propertyInfo != null && propertyInfo.GetGetMethod(true) != null) - { - var entity = propertyInfo.GetValue(eventData); - if (entity != null && entity is IMultiTenant multiTenantEntity) - { - return multiTenantEntity.TenantId; - } - } - - return CurrentTenant.Id; + IMultiTenant multiTenantEventData => multiTenantEventData.TenantId, + IEventDataMayHaveTenantId eventDataMayHaveTenantId when eventDataMayHaveTenantId.IsMultiTenant(out var tenantId) => tenantId, + _ => CurrentTenant.Id + }; } protected class EventTypeWithEventHandlerFactories From a391e7a5280507314e7d22ea7fe1e9b7a1569060 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 11:07:19 +0800 Subject: [PATCH 10/38] Refactor. --- .../Volo/Abp/Caching/DistributedCache.cs | 160 +++++++++--------- .../Volo/Abp/Caching/IDistributedCache.cs | 58 +++---- .../Abp/Caching/DistributedCache_Tests.cs | 12 +- 3 files changed, 115 insertions(+), 115 deletions(-) diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 28082832b0..b26feec136 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -53,7 +53,7 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { - public const string DistributedCacheName = "AbpDistributedCache"; + public const string UowCacheName = "AbpDistributedCache"; public ILogger> Logger { get; set; } @@ -142,13 +142,13 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. public virtual TCacheItem Get( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -183,8 +183,8 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var keyArray = keys.ToArray(); @@ -193,19 +193,19 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, - considerUow, - hideErrors + hideErrors, + considerUow ); } - var cachedValues = new List>(); var notCachedKeys = new List(); + var cachedValues = new List>(); if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var key in keyArray) { - var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); if (value != null) { cachedValues.Add(new KeyValuePair(key, value)); @@ -232,7 +232,7 @@ namespace Volo.Abp.Caching if (hideErrors == true) { HandleException(ex); - return ToCacheItemsWithDefaultValues(readKeys); + return ToCacheItemsWithDefaultValues(keyArray); } throw; @@ -243,8 +243,8 @@ namespace Volo.Abp.Caching protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -253,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, considerUow, hideErrors: false) + Get(key, false, considerUow) ) ).ToArray(); } @@ -271,8 +271,8 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var keyArray = keys.ToArray(); @@ -282,20 +282,20 @@ namespace Volo.Abp.Caching { return await GetManyFallbackAsync( keyArray, - considerUow, hideErrors, + considerUow, token ); } - var cachedValues = new List>(); var notCachedKeys = new List(); + var cachedValues = new List>(); if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var key in keyArray) { - var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); if (value != null) { cachedValues.Add(new KeyValuePair(key, value)); @@ -326,7 +326,7 @@ namespace Volo.Abp.Caching if (hideErrors == true) { await HandleExceptionAsync(ex); - return ToCacheItemsWithDefaultValues(readKeys); + return ToCacheItemsWithDefaultValues(keyArray); } throw; @@ -337,8 +337,8 @@ namespace Volo.Abp.Caching protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -351,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, considerUow, hideErrors: false, token: token)) + await GetAsync(key, false, considerUow, token: token)) ); } @@ -373,14 +373,14 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -429,17 +429,17 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - var value = Get(key, considerUow, hideErrors); + var value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -447,7 +447,7 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, considerUow, hideErrors); + value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -468,7 +468,7 @@ namespace Volo.Abp.Caching } } - Set(key, value, optionsFactory?.Invoke(), considerUow, hideErrors); + Set(key, value, optionsFactory?.Invoke(), hideErrors, considerUow); } return value; @@ -489,12 +489,12 @@ namespace Volo.Abp.Caching TCacheKey key, Func> factory, Func optionsFactory = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, considerUow, hideErrors, token); + var value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -502,7 +502,7 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, considerUow, hideErrors, token); + value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -523,7 +523,7 @@ namespace Volo.Abp.Caching } } - await SetAsync(key, value, optionsFactory?.Invoke(), considerUow, hideErrors, token); + await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token); } return value; @@ -535,14 +535,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { void SetRealCache() { @@ -570,14 +570,14 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].SetValue(value); + uowCache[key].SetValue(value); } else { - cache.Add(key, new UnitOfWorkCacheItem(value)); + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } // ReSharper disable once PossibleNullReferenceException @@ -598,16 +598,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { async Task SetRealCache() @@ -637,14 +637,14 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].SetValue(value); + uowCache[key].SetValue(value); } else { - cache.Add(key, new UnitOfWorkCacheItem(value)); + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } // ReSharper disable once PossibleNullReferenceException @@ -659,8 +659,8 @@ namespace Volo.Abp.Caching public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var itemsArray = items.ToArray(); @@ -670,8 +670,8 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, - considerUow, - hideErrors + hideErrors, + considerUow ); return; @@ -702,17 +702,17 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var pair in itemsArray) { - if (cache.TryGetValue(pair.Key, out _)) + if (uowCache.TryGetValue(pair.Key, out _)) { - cache[pair.Key].SetValue(pair.Value); + uowCache[pair.Key].SetValue(pair.Value); } else { - cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); } } @@ -732,8 +732,8 @@ namespace Volo.Abp.Caching protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -745,8 +745,8 @@ namespace Volo.Abp.Caching item.Key, item.Value, options, - considerUow, - hideErrors: false + false, + considerUow ); } } @@ -765,8 +765,8 @@ namespace Volo.Abp.Caching public virtual async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var itemsArray = items.ToArray(); @@ -777,8 +777,8 @@ namespace Volo.Abp.Caching await SetManyFallbackAsync( itemsArray, options, - considerUow, hideErrors, + considerUow, token ); @@ -811,17 +811,17 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var pair in itemsArray) { - if (cache.TryGetValue(pair.Key, out _)) + if (uowCache.TryGetValue(pair.Key, out _)) { - cache[pair.Key].SetValue(pair.Value); + uowCache[pair.Key].SetValue(pair.Value); } else { - cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); } } @@ -837,8 +837,8 @@ namespace Volo.Abp.Caching protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -851,8 +851,8 @@ namespace Volo.Abp.Caching item.Key, item.Value, options, + false, considerUow, - hideErrors: false, token: token ); } @@ -934,8 +934,8 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { void RemoveRealCache() { @@ -959,10 +959,10 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].RemoveValue(); + uowCache[key].RemoveValue(); } // ReSharper disable once PossibleNullReferenceException @@ -982,14 +982,14 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { async Task RemoveRealCache() @@ -1014,10 +1014,10 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].RemoveValue(); + uowCache[key].RemoveValue(); } // ReSharper disable once PossibleNullReferenceException @@ -1104,14 +1104,14 @@ namespace Volo.Abp.Caching protected virtual string GetUnitOfWorkCacheKey() { - return DistributedCacheName + CacheName; + return UowCacheName + CacheName; } protected virtual Dictionary> GetUnitOfWorkCache() { if (UnitOfWorkManager.Current == null) { - throw new AbpException($"There is no unit of work in the current context, The {GetType().Name} can only be used in a unit of work."); + throw new AbpException($"There is no active UOW."); } return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 1b0e58815e..2f74fd678b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -29,13 +29,13 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. TCacheItem Get( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -46,13 +46,13 @@ namespace Volo.Abp.Caching /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -64,14 +64,14 @@ namespace Volo.Abp.Caching /// /// /// The keys of cached items to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// /// The for the task. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -85,8 +85,8 @@ namespace Volo.Abp.Caching /// The cache item, or null. Task GetAsync( [NotNull] TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -97,15 +97,15 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -115,16 +115,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. Task GetOrAddAsync( [NotNull] TCacheKey key, Func> factory, Func optionsFactory = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -134,14 +134,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -150,16 +150,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetAsync( [NotNull] TCacheKey key, [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -169,13 +169,13 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -184,15 +184,15 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -223,26 +223,26 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Remove( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. - /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); } diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 61a61ac2cd..35a72362ac 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -181,10 +181,10 @@ namespace Volo.Abp.Caching factoryExecuted = false; cacheItem = await personCache.GetOrAddAsync(cacheKey, - async () => + () => { factoryExecuted = true; - return new PersonCacheItem(personName); + return Task.FromResult(new PersonCacheItem(personName)); }); factoryExecuted.ShouldBeFalse(); @@ -539,7 +539,7 @@ namespace Volo.Abp.Caching cacheValue = await personCache.GetAsync(key, considerUow: false); cacheValue.ShouldBeNull(); } - + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -547,7 +547,7 @@ namespace Volo.Abp.Caching await personCache.SetManyAsync(new[] { - new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("john", new PersonCacheItem("John Nash")), new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) }); @@ -557,7 +557,7 @@ namespace Volo.Abp.Caching "thomas", "baris" //doesn't exist }); - + cacheItems.Length.ShouldBe(3); cacheItems[0].Key.ShouldBe("john"); cacheItems[0].Value.Name.ShouldBe("John Nash"); @@ -565,7 +565,7 @@ namespace Volo.Abp.Caching cacheItems[1].Value.Name.ShouldBe("Thomas Moore"); cacheItems[2].Key.ShouldBe("baris"); cacheItems[2].Value.ShouldBeNull(); - + (await personCache.GetAsync("john")).Name.ShouldBe("John Nash"); (await personCache.GetAsync("baris")).ShouldBeNull(); } From 57fdecc462a44fea4e64717956ebb8b93b87bb3a Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 13:00:06 +0800 Subject: [PATCH 11/38] Add GetMany & SetMany unit tests. --- .../Abp/Caching/DistributedCache_Tests.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 35a72362ac..32876c08fe 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; @@ -540,6 +541,148 @@ namespace Volo.Abp.Caching cacheValue.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { From 83aeea2230dcac31a3c8b8c571dc6ac803cdbe8d Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 15:34:22 +0800 Subject: [PATCH 12/38] Use IdentitySecurityLogManager instead EventBus to save security logs. https://github.com/abpframework/abp/pull/4675#issuecomment-660089083 --- .../IdentityServerSupportedLoginModel.cs | 2 +- .../IdentityServerSupportedLogoutModel.cs | 2 +- .../Account/Controllers/AccountController.cs | 16 +++----- .../Pages/Account/AccountPageModel.cs | 3 +- .../Pages/Account/Login.cshtml.cs | 6 +-- .../Pages/Account/Logout.cshtml.cs | 2 +- ...Event.cs => IdentitySecurityLogContext.cs} | 7 ++-- ...ndler.cs => IdentitySecurityLogManager.cs} | 26 ++++++------- .../IdentityServerSecurityLogActionConsts.cs | 19 ++++++++++ ...IdentityServerSecurityLogIdentityConsts.cs | 7 ++++ .../AbpResourceOwnerPasswordValidator.cs | 25 ++++++++++++- .../AspNetIdentity/SignInResultExtensions.cs | 37 +++++++++++++++++++ 12 files changed, 115 insertions(+), 37 deletions(-) rename modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/{IdentitySecurityLogEvent.cs => IdentitySecurityLogContext.cs} (72%) rename modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/{IdentitySecurityLogHandler.cs => IdentitySecurityLogManager.cs} (74%) create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index 5afe312a6e..297e54cd69 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -132,7 +132,7 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = result.ToIdentitySecurityLogAction(), diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs index b25b34799d..bec2ede369 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs @@ -20,7 +20,7 @@ namespace Volo.Abp.Account.Web.Pages.Account public override async Task OnGetAsync() { - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.Logout diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index aa22882701..575853f410 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -6,10 +6,8 @@ using Volo.Abp.Account.Localization; using Volo.Abp.Account.Settings; using Volo.Abp.Account.Web.Areas.Account.Controllers.Models; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.EventBus.Local; using Volo.Abp.Identity; using Volo.Abp.Identity.AspNetCore; -using Volo.Abp.SecurityLog; using Volo.Abp.Settings; using Volo.Abp.Validation; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; @@ -28,22 +26,20 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers protected SignInManager SignInManager { get; } protected IdentityUserManager UserManager { get; } protected ISettingProvider SettingProvider { get; } - - protected ILocalEventBus LocalEventBus { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } public AccountController( SignInManager signInManager, IdentityUserManager userManager, ISettingProvider settingProvider, - ISecurityLogManager securityLogManager, - ILocalEventBus localEventBus) + IdentitySecurityLogManager identitySecurityLogManager) { LocalizationResource = typeof(AccountResource); SignInManager = signInManager; UserManager = userManager; SettingProvider = settingProvider; - LocalEventBus = localEventBus; + IdentitySecurityLogManager = identitySecurityLogManager; } [HttpPost] @@ -62,7 +58,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers true ); - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = signInResult.ToIdentitySecurityLogAction(), @@ -76,13 +72,13 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers [Route("logout")] public virtual async Task Logout() { - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.Logout }); - await SignInManager.SignOutAsync(); + await SignInManager.SignOutAsync(); } [HttpPost] diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs index e645c9c9cf..7e79442bd7 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account.Localization; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; -using Volo.Abp.EventBus.Local; using Volo.Abp.Identity; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -15,7 +14,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { public SignInManager SignInManager { get; set; } public IdentityUserManager UserManager { get; set; } - public ILocalEventBus LocalEventBus { get; set; } + public IdentitySecurityLogManager IdentitySecurityLogManager { get; set; } protected AccountPageModel() { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 05946182db..9d6c540ce5 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -98,7 +98,7 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = result.ToIdentitySecurityLogAction(), @@ -194,7 +194,7 @@ namespace Volo.Abp.Account.Web.Pages.Account if (!result.Succeeded) { - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, Action = "Login" + result @@ -224,7 +224,7 @@ namespace Volo.Abp.Account.Web.Pages.Account await SignInManager.SignInAsync(user, false); - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, Action = result.ToIdentitySecurityLogAction(), diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index fe7361e4ce..5923827a24 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { - await LocalEventBus.PublishAsync(new IdentitySecurityLogEvent + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.Logout diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogEvent.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs similarity index 72% rename from modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogEvent.cs rename to modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs index faac2d5033..42ec5e6fe5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogEvent.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity { - public class IdentitySecurityLogEvent : IMultiTenant + public class IdentitySecurityLogContext { public Guid? TenantId { get; set; } @@ -18,12 +17,12 @@ namespace Volo.Abp.Identity public Dictionary ExtraProperties { get; } - public IdentitySecurityLogEvent() + public IdentitySecurityLogContext() { ExtraProperties = new Dictionary(); } - public virtual IdentitySecurityLogEvent WithProperty(string key, object value) + public virtual IdentitySecurityLogContext WithProperty(string key, object value) { ExtraProperties[key] = value; return this; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogHandler.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs similarity index 74% rename from modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogHandler.cs rename to modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs index 5000d4d288..bdad21df13 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogHandler.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs @@ -2,15 +2,13 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus; using Volo.Abp.Security.Claims; using Volo.Abp.SecurityLog; -using Volo.Abp.Uow; using Volo.Abp.Users; namespace Volo.Abp.Identity { - public class IdentitySecurityLogHandler : ILocalEventHandler, ITransientDependency + public class IdentitySecurityLogManager : ITransientDependency { protected ISecurityLogManager SecurityLogManager { get; } protected IdentityUserManager UserManager { get; } @@ -18,7 +16,7 @@ namespace Volo.Abp.Identity protected IUserClaimsPrincipalFactory UserClaimsPrincipalFactory { get; } protected ICurrentUser CurrentUser { get; } - public IdentitySecurityLogHandler( + public IdentitySecurityLogManager( ISecurityLogManager securityLogManager, IdentityUserManager userManager, ICurrentPrincipalAccessor currentPrincipalAccessor, @@ -32,24 +30,24 @@ namespace Volo.Abp.Identity CurrentUser = currentUser; } - public async Task HandleEventAsync(IdentitySecurityLogEvent eventData) + public async Task SaveAsync(IdentitySecurityLogContext context) { Action securityLogAction = securityLog => { - securityLog.Identity = eventData.Identity; - securityLog.Action = eventData.Action; + securityLog.Identity = context.Identity; + securityLog.Action = context.Action; - if (securityLog.UserName.IsNullOrWhiteSpace()) + if (!context.UserName.IsNullOrWhiteSpace()) { - securityLog.UserName = eventData.UserName; + securityLog.UserName = context.UserName; } - if (securityLog.ClientId.IsNullOrWhiteSpace()) + if (!context.ClientId.IsNullOrWhiteSpace()) { - securityLog.ClientId = eventData.ClientId; + securityLog.ClientId = context.ClientId; } - foreach (var property in eventData.ExtraProperties) + foreach (var property in context.ExtraProperties) { securityLog.ExtraProperties[property.Key] = property.Value; } @@ -61,13 +59,13 @@ namespace Volo.Abp.Identity } else { - if (eventData.UserName.IsNullOrWhiteSpace()) + if (context.UserName.IsNullOrWhiteSpace()) { await SecurityLogManager.SaveAsync(securityLogAction); } else { - var user = await UserManager.FindByNameAsync(eventData.UserName); + var user = await UserManager.FindByNameAsync(context.UserName); if (user != null) { using (CurrentPrincipalAccessor.Change(await UserClaimsPrincipalFactory.CreateAsync(user))) diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs new file mode 100644 index 0000000000..47ad6b21d5 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.IdentityServer +{ + public class IdentityServerSecurityLogActionConsts + { + public static string LoginSucceeded { get; set; } = "LoginSucceeded"; + + public static string LoginLockedout { get; set; } = "LoginLockedout"; + + public static string LoginNotAllowed { get; set; } = "LoginNotAllowed"; + + public static string LoginRequiresTwoFactor { get; set; } = "LoginRequiresTwoFactor"; + + public static string LoginFailed { get; set; } = "LoginFailed"; + + public static string LoginInvalidUserName { get; set; } = "LoginInvalidUserName"; + + public static string LoginInvalidUserNameOrPassword { get; set; } = "LoginInvalidUserNameOrPassword"; + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs new file mode 100644 index 0000000000..4a5f96e0e2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer +{ + public class IdentityServerSecurityLogIdentityConsts + { + public static string IdentityServer { get; set; } = "IdentityServer"; + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs index e45df9b3f7..90910e1894 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs @@ -10,6 +10,7 @@ using IdentityServer4.Validation; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; +using Volo.Abp.Identity; using Volo.Abp.IdentityServer.Localization; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; @@ -23,18 +24,21 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity protected SignInManager SignInManager { get; } protected IEventService Events { get; } protected UserManager UserManager { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } protected ILogger> Logger { get; } protected IStringLocalizer Localizer { get; } public AbpResourceOwnerPasswordValidator( UserManager userManager, SignInManager signInManager, + IdentitySecurityLogManager identitySecurityLogManager, IEventService events, - ILogger> logger, + ILogger> logger, IStringLocalizer localizer) { UserManager = userManager; SignInManager = signInManager; + IdentitySecurityLogManager = identitySecurityLogManager; Events = events; Logger = logger; Localizer = localizer; @@ -71,6 +75,12 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity additionalClaims.ToArray() ); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = result.ToIdentitySecurityLogAction(), + }); + return; } else if (result.IsLockedOut) @@ -91,12 +101,25 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity await Events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid credentials", interactive: false)); errorDescription = Localizer["InvalidUserNameOrPassword"]; } + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = result.ToIdentitySecurityLogAction(), + UserName = context.UserName + }); } else { Logger.LogInformation("No user found matching username: {username}", context.UserName); await Events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid username", interactive: false)); errorDescription = Localizer["InvalidUsername"]; + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = IdentityServerSecurityLogActionConsts.LoginInvalidUserName + }); } context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription); diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs new file mode 100644 index 0000000000..6f167912c3 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Identity; + +namespace Volo.Abp.IdentityServer.AspNetIdentity +{ + public static class SignInResultExtensions + { + public static string ToIdentitySecurityLogAction(this SignInResult result) + { + if (result.Succeeded) + { + return IdentityServerSecurityLogActionConsts.LoginSucceeded; + } + + if (result.IsLockedOut) + { + return IdentityServerSecurityLogActionConsts.LoginLockedout; + } + + if (result.RequiresTwoFactor) + { + return IdentityServerSecurityLogActionConsts.LoginRequiresTwoFactor; + } + + if (result.IsNotAllowed) + { + return IdentityServerSecurityLogActionConsts.LoginNotAllowed; + } + + if (!result.Succeeded) + { + return IdentityServerSecurityLogActionConsts.LoginFailed; + } + + return IdentityServerSecurityLogActionConsts.LoginFailed; + } + } +} From 1cc65bc3ef726ba78ef3a90a1aec59fe645b843a Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 16:03:01 +0800 Subject: [PATCH 13/38] Remove TenantId from IdentitySecurityLogContext. --- .../Volo/Abp/Identity/IdentitySecurityLogContext.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs index 42ec5e6fe5..740688ff84 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs @@ -5,8 +5,6 @@ namespace Volo.Abp.Identity { public class IdentitySecurityLogContext { - public Guid? TenantId { get; set; } - public string Identity { get; set; } public string Action { get; set; } From dbe69735c93c9c9ad9071a2df131bbcc58b86a5b Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 21 Jul 2020 21:25:02 +0800 Subject: [PATCH 14/38] Handle the case where currentLanguage is null. --- .../LanguageSwitchViewComponent.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs index c6921d3210..3dd1ba2b9d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs @@ -1,7 +1,9 @@ -using System.Globalization; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RequestLocalization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar.LanguageSwitch @@ -23,12 +25,32 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar CultureInfo.CurrentUICulture.Name ); + if (currentLanguage == null) + { + var abpRequestLocalizationOptionsProvider = HttpContext.RequestServices.GetRequiredService(); + var localizationOptions = await abpRequestLocalizationOptionsProvider.GetLocalizationOptionsAsync(); + if (localizationOptions.DefaultRequestCulture != null) + { + currentLanguage = new LanguageInfo( + localizationOptions.DefaultRequestCulture.Culture.Name, + localizationOptions.DefaultRequestCulture.UICulture.Name, + localizationOptions.DefaultRequestCulture.UICulture.DisplayName); + } + else + { + currentLanguage = new LanguageInfo( + CultureInfo.CurrentCulture.Name, + CultureInfo.CurrentUICulture.Name, + CultureInfo.CurrentUICulture.DisplayName); + } + } + var model = new LanguageSwitchViewComponentModel { CurrentLanguage = currentLanguage, OtherLanguages = languages.Where(l => l != currentLanguage).ToList() }; - + return View("~/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml", model); } } From acf161bd2134dfe9d26fa38d3404432788cf0768 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Thu, 23 Jul 2020 14:37:06 +0800 Subject: [PATCH 15/38] Filter permission provider in PermissionDataSeedContributor. Resolve #4841 --- .../PermissionDataSeedContributor.cs | 1 + .../PermissionDataSeedContributor_Tests.cs | 32 +++++++++++++++++++ .../TestPermissionDefinitionProvider.cs | 4 ++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs index 906192fd4f..ac8274d445 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs @@ -30,6 +30,7 @@ namespace Volo.Abp.PermissionManagement var permissionNames = PermissionDefinitionManager .GetPermissions() .Where(p => p.MultiTenancySide.HasFlag(multiTenancySide)) + .Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName)) .Select(p => p.Name) .ToArray(); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs new file mode 100644 index 0000000000..a888beb5f9 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Data; +using Xunit; + +namespace Volo.Abp.PermissionManagement +{ + public class PermissionDataSeedContributor_Tests : PermissionTestBase + { + private readonly PermissionDataSeedContributor _permissionDataSeedContributor; + private readonly IPermissionGrantRepository _grantpermissionGrantRepository; + + public PermissionDataSeedContributor_Tests() + { + _permissionDataSeedContributor = GetRequiredService(); + _grantpermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task SeedAsync() + { + (await _grantpermissionGrantRepository.FindAsync("MyPermission1", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + (await _grantpermissionGrantRepository.FindAsync("MyPermission4", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + + await _permissionDataSeedContributor.SeedAsync(new DataSeedContext(null)); + + (await _grantpermissionGrantRepository.FindAsync("MyPermission1", RolePermissionValueProvider.ProviderName, "admin")).ShouldNotBeNull(); + (await _grantpermissionGrantRepository.FindAsync("MyPermission4", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + } + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs index 584c4dd2be..c6de42ac3b 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs @@ -16,6 +16,8 @@ namespace Volo.Abp.PermissionManagement myPermission2.AddChild("MyPermission2.ChildPermission1"); testGroup.AddPermission("MyPermission3", multiTenancySide: MultiTenancySides.Host); + + testGroup.AddPermission("MyPermission4", multiTenancySide: MultiTenancySides.Host).WithProviders(UserPermissionValueProvider.ProviderName); } } -} \ No newline at end of file +} From 6d03d67741dc47b58dcce9a83eeed6e41899aabf Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 23 Jul 2020 14:54:57 +0300 Subject: [PATCH 16/38] feat: create RootThemeBasicModule and import NgxValidateCoreModule to it resolves #4858 --- .../theme-basic/src/lib/theme-basic.module.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts index f599124e83..bfacbcf9a7 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts @@ -42,6 +42,20 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt NgbCollapseModule, NgbDropdownModule, NgxValidateCoreModule, + ], + entryComponents: [...LAYOUTS, ValidationErrorComponent, CurrentUserComponent, LanguagesComponent], +}) +export class ThemeBasicModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: RootThemeBasicModule, + providers: [BASIC_THEME_NAV_ITEM_PROVIDERS, BASIC_THEME_STYLES_PROVIDERS], + }; + } +} + +@NgModule({ + imports: [ NgxValidateCoreModule.forRoot({ targetSelector: '.form-group', blueprints: { @@ -63,13 +77,5 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt errorTemplate: ValidationErrorComponent, }), ], - entryComponents: [...LAYOUTS, ValidationErrorComponent, CurrentUserComponent, LanguagesComponent], }) -export class ThemeBasicModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: ThemeBasicModule, - providers: [BASIC_THEME_NAV_ITEM_PROVIDERS, BASIC_THEME_STYLES_PROVIDERS], - }; - } -} +export class RootThemeBasicModule {} From c0230b7ccc1eb6a77d293909c46f5684fc5b4803 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 24 Jul 2020 06:01:08 +0300 Subject: [PATCH 17/38] feat: add support for mapping of culture name to Angular locale file name resolves #4867 --- npm/ng-packs/packages/core/src/lib/core.module.ts | 9 +++++++-- .../packages/core/src/lib/models/common.ts | 1 + .../core/src/lib/services/localization.service.ts | 4 +++- .../packages/core/src/lib/tokens/options.token.ts | 11 +++++++++++ .../packages/core/src/lib/utils/initial-utils.ts | 14 ++++++++------ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index f118ab2836..3fd29fec53 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -34,7 +34,7 @@ import { ConfigState } from './states/config.state'; import { ProfileState } from './states/profile.state'; import { ReplaceableComponentsState } from './states/replaceable-components.state'; import { SessionState } from './states/session.state'; -import { CORE_OPTIONS } from './tokens/options.token'; +import { CORE_OPTIONS, coreOptionsFactory } from './tokens/options.token'; import { noop } from './utils/common-utils'; import './utils/date-extensions'; import { getInitialData, localeInitializer, configureOAuth } from './utils/initial-utils'; @@ -171,9 +171,14 @@ export class CoreModule { useValue: { environment: options.environment }, }, { - provide: CORE_OPTIONS, + provide: 'CORE_OPTIONS', useValue: options, }, + { + provide: CORE_OPTIONS, + useFactory: coreOptionsFactory, + deps: ['CORE_OPTIONS'], + }, { provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 930265020c..aa2f961b7b 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -9,6 +9,7 @@ export namespace ABP { environment: Partial; skipGetAppConfiguration?: boolean; sendNullsAsQueryParam?: boolean; + cultureNameToLocaleFileNameMapping?: Dictionary; } export interface Test { diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 3f89642fd9..e488bafaf4 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -8,6 +8,7 @@ import { Config } from '../models/config'; import { ConfigState } from '../states/config.state'; import { registerLocale } from '../utils/initial-utils'; import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; +import { CORE_OPTIONS } from '../tokens/options.token'; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; @@ -44,11 +45,12 @@ export class LocalizationService { registerLocale(locale: string) { const router = this.injector.get(Router); + const { cultureNameToLocaleFileNameMapping: localeNameMap } = this.injector.get(CORE_OPTIONS); const { shouldReuseRoute } = router.routeReuseStrategy; router.routeReuseStrategy.shouldReuseRoute = () => false; router.navigated = false; - return registerLocale(locale).then(() => { + return registerLocale(locale, localeNameMap).then(() => { this.ngZone.run(async () => { await router.navigateByUrl(router.url).catch(noop); router.routeReuseStrategy.shouldReuseRoute = shouldReuseRoute; diff --git a/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts b/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts index 2c4dffe9dc..a4b05c8cc7 100644 --- a/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts +++ b/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts @@ -1,4 +1,15 @@ import { InjectionToken } from '@angular/core'; import { ABP } from '../models/common'; +import differentLocales from '../constants/different-locales'; export const CORE_OPTIONS = new InjectionToken('CORE_OPTIONS'); + +export function coreOptionsFactory({ + cultureNameToLocaleFileNameMapping: localeNameMap = {}, + ...options +}: ABP.Root) { + return { + ...options, + cultureNameToLocaleFileNameMapping: { ...differentLocales, ...localeNameMap }, + } as ABP.Root; +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index bfca50bcd2..78677b7ca4 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -4,7 +4,6 @@ import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { tap } from 'rxjs/operators'; import { GetAppConfiguration } from '../actions/config.actions'; -import differentLocales from '../constants/different-locales'; import { ABP } from '../models/common'; import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens/options.token'; @@ -45,22 +44,25 @@ function checkAccessToken(store: Store, injector: Injector) { export function localeInitializer(injector: Injector) { const fn = () => { const store: Store = injector.get(Store); + const options = injector.get(CORE_OPTIONS); const lang = store.selectSnapshot(state => state.SessionState.language) || 'en'; return new Promise((resolve, reject) => { - registerLocale(lang).then(() => resolve('resolved'), reject); + registerLocale(lang, options.cultureNameToLocaleFileNameMapping).then( + () => resolve('resolved'), + reject, + ); }); }; return fn; } -export function registerLocale(locale: string) { +export function registerLocale(locale: string, localeNameMap: ABP.Dictionary) { return import( - /* webpackInclude: /(af|ar|am|ar-SA|as|az-Latn|be|bg|bn-BD|bn-IN|bs|ca|ca-ES-VALENCIA|cs|cy|da|de|de|el|en-GB|en|es|en|es-US|es-MX|et|eu|fa|fi|en|fr|fr|fr-CA|ga|gd|gl|gu|ha|he|hi|hr|hu|hy|id|ig|is|it|it|ja|ka|kk|km|kn|ko|kok|en|en|lb|lt|lv|en|mk|ml|mn|mr|ms|mt|nb|ne|nl|nl-BE|nn|en|or|pa|pa-Arab|pl|en|pt|pt-PT|en|en|ro|ru|rw|pa-Arab|si|sk|sl|sq|sr-Cyrl-BA|sr-Cyrl|sr-Latn|sv|sw|ta|te|tg|th|ti|tk|tn|tr|tt|ug|uk|ur|uz-Latn|vi|wo|xh|yo|zh-Hans|zh-Hant|zu)\.js$/ */ - /* webpackChunkName: "[request]"*/ - `@angular/common/locales/${differentLocales[locale] || locale}.js` + /* webpackChunkName: "_locale-[request]"*/ + `@angular/common/locales/${localeNameMap[locale] || locale}.js` ).then(module => { registerLocaleData(module.default); }); From 6b2cb684ce30a039931ca367f664b7b6e951fef0 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 24 Jul 2020 06:03:14 +0300 Subject: [PATCH 18/38] docs: add Mapping of Culture Name to Angular Locale File Name topic to Localization.md #4867 --- docs/en/UI/Angular/Localization.md | 29 +++++++++++++++++++++ docs/en/UI/Angular/images/locale-error.png | Bin 0 -> 19175 bytes 2 files changed, 29 insertions(+) create mode 100644 docs/en/UI/Angular/images/locale-error.png diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 78822a16de..d3215bd010 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -193,6 +193,35 @@ import { Component } from '@angular/core'; export class AppComponent {} ``` +## Mapping of Culture Name to Angular Locale File Name + +Some of the culture names defined in .NET do not match Angular locales. In such cases, the Angular app throws an error like below at runtime: + +![locale-error](./images/locale-error.png) + +If you see the error like above, you should pass the `cultureNameToLocaleFileNameMapping` property like below to CoreModule's forRoot static method. + +```js +// app.module.ts + +@NgModule({ + imports: [ + // other imports + CoreModule.forRoot({ + // other options + cultureNameToLocaleFileNameMapping: { + "X": "Y", + "AnotherLocaleNameDefinedInDotnet": "AnotherLocaleNameDefinedInAngular" + } + }) + //... +``` + +- The key indicated by "X" above represents the culture name defined in .NET (e.g. "en-US"). +- The value indicated by "Y" above represents the locale file name defined in Angular (e.g. "en"). + +See the [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales) + ## See Also diff --git a/docs/en/UI/Angular/images/locale-error.png b/docs/en/UI/Angular/images/locale-error.png new file mode 100644 index 0000000000000000000000000000000000000000..de385a59c021b876249fe9886d6758dd81b1d2b1 GIT binary patch literal 19175 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 Date: Fri, 24 Jul 2020 10:00:14 +0300 Subject: [PATCH 19/38] docs: update Localization.md --- docs/en/UI/Angular/Localization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index d3215bd010..94660c6f44 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -211,7 +211,7 @@ If you see the error like above, you should pass the `cultureNameToLocaleFileNam // other options cultureNameToLocaleFileNameMapping: { "X": "Y", - "AnotherLocaleNameDefinedInDotnet": "AnotherLocaleNameDefinedInAngular" + "AnotherCultureNameDefinedInDotnet": "AnotherLocaleFileNameDefinedInAngular" } }) //... From c16b25fc9948157819465120c0c0cad04d31e344 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Fri, 24 Jul 2020 10:34:23 +0300 Subject: [PATCH 20/38] Update Text-Templating.md --- docs/en/Text-Templating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Text-Templating.md b/docs/en/Text-Templating.md index c71787f598..3008db5c41 100644 --- a/docs/en/Text-Templating.md +++ b/docs/en/Text-Templating.md @@ -4,7 +4,7 @@ ABP Framework provides a simple, yet efficient text template system. Text templating is used to dynamically render contents based on a template and a model (a data object): -***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** +Template + Model =renderer=> Rendered Content It is very similar to an ASP.NET Core Razor View (or Page): @@ -454,4 +454,4 @@ Return `null` if your source can not find the content, so `ITemplateContentProvi * [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. * [Localization system](Localization.md). -* [Virtual File System](Virtual-File-System.md). \ No newline at end of file +* [Virtual File System](Virtual-File-System.md). From e3c81fed142a98c1a66300709260b6b31b798a64 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 24 Jul 2020 12:01:26 +0300 Subject: [PATCH 21/38] docs: update Localization.md --- docs/en/UI/Angular/Localization.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 94660c6f44..2282a40598 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -207,19 +207,16 @@ If you see the error like above, you should pass the `cultureNameToLocaleFileNam @NgModule({ imports: [ // other imports - CoreModule.forRoot({ + CoreModule.forRoot({ // other options cultureNameToLocaleFileNameMapping: { - "X": "Y", - "AnotherCultureNameDefinedInDotnet": "AnotherLocaleFileNameDefinedInAngular" + "DotnetCultureName": "AngularLocaleFileName", + "pt-BR": "pt" // example } }) //... ``` -- The key indicated by "X" above represents the culture name defined in .NET (e.g. "en-US"). -- The value indicated by "Y" above represents the locale file name defined in Angular (e.g. "en"). - See the [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales) From aaf3c7d47f3034a32093893475992e1963c9403b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 24 Jul 2020 12:02:50 +0300 Subject: [PATCH 22/38] docs: add a dot --- docs/en/UI/Angular/Localization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 2282a40598..af80ccf965 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -217,7 +217,7 @@ If you see the error like above, you should pass the `cultureNameToLocaleFileNam //... ``` -See the [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales) +See the [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales). ## See Also From 7011863bbee54934d007f929343bfcbeae53fd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Fri, 24 Jul 2020 12:04:14 +0300 Subject: [PATCH 23/38] Update Localization.md --- docs/en/UI/Angular/Localization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index af80ccf965..1ee68ace82 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -199,7 +199,7 @@ Some of the culture names defined in .NET do not match Angular locales. In such ![locale-error](./images/locale-error.png) -If you see the error like above, you should pass the `cultureNameToLocaleFileNameMapping` property like below to CoreModule's forRoot static method. +If you see an error like this, you should pass the `cultureNameToLocaleFileNameMapping` property like below to CoreModule's forRoot static method. ```js // app.module.ts @@ -217,7 +217,7 @@ If you see the error like above, you should pass the `cultureNameToLocaleFileNam //... ``` -See the [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales). +See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales). ## See Also @@ -226,4 +226,4 @@ See the [all locale files in Angular](https://github.com/angular/angular/tree/ma ## What's Next? -* [Permission Management](./Permission-Management.md) \ No newline at end of file +* [Permission Management](./Permission-Management.md) From b4906e94843d9db9f139d1350430c0aa83966bac Mon Sep 17 00:00:00 2001 From: Erol Arkat Date: Fri, 24 Jul 2020 13:42:27 +0300 Subject: [PATCH 24/38] added -f parameter for test-all.ps1 --- build/test-all.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/test-all.ps1 b/build/test-all.ps1 index 9123aee313..9a94ec3760 100644 --- a/build/test-all.ps1 +++ b/build/test-all.ps1 @@ -1,4 +1,6 @@ -. ".\common.ps1" -f +$full = $args[0] + +. ".\common.ps1" $full # Test all solutions From 1a1dc3e0da20e447234350a528ddf818a0639160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Jul 2020 13:55:48 +0300 Subject: [PATCH 25/38] Fix Account.cshtml --- .../Themes/Basic/Layouts/Account.cshtml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index b4ae34d8fb..5fcd5951cd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -39,8 +39,10 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - + @if (ViewBag.Description != null) + { + + } @await RenderSectionAsync("styles", false) From 276a48a240a62a53bccba60ba7ef08c0439ca5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Jul 2020 14:49:17 +0300 Subject: [PATCH 26/38] Resolved #4862 Support async object mapping for the base CRUD application service. --- .../Services/AbstractKeyCrudAppService.cs | 39 ++++++++++++++-- .../Services/AbstractKeyReadOnlyAppService.cs | 46 ++++++++++++++++++- .../Application/Services/CrudAppService.cs | 5 ++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs index 11550c456f..91879f030e 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; @@ -51,6 +52,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); @@ -80,13 +86,13 @@ namespace Volo.Abp.Application.Services { await CheckCreatePolicyAsync(); - var entity = MapToEntity(input); + var entity = await MapToEntityAsync(input); TryToSetTenantId(entity); await Repository.InsertAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task UpdateAsync(TKey id, TUpdateInput input) @@ -95,10 +101,10 @@ namespace Volo.Abp.Application.Services var entity = await GetEntityByIdAsync(id); //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); + await MapToEntityAsync(input, entity); await Repository.UpdateAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task DeleteAsync(TKey id) @@ -125,6 +131,17 @@ namespace Volo.Abp.Application.Services await CheckPolicyAsync(DeletePolicyName); } + /// + /// Maps to to create a new entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TCreateInput createInput) + { + return Task.FromResult(MapToEntity(createInput)); + } + /// /// Maps to to create a new entity. /// It uses by default. @@ -153,6 +170,18 @@ namespace Volo.Abp.Application.Services } } + /// + /// Maps to to update the entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TUpdateInput updateInput, TEntity entity) + { + MapToEntity(updateInput, entity); + return Task.CompletedTask; + } + /// /// Maps to to update the entity. /// It uses by default. @@ -190,4 +219,4 @@ namespace Volo.Abp.Application.Services return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 9085434211..5781fb1fa3 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services { @@ -52,7 +54,8 @@ namespace Volo.Abp.Application.Services await CheckGetPolicyAsync(); var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); + + return await MapToGetOutputDtoAsync(entity); } public virtual async Task> GetListAsync(TGetListInput input) @@ -67,10 +70,11 @@ namespace Volo.Abp.Application.Services query = ApplyPaging(query, input); var entities = await AsyncExecuter.ToListAsync(query); + var entityDtos = await MapToGetListOutputDtosAsync(entities); return new PagedResultDto( totalCount, - entities.Select(MapToGetListOutputDto).ToList() + entityDtos ); } @@ -161,6 +165,17 @@ namespace Volo.Abp.Application.Services return ReadOnlyRepository; } + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. @@ -171,6 +186,33 @@ namespace Volo.Abp.Application.Services return ObjectMapper.Map(entity); } + /// + /// Maps a list of to objects. + /// It uses method for each item in the list. + /// + protected virtual async Task> MapToGetListOutputDtosAsync(List entities) + { + var dtos = new List(); + + foreach (var entity in entities) + { + dtos.Add(await MapToGetListOutputDtoAsync(entity)); + } + + return dtos; + } + + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetListOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetListOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 7832e8a4ab..6c06cf95fe 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -55,6 +55,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return base.MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); From 27050189dc6653c851fa99ce191013c600a2bfad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Jul 2020 15:27:04 +0300 Subject: [PATCH 27/38] Check IHasCreationTime instead of ICreationAuditedObject --- .../Abp/Application/Services/AbstractKeyReadOnlyAppService.cs | 4 ++-- .../Volo/Abp/Application/Services/CrudAppService.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 5781fb1fa3..b9ffe3359b 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -122,9 +122,9 @@ namespace Volo.Abp.Application.Services /// The query. protected virtual IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 6c06cf95fe..db66881fb7 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -102,9 +102,9 @@ namespace Volo.Abp.Application.Services protected override IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } else { From 07728499717d5dc4a2cb647a3fa36d2742b056b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Jul 2020 15:27:51 +0300 Subject: [PATCH 28/38] Add more info about the CRUD app services. --- docs/en/Application-Services.md | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md index d28a7290fb..9cfec614cb 100644 --- a/docs/en/Application-Services.md +++ b/docs/en/Application-Services.md @@ -367,6 +367,84 @@ public class DistrictKey } ```` +### Authorization (for CRUD App Services) + +There are two ways of authorizing the base application service methods; + +1. You can set the policy properties (xxxPolicyName) in the constructor of your service. Example: + +```csharp +public class MyPeopleAppService : CrudAppService +{ + public MyPeopleAppService(IRepository repository) + : base(repository) + { + GetPolicyName = "..."; + GetListPolicyName = "..."; + CreatePolicyName = "..."; + UpdatePolicyName = "..."; + DeletePolicyName = "..."; + } +} +``` + +`CreatePolicyName` is checked by the `CreateAsync` method and so on... You should specify a policy (permission) name defined in your application. + +2. You can override the check methods (CheckXxxPolicyAsync) in your service. Example: + +```csharp +public class MyPeopleAppService : CrudAppService +{ + public MyPeopleAppService(IRepository repository) + : base(repository) + { + } + + protected override async Task CheckDeletePolicyAsync() + { + await AuthorizationService.CheckAsync("..."); + } +} +``` + +You can perform any logic in the `CheckDeletePolicyAsync` method. It is expected to throw an `AbpAuthorizationException` in any unauthorized case, like `AuthorizationService.CheckAsync` already does. + +### Base Properties & Methods + +CRUD application service base class provides many useful base methods that **you can override** to customize it based on your requirements. + +#### CRUD Methods + +These are the essential CRUD methods. You can override any of them to completely customize the operation. Here, the definitions of the methods: + +````csharp +Task GetAsync(TKey id); +Task> GetListAsync(TGetListInput input); +Task CreateAsync(TCreateInput input); +Task UpdateAsync(TKey id, TUpdateInput input); +Task DeleteAsync(TKey id); +```` + +#### Querying + +These methods are low level methods those can be control how to query entities from the database. + +* `CreateFilteredQuery` can be overridden to create an `IQueryable` that is filtered by the given input. If your `TGetListInput` class contains any filter, it is proper to override this method and filter the query. It returns the (unfiltered) repository (which is already `IQueryable`) by default. +* `ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP Framework automatically understands it and performs the paging. +* `ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP Framework automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creating time, if your entity implements the standard `IHasCreationTime` interface. +* `GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default. +* `DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default. + +#### Object to Object Mapping + +These methods are used to convert Entities to DTOs and vice verse. They uses the [IObjectMapper](Object-To-Object-Mapping.md) by default. + +* `MapToGetOutputDtoAsync` is used to map the entity to the DTO returned from the `GetAsync`, `CreateAsync` and `UpdateAsync` methods. Alternatively, you can override the `MapToGetOutputDto` if you don't need to perform any async operation. +* `MapToGetListOutputDtosAsync` is used to map a list of entities to a list of DTOs returned from the `GetListAsync` method. It uses the `MapToGetListOutputDtoAsync` to map each entity in the list. You can override one of them based on your case. Alternatively, you can override the `MapToGetListOutputDto` if you don't need to perform any async operation. +* `MapToEntityAsync` method has two overloads; + * `MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`. + * `MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`. + ## Lifetime Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system. From f5ab58632a5fab56221af36c08edc563e9a5c17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Jul 2020 17:33:21 +0300 Subject: [PATCH 29/38] Fix link. --- docs/en/Tutorials/Part-2.md | 2 +- docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index 32f54c1d04..1f3b6c84d1 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -56,7 +56,7 @@ This tutorials has multiple versions based on your **UI** and **Database** prefe It's common to call the HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way. -ABP **dynamically** creates **[JavaScript Proxies](../UI/AspNetCore/)** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. +ABP **dynamically** creates **[JavaScript Proxies](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. ### Testing in the Developer Console diff --git a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md new file mode 100644 index 0000000000..391a63a910 --- /dev/null +++ b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md @@ -0,0 +1,3 @@ +# Dynamic JavaScript HTTP API Proxies + +TODO \ No newline at end of file From c2b1cb9c5b0417b7471bf44195581c041359b663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 26 Jul 2020 17:49:12 +0300 Subject: [PATCH 30/38] Revert #4630: Remove AutoMapper to the Application.Contracts project. --- ...nyName.MyProjectName.Application.Contracts.csproj | 1 - .../MyProjectNameApplicationContractsModule.cs | 12 +----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj index f9c3f9a045..fe2e40eb14 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj @@ -13,7 +13,6 @@ - diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs index d8e848e86b..5c3ba41364 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs @@ -1,5 +1,4 @@ using Volo.Abp.Account; -using Volo.Abp.AutoMapper; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -16,8 +15,7 @@ namespace MyCompanyName.MyProjectName typeof(AbpIdentityApplicationContractsModule), typeof(AbpPermissionManagementApplicationContractsModule), typeof(AbpTenantManagementApplicationContractsModule), - typeof(AbpObjectExtendingModule), - typeof(AbpAutoMapperModule) + typeof(AbpObjectExtendingModule) )] public class MyProjectNameApplicationContractsModule : AbpModule { @@ -25,13 +23,5 @@ namespace MyCompanyName.MyProjectName { MyProjectNameDtoExtensions.Configure(); } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } } From f2a722314e7b9aaf0d109bca33493b14fefbe331 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:28:25 +0800 Subject: [PATCH 31/38] Update Part-3.md --- docs/zh-Hans/Tutorials/Part-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md index cded954e6b..c9eb3bac78 100644 --- a/docs/zh-Hans/Tutorials/Part-3.md +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -23,7 +23,7 @@ end ### 关于本教程 -这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章: +这是ASP.NET Core{{UI_Value}}系列教程的第三章. 共有三章: - [Part-1: 创建项目和书籍列表页面](Part-1.md) - [Part 2: 创建,编辑,删除书籍](Part-2.md) From dca1b6af7a9e1cc561e9ad30afba8f1febd3ab6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 27 Jul 2020 11:30:57 +0300 Subject: [PATCH 32/38] Remove unused namespaces. --- .../Mvc/AspNetCoreApiDescriptionModelProvider.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index f5ea41d765..082a803cd9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -13,7 +12,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; -using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; @@ -67,7 +65,7 @@ namespace Volo.Abp.AspNetCore.Mvc private void AddApiDescriptionToModel( ApiDescription apiDescription, - ApplicationApiDescriptionModel applicationModel, + ApplicationApiDescriptionModel applicationModel, ApplicationApiDescriptionModelRequestDto input) { var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); @@ -182,7 +180,7 @@ namespace Volo.Abp.AspNetCore.Mvc type = AsyncHelper.UnwrapTask(type); if (type == typeof(object) || - type == typeof(void) || + type == typeof(void) || type == typeof(Enum) || type == typeof(ValueType) || TypeHelper.IsPrimitiveExtended(type)) @@ -212,7 +210,7 @@ namespace Volo.Abp.AspNetCore.Mvc { return; } - + var typeModel = TypeApiDescriptionModel.Create(type); applicationModel.Types[typeName] = typeModel; From d04910fdb6d9835aa44ef5350f040ba57107d664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 27 Jul 2020 15:50:22 +0300 Subject: [PATCH 33/38] Properly handle dictionaries for API description model. --- .../AspNetCoreApiDescriptionModelProvider.cs | 43 +++++++++++++------ .../MethodParameterApiDescriptionModel.cs | 6 +-- .../Modeling/PropertyApiDescriptionModel.cs | 16 +++---- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 082a803cd9..b5c5699757 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -68,12 +68,24 @@ namespace Volo.Abp.AspNetCore.Mvc ApplicationApiDescriptionModel applicationModel, ApplicationApiDescriptionModelRequestDto input) { - var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); + var controllerType = apiDescription + .ActionDescriptor + .AsControllerActionDescriptor() + .ControllerTypeInfo; + var setting = FindSetting(controllerType); - var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting), GetRemoteServiceName(controllerType, setting)); + var moduleModel = applicationModel.GetOrAddModule( + GetRootPath(controllerType, setting), + GetRemoteServiceName(controllerType, setting) + ); - var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); + var controllerModel = moduleModel.GetOrAddController( + controllerType.FullName, + CalculateControllerName(controllerType, setting), + controllerType, + _modelOptions.IgnoredInterfaces + ); var method = apiDescription.ActionDescriptor.GetMethodInfo(); @@ -86,13 +98,16 @@ namespace Volo.Abp.AspNetCore.Mvc Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); - var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( + var actionModel = controllerModel.AddAction( uniqueMethodName, - method, - apiDescription.RelativePath, - apiDescription.HttpMethod, - GetSupportedVersions(controllerType, method, setting) - )); + ActionApiDescriptionModel.Create( + uniqueMethodName, + method, + apiDescription.RelativePath, + apiDescription.HttpMethod, + GetSupportedVersions(controllerType, method, setting) + ) + ); if (input.IncludeTypes) { @@ -188,16 +203,16 @@ namespace Volo.Abp.AspNetCore.Mvc return; } - if (TypeHelper.IsEnumerable(type, out var itemType)) + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) { - AddCustomTypesToModel(applicationModel, itemType); + AddCustomTypesToModel(applicationModel, keyType); + AddCustomTypesToModel(applicationModel, valueType); return; } - if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + if (TypeHelper.IsEnumerable(type, out var itemType)) { - AddCustomTypesToModel(applicationModel, keyType); - AddCustomTypesToModel(applicationModel, valueType); + AddCustomTypesToModel(applicationModel, itemType); return; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 211359d789..603d3994e0 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -30,11 +30,11 @@ namespace Volo.Abp.Http.Modeling { Name = parameterInfo.Name, TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), - Type = parameterInfo.ParameterType != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType) : null, - TypeSimple = parameterInfo.ParameterType != null ? TypeHelper.GetSimplifiedName(parameterInfo.ParameterType) : null, + Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType), + TypeSimple = TypeHelper.GetSimplifiedName(parameterInfo.ParameterType), IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs index 820d9a46f9..80a3cee91a 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -19,22 +19,22 @@ namespace Volo.Abp.Http.Modeling string typeName; string simpleTypeName; - if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) - { - typeName = $"[{TypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; - simpleTypeName = $"[{TypeHelper.GetSimplifiedName(itemType)}]"; - } - else if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) + if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) { typeName = $"{{{TypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{TypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}"; simpleTypeName = $"{{{TypeHelper.GetSimplifiedName(keyType)}:{TypeHelper.GetSimplifiedName(valueType)}}}"; } + else if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) + { + typeName = $"[{TypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; + simpleTypeName = $"[{TypeHelper.GetSimplifiedName(itemType)}]"; + } else { typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType); simpleTypeName = TypeHelper.GetSimplifiedName(propertyInfo.PropertyType); } - + return new PropertyApiDescriptionModel { Name = propertyInfo.Name, @@ -43,4 +43,4 @@ namespace Volo.Abp.Http.Modeling }; } } -} \ No newline at end of file +} From 6d4c98a09601c5251a0534112b0bfdfaa0180063 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 27 Jul 2020 21:29:19 +0800 Subject: [PATCH 34/38] Update Chinese document --- docs/zh-Hans/Authorization.md | 2 +- docs/zh-Hans/CLI.md | 11 +- docs/zh-Hans/Getting-Started-React-Native.md | 70 ++++++++++++ docs/zh-Hans/Getting-Started.md | 114 +++++-------------- docs/zh-Hans/Index.md | 14 +-- docs/zh-Hans/Modules/Identity.md | 2 + docs/zh-Hans/Modules/Index.md | 2 +- docs/zh-Hans/Modules/Organization-Units.md | 47 ++++++++ docs/zh-Hans/Nightly-Builds.md | 4 +- docs/zh-Hans/Options.md | 2 +- docs/zh-Hans/Repositories.md | 2 - docs/zh-Hans/Road-Map.md | 1 - docs/zh-Hans/Samples/Index.md | 4 +- docs/zh-Hans/Samples/Microservice-Demo.md | 8 +- docs/zh-Hans/SignalR-Integration.md | 4 +- docs/zh-Hans/Text-Templating.md | 2 +- docs/zh-Hans/docs-nav.json | 13 +++ 17 files changed, 190 insertions(+), 112 deletions(-) create mode 100644 docs/zh-Hans/Getting-Started-React-Native.md create mode 100644 docs/zh-Hans/Modules/Organization-Units.md diff --git a/docs/zh-Hans/Authorization.md b/docs/zh-Hans/Authorization.md index 99139be3ac..2c269ab5fa 100644 --- a/docs/zh-Hans/Authorization.md +++ b/docs/zh-Hans/Authorization.md @@ -285,7 +285,7 @@ public async Task CreateAsync(CreateAuthorDto input) abp.auth.isGranted('MyPermissionName'); ```` -参阅 [abp.auth](AspNetCore/JavaScript-API/Auth.md) API 文档了解详情. +参阅 [abp.auth](UI/AspNetCore/JavaScript-API/Index.md) API 文档了解详情. ## 权限管理 diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index e733b6e2e5..4af252e89f 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -90,9 +90,10 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本. -* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://.zip`). +* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时). +* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat` 或 `https://.../my-template-file.zip`). * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. -* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串! +* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后). * `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用. ### add-package @@ -173,15 +174,15 @@ abp update [options] ### 切换到每晚构建(预览)包 -想要切换到ABP框架的最新预览版可以使用此命令. +想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令. 用法: ````bash -abp switch-to-preview [options] +abp switch-to-nightly [options] ```` -你也可以使用切换回稳定版本: +你也可以使用切换回最新稳定版本: ````bash abp switch-to-stable [options] diff --git a/docs/zh-Hans/Getting-Started-React-Native.md b/docs/zh-Hans/Getting-Started-React-Native.md new file mode 100644 index 0000000000..54d093cb7c --- /dev/null +++ b/docs/zh-Hans/Getting-Started-React-Native.md @@ -0,0 +1,70 @@ +# React Native入门 + +ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. + +当你按照[入门文档](Getting-Started.md)中所述**创建新应用程序**时,解决方案默认将React Native应用程序包含在 `react-native` 文件夹中. + +## 配置你的本地IP地址 + +运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端. + +{{ if Tiered == "No"}} +![React Native host project local IP entry](images/rn-host-local-ip.png) + +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ else if Tiered == "Yes" }} + +![React Native tiered project local IP entry](images/rn-tiered-local-ip.png) + +* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ end }} + +按照**运行HTTP API Host (服务端口)**那样运行后端. + +> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP. + +在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). + +```bash +yarn +``` + +* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址: + +![react native environment local IP](images/rn-environment-local-ip.png) + +{{ if Tiered == "Yes" }} + +> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配. + +{{else}} + +> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配 + +{{ end }} + +等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: + +```bash +yarn start +``` + +等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面. + +![expo-interface](images/rn-expo-interface.png) + +在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序. + +> 请参阅expo.io上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/). + +![React Native login screen on iPhone 11](images/rn-login-iphone.png) + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md index adf9517eec..fe1a9ec6f9 100644 --- a/docs/zh-Hans/Getting-Started.md +++ b/docs/zh-Hans/Getting-Started.md @@ -19,24 +19,27 @@ 你需要安装以下工具: -* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) +* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).[1](#f-editor) +* [.NET Core 3.1+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) +* [Node v12 或 v14](https://nodejs.org/en/) * [Yarn v1.19+](https://classic.yarnpkg.com/) +* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [2](#f-yarn) 或 npm v6+ (与Node一起安装) {{ if Tiered == "Yes" }} -* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis. +* [Redis](https://redis.io/): 入门解决方案将Redis用作[分布式缓存](Caching.md). 因此你需要安装并运行Redis. {{ end }} -> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. +1 _只要支持.NET Core和ASP.NET Core,就可以使用其他编辑器代替Visual Studio._ [↩](#a-editor) + +2 _Yarn v2 的工作方式不同,不受支持._ [↩](#a-yarn) ### 安装ABP CLI -[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. +[ABP CLI](./CLI.md)是一个命令行页面,用于自动执行一些基于ABP的应用程序的常见任务. -> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. +> ABP CLI是ABP框架一个免费开源的工具. 你需要使用以下命令安排ABP CLI: @@ -59,10 +62,10 @@ dotnet tool update -g Volo.Abp.Cli 使用ABP CLI的 `new` 命令创建新项目: ````shell -abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} +abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} ```` -* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md). +* 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它,或者在 `abp new` 命令中指定 `-m none` 选项,以使其完全不包含在解决方案中. {{ if UI == "NG" }} @@ -70,7 +73,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon {{ if Tiered == "Yes" }} -* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. +* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,则服务器上将只有一个端点. {{ end }} @@ -90,6 +93,12 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon > 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. +#### ABP CLI 命令 & 选项 + +[ABP CLI文档](./CLI.md)涵盖了ABP CLI的所有可用命令和选项. 本文档使用[应用程序启动模板](Startup-Templates/Application.md)创建新的Web应用程序. 有关其他模板,请参见[ABP启动模板](Startup-Templates/Index.md)文档. + +> 或者,您可以从[ABP Framework网站](https://abp.io/get-started)中选择"直接下载"选项卡创建新的解决方案. + ## 解决方案结构 {{ if UI == "MVC" }} @@ -146,7 +155,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon ## 创建数据库 -### 数据库连接字符串 +### 连接字符串 检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: @@ -214,7 +223,9 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 这将基于配置的连接字符串创建新数据库. -> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. +> **使用`.DbMigrator`工具是建议的方法**, 因为它能初始化初始数据能够正确运行Web应用程序. +> +> 如果你只是使用 `Update-Database` 命令,你会得到一个空数据库,所以你无法登录到应用程序因为数据库中没有初始管理用户. 不需要种子数据库时,可以在开发期间使用 `Update-Database` 命令. 但是使用 `.DbMigrator` 应用程序会更简单,你始终可以使用它来迁移模式并为数据库添加种子. {{ else if DB == "Mongo" }} @@ -240,7 +251,7 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 ![db-migrator-output](images/db-migrator-output.png) -> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. +> 数据库创建后会初始化[种子数据](Data-Seeding.md), 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. {{ end }} @@ -336,83 +347,18 @@ yarn start {{ end }} -输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. - -![bookstore-home](images/bookstore-home.png) - -应用程序已经启动并执行,你可以基于该启动模板开发应用程序. +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序,应用程序已经启动并执行,你可以基于此启动模板开始开发应用程序. #### 移动开发 -ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. - -> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. - -运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端. - -{{ if Tiered == "No"}} -![React Native host project local IP entry](images/rn-host-local-ip.png) - -* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址. -* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. - -{{ else if Tiered == "Yes" }} - -![React Native tiered project local IP entry](images/rn-tiered-local-ip.png) - -* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址. -* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. -* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址. -* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. - -{{ end }} - -按照**运行HTTP API Host (服务端口)**那样运行后端. - -> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP. - -在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). - -```bash -yarn -``` - -* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址: - -![react native environment local IP](images/rn-environment-local-ip.png) - -{{ if Tiered == "Yes" }} - -> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配. - -{{else}} - -> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配 - -{{ end }} - -等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: - -```bash -yarn start -``` - -等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面. - -![expo-interface](images/rn-expo-interface.png) - -在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序. - -> 请参阅expo.io上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/). - -![React Native login screen on iPhone 11](images/rn-login-iphone.png) +当你创建一个新的应用程序时.该解决方案默认包含 `react-native`文件夹. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序. -输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. +如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. -应用程序已经启动并执行,你可以基于该启动模板开发应用程序. +> 你可以在ABP CLI中指定 `-m none` 选项,以使 `react-native` 目录完全不包含在解决方案中 -> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. +请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序. ## 下一步是什么? -[应用程序开发教程](Tutorials/Part-1.md) +[Web应用程序开发教程](Tutorials/Part-1.md) diff --git a/docs/zh-Hans/Index.md b/docs/zh-Hans/Index.md index 3dd728c846..d409f1e8ab 100644 --- a/docs/zh-Hans/Index.md +++ b/docs/zh-Hans/Index.md @@ -4,23 +4,17 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用程序开发,但也支持开发其他类型的应用程序. -浏览左侧导航菜单以深入了解文档. +浏览导航菜单以深入了解文档. ## 入门 -使用ABP开发新项目的最简单方法是使用启动模板: +使用ABP开发新Web应用程序的最简单方法是使用[入门](Getting-Started.md)教程: -* [ASP.NET Core MVC (Razor Pages) UI 启动模板](Getting-Started.md?UI=MVC&DB=EF&Tiered=No) -* [Angular UI 启动模板](Getting-Started.md?UI=NG&DB=EF&Tiered=No) - -如果你想从头开始(使用空项目),请手动安装ABP框架并使用以下教程: - -* [控制台应用程序](Getting-Started-Console-Application.md) -* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md) +然后你可以继续进行[Web应用程序开发教程](Tutorials/Part-1.md). ## 源码 -ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework/abp). +ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework). ## 贡献代码 diff --git a/docs/zh-Hans/Modules/Identity.md b/docs/zh-Hans/Modules/Identity.md index ea50006de9..f396de00c1 100644 --- a/docs/zh-Hans/Modules/Identity.md +++ b/docs/zh-Hans/Modules/Identity.md @@ -1,3 +1,5 @@ # 身份管理模块 +身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. + 参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Index.md b/docs/zh-Hans/Modules/Index.md index a24a95c121..dab2f838df 100644 --- a/docs/zh-Hans/Modules/Index.md +++ b/docs/zh-Hans/Modules/Index.md @@ -17,7 +17,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** * **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. * **Feature Management**: 用于保存和管理功能. -* **Identity**: 基于Microsoft Identity管理角色,用户和他们的权限. +* [**Identity**](Identity.md): 基于Microsoft Identity管理组织单元,角色,用户和他们的权限. * **Identity Server**: 集成了IdentityServer4. * **Permission Management**: 用于保存权限. * **Setting Management**: 用于保存设置. diff --git a/docs/zh-Hans/Modules/Organization-Units.md b/docs/zh-Hans/Modules/Organization-Units.md new file mode 100644 index 0000000000..8047b6a48d --- /dev/null +++ b/docs/zh-Hans/Modules/Organization-Units.md @@ -0,0 +1,47 @@ +# 组织单元管理 + +组织单元(OU)是"身份模块"的一部分,用于**对用户和实体进行分层分组**. + +### OrganizationUnit 实体 + +OU由 **OrganizationUnit** 实体表示. 实体有以下基本属性: + +- **TenantId**: OU租户的ID,为null代表是宿主OU. +- **ParentId**: OU的父亲Id,为null为根OU. +- **Code**: 租户唯一的分层字符串代码. +- **DisplayName**: OU的显示名称. + +OrganizationUnit实体的主键(Id)是 **Guid** 类型,派生自[**FullAuditedAggregateRoot**](../Entities.md)类. + +#### Organization 树 + +因为OU可以有父亲,租户所有的OU是一个**树**结构. 树有一些规则: + +- 可以有多个根(`ParentId` 为 `null`). +- OU的第一级子级数有限制(面说明的固定OU代码单位长度). + +#### OU Code + +OU代码由OrganizationUnit Manager自动生成和维护. 看起来像这样的字符串: + +"**00001.00042.00005**" + +此代码可用于轻松查询数据库中OU的所有子级(递归). 代码有一些规则: + +- 必须[租户](../Multi-Tenancy.md)**唯一**的. +- 同一OU的所有子代均以**父OU的代码开头**. +- 它是**固定长度**的,并且基于树中OU的级别,如示例中所示. +- 虽然OU代码是唯一的,但是如果移动OU,它是**可更改的**. +- 你必须通过Id而不是代码引用OU. + +### OrganizationUnit Manager + +可以注入 **OrganizationUnitManager** 管理OU. 常见的用例有: + +- 创建,更改或删除OU. +- 在OU树中移动OU. +- 获取有关OU树及其项的信息 + +#### 多租户 + +`OrganizationUnitManager` 设置为一次性为 **单个租户** 工作,默认是 **当前租户**. \ No newline at end of file diff --git a/docs/zh-Hans/Nightly-Builds.md b/docs/zh-Hans/Nightly-Builds.md index 84ea243b05..020d69e02e 100644 --- a/docs/zh-Hans/Nightly-Builds.md +++ b/docs/zh-Hans/Nightly-Builds.md @@ -29,13 +29,13 @@ 预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装: ```bash -abp switch-to-preview +abp switch-to-preview --npm ``` 如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本: ```bash -abp switch-to-stable +abp switch-to-stable --npm ``` 参阅 [ABP CLI 文档](./CLI.md) 了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Options.md b/docs/zh-Hans/Options.md index c88ff0adda..5bb4b29a8b 100644 --- a/docs/zh-Hans/Options.md +++ b/docs/zh-Hans/Options.md @@ -82,7 +82,7 @@ public class MyService : ITransientDependency 对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure` 和 `ExecutePreConfiguredActions` 扩展方法. 该模式的工作原理如下所述. -1. 你的模块中定义计划选项类. 例: +在你的模块中定义预先选项类. 例: ````csharp public class MyPreOptions diff --git a/docs/zh-Hans/Repositories.md b/docs/zh-Hans/Repositories.md index 18bfa5f77d..ee48186985 100644 --- a/docs/zh-Hans/Repositories.md +++ b/docs/zh-Hans/Repositories.md @@ -193,8 +193,6 @@ var people = ((IMongoQueryable)_personRepository ### 选项-3: IAsyncQueryableExecuter -> 注意,此功能在ABP框架3.0以之后的版本可用,虽然它也可以用于较早的版本,但它提供的方法非常有限. - `IAsyncQueryableExecuter` 是一个用于异步执行 `IQueryable` 对象的服务,**不依赖于实际的数据库提供程序**. **示例: 注入并使用 `IAsyncQueryableExecuter.ToListAsync()` 方法** diff --git a/docs/zh-Hans/Road-Map.md b/docs/zh-Hans/Road-Map.md index f18441cb41..d8a277ac0e 100644 --- a/docs/zh-Hans/Road-Map.md +++ b/docs/zh-Hans/Road-Map.md @@ -9,7 +9,6 @@ 除了中期目录,还有一些[积压](https://github.com/abpframework/abp/milestone/2)的功能, 这里积压中重要功能的列表: -* [#4098](https://github.com/abpframework/abp/issues/4098) / Blob存储Azure提供者. * [#2882](https://github.com/abpframework/abp/issues/2882) / 提供 **gRPC集成** 基础设施 (虽然[已经可以](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)为你的应用程序创建和使用gRPC端点,但我们计划为所有[标准应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)创建端点) * [#236](https://github.com/abpframework/abp/issues/236) 基于权限系统的资源 * [#1754](https://github.com/abpframework/abp/issues/1754) / 多语言实体 diff --git a/docs/zh-Hans/Samples/Index.md b/docs/zh-Hans/Samples/Index.md index c54b2e8ba6..94e5392b51 100644 --- a/docs/zh-Hans/Samples/Index.md +++ b/docs/zh-Hans/Samples/Index.md @@ -7,7 +7,7 @@ 演示如何基于微服务体系结构构建系统的完整解决方案. * [示例的文档](Microservice-Demo.md) -* [源码](https://github.com/abpframework/abp/tree/dev/samples/MicroserviceDemo) +* [源码](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo) * [微服务架构文档](../Microservice-Architecture.md) ### Book Store @@ -48,6 +48,8 @@ * **文本模板 Demo**: 文本模板系统的不同用例. * [源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) * [文本模板文档](../Text-Templating.md) +* **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数. + * [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) * **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案. * [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) * 相关 "[How To](../How-To/Index.md)" 文档: diff --git a/docs/zh-Hans/Samples/Microservice-Demo.md b/docs/zh-Hans/Samples/Microservice-Demo.md index b0134eddde..59977908a1 100644 --- a/docs/zh-Hans/Samples/Microservice-Demo.md +++ b/docs/zh-Hans/Samples/Microservice-Demo.md @@ -28,7 +28,7 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 ### 源码 -你可以从[GitHub仓库](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo)获取源码. +你可以从[GitHub仓库](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo)获取源码. ## 运行解决方案 @@ -43,6 +43,12 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (可选,建议显示日志) +### 运行基础设施 + +- 默认情况下运行Docker-compose可以轻松运行预先要求. 如果你没有安装,在windows环境下你可以在[这里](https://docs.docker.com/docker-for-windows/install/)下载并使用[Docker for Windows](https://docs.docker.com/docker-for-windows/). +* 在 `MicroserviceDemo` 目录下运行命令 `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` 或使用powershell运行 `MicroserviceDemo/_run` 文件夹下的 `__Run_Infrastructure.ps1` 脚本. +* 如果你不想使用docker运行预先要求的服务在你的本地环境,你需要更改微服务示例解决方案项目中的 `appsettings.json` 文件. + ### 打开并构建Visual Studio解决方案 * 在Visual Studio 2017 (15.9.0+)中打开`samples\MicroserviceDemo\MicroserviceDemo.sln`. diff --git a/docs/zh-Hans/SignalR-Integration.md b/docs/zh-Hans/SignalR-Integration.md index 5e17b1c75c..bdf4a7a786 100644 --- a/docs/zh-Hans/SignalR-Integration.md +++ b/docs/zh-Hans/SignalR-Integration.md @@ -115,7 +115,7 @@ public class MessagingHub : Hub } ```` -`MessasingHub` 集线器的路由为 `/signalr-hubs/messasing`: +`MessagingHub` 集线器的路由为 `/signalr-hubs/messaging`: * 添加了标准 `/signalr-hubs/` 前缀. * 使用**驼峰命名**集线器名称,不包含 `Hub` 后缀. @@ -123,7 +123,7 @@ public class MessagingHub : Hub 如果你想指定路由,你可以使用 `HubRoute` attribute: ````csharp -[HubRoute("/my-messasing-hub")] +[HubRoute("/my-messaging-hub")] public class MessagingHub : Hub { //... diff --git a/docs/zh-Hans/Text-Templating.md b/docs/zh-Hans/Text-Templating.md index 0785dbba29..be9cbb3a79 100644 --- a/docs/zh-Hans/Text-Templating.md +++ b/docs/zh-Hans/Text-Templating.md @@ -4,7 +4,7 @@ ABP框架提供了一个简单有效的文本模板系统,文本模板用于动态渲染基于模板和模型(数据对象)内容: -***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** +Template + Model =renderer=> Rendered Content 它非常类似于 ASP.NET Core Razor View (或 Page): diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index cae14bdae2..683bf750c0 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -446,6 +446,15 @@ } ] }, + { + "text": "React Native", + "items": [ + { + "text": "入门", + "path": "Getting-Started-React-Native.md" + } + ] + }, { "text": "通用", "items": [ @@ -624,6 +633,10 @@ { "text": "API文档", "path": "{ApiDocumentationUrl}" + }, + { + "text": "官方包", + "path": "https://abp.io/packages" } ] } From 64f27887b079fec35df71cbd4b450bf280e95b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 28 Jul 2020 12:07:54 +0300 Subject: [PATCH 35/38] Better hande generics on API definition endpoint. --- .../AspNetCoreApiDescriptionModelProvider.cs | 70 +++++++++++++++---- .../Volo/Abp/Reflection/TypeHelper.cs | 6 +- .../Http/Modeling/TypeApiDescriptionModel.cs | 11 ++- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index b5c5699757..3439b744e6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -92,7 +92,9 @@ namespace Volo.Abp.AspNetCore.Mvc var uniqueMethodName = GetUniqueActionName(method); if (controllerModel.Actions.ContainsKey(uniqueMethodName)) { - Logger.LogWarning($"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + method); + Logger.LogWarning( + $"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + + method); return; } @@ -119,11 +121,14 @@ namespace Volo.Abp.AspNetCore.Mvc private static string CalculateControllerName(Type controllerType, ConventionalControllerSetting setting) { - var controllerName = controllerType.Name.RemovePostFix("Controller").RemovePostFix(ApplicationService.CommonPostfixes); + var controllerName = controllerType.Name.RemovePostFix("Controller") + .RemovePostFix(ApplicationService.CommonPostfixes); if (setting?.UrlControllerNameNormalizer != null) { - controllerName = setting.UrlControllerNameNormalizer(new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); + controllerName = + setting.UrlControllerNameNormalizer( + new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); } return controllerName; @@ -152,7 +157,8 @@ namespace Volo.Abp.AspNetCore.Mvc return methodNameBuilder.ToString(); } - private static List GetSupportedVersions(Type controllerType, MethodInfo method, ConventionalControllerSetting setting) + private static List GetSupportedVersions(Type controllerType, MethodInfo method, + ConventionalControllerSetting setting) { var supportedVersions = new List(); @@ -185,13 +191,19 @@ namespace Volo.Abp.AspNetCore.Mvc AddCustomTypesToModel(applicationModel, method.ReturnType); } - private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type) + private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, + [CanBeNull] Type type) { if (type == null) { return; } + if (type.IsGenericParameter) + { + return; + } + type = AsyncHelper.UnwrapTask(type); if (type == typeof(object) || @@ -216,18 +228,27 @@ namespace Volo.Abp.AspNetCore.Mvc return; } - /* TODO: Add interfaces - */ + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + var genericTypeDefinition = type.GetGenericTypeDefinition(); + + AddCustomTypesToModel(applicationModel, genericTypeDefinition); + + foreach (var genericArgument in type.GetGenericArguments()) + { + AddCustomTypesToModel(applicationModel, genericArgument); + } - var typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + return; + } + var typeName = CalculateTypeName(type); if (applicationModel.Types.ContainsKey(typeName)) { return; } - var typeModel = TypeApiDescriptionModel.Create(type); - applicationModel.Types[typeName] = typeModel; + applicationModel.Types[typeName] = TypeApiDescriptionModel.Create(type); AddCustomTypesToModel(applicationModel, type.BaseType); @@ -237,7 +258,24 @@ namespace Volo.Abp.AspNetCore.Mvc } } - private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) + private static string CalculateTypeName(Type type) + { + if (!type.IsGenericTypeDefinition) + { + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + var i = 0; + var argumentList = type + .GetGenericArguments() + .Select(_ => "T" + i++) + .JoinAsString(","); + + return $"{type.FullName.Left(type.FullName.IndexOf('`'))}<{argumentList}>"; + } + + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, + ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) { @@ -253,8 +291,8 @@ namespace Volo.Abp.AspNetCore.Mvc { var parameterDescription = apiDescription.ParameterDescriptions[i]; var matchedMethodParamName = matchedMethodParamNames.Length > i - ? matchedMethodParamNames[i] - : parameterDescription.Name; + ? matchedMethodParamNames[i] + : parameterDescription.Name; actionModel.AddParameter(ParameterApiDescriptionModel.Create( parameterDescription.Name, @@ -286,7 +324,8 @@ namespace Volo.Abp.AspNetCore.Mvc return modelNameProvider.Name ?? parameterInfo.Name; } - private static string GetRootPath([NotNull] Type controllerType, [CanBeNull] ConventionalControllerSetting setting) + private static string GetRootPath([NotNull] Type controllerType, + [CanBeNull] ConventionalControllerSetting setting) { if (setting != null) { @@ -309,7 +348,8 @@ namespace Volo.Abp.AspNetCore.Mvc return setting.RemoteServiceName; } - var remoteServiceAttr = controllerType.GetCustomAttributes().OfType().FirstOrDefault(); + var remoteServiceAttr = + controllerType.GetCustomAttributes().OfType().FirstOrDefault(); if (remoteServiceAttr?.Name != null) { return remoteServiceAttr.Name; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 83c2c4c38e..0ba2a0d277 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -71,7 +71,7 @@ namespace Volo.Abp.Reflection return true; } - if (includeNullables && IsNullable(type)) + if (includeNullables && IsNullable(type) && type.GenericTypeArguments.Any()) { return IsPrimitiveExtendedInternal(type.GenericTypeArguments[0], includeEnums); } @@ -197,7 +197,7 @@ namespace Volo.Abp.Reflection return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetFullNameHandlingNullableAndGenerics).JoinAsString(",")}>"; } - return type.FullName; + return type.FullName ?? type.Name; } public static string GetSimplifiedName([NotNull] Type type) @@ -296,7 +296,7 @@ namespace Volo.Abp.Reflection return "number"; } - return type.FullName; + return type.FullName ?? type.Name; } public static object ConvertFromString(string value) diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index 161584279b..a73c7dd40d 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -15,11 +15,13 @@ namespace Volo.Abp.Http.Modeling public object[] EnumValues { get; set; } + public string[] GenericArguments { get; set; } + public PropertyApiDescriptionModel[] Properties { get; set; } private TypeApiDescriptionModel() { - + } public static TypeApiDescriptionModel Create(Type type) @@ -47,9 +49,14 @@ namespace Volo.Abp.Http.Modeling .GetProperties() .Select(PropertyApiDescriptionModel.Create) .ToArray(); + + if (type.IsGenericTypeDefinition) + { + typeModel.GenericArguments = type.GetGenericArguments().Select(a => a.Name).ToArray(); + } } return typeModel; } } -} \ No newline at end of file +} From d0b1ce8076b2bd7927683e1e5994d4443540445d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 28 Jul 2020 12:18:21 +0300 Subject: [PATCH 36/38] Do not include base properties to type definitions --- .../Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs | 2 +- .../Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 3439b744e6..617650d0e2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -252,7 +252,7 @@ namespace Volo.Abp.AspNetCore.Mvc AddCustomTypesToModel(applicationModel, type.BaseType); - foreach (var propertyInfo in type.GetProperties()) + foreach (var propertyInfo in type.GetProperties().Where(p => p.DeclaringType == type)) { AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType); } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index a73c7dd40d..4b0b0b870c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -47,6 +47,7 @@ namespace Volo.Abp.Http.Modeling { typeModel.Properties = type .GetProperties() + .Where(p => p.DeclaringType == type) .Select(PropertyApiDescriptionModel.Create) .ToArray(); From 8007029b05612c23d31e392dfac2c07b7421e079 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 28 Jul 2020 15:20:33 +0300 Subject: [PATCH 37/38] chore: reduce angular compiler versions --- npm/ng-packs/package.json | 8 +- npm/ng-packs/tsconfig.base.json | 3 +- npm/ng-packs/yarn.lock | 501 +++++++++++++++----------------- 3 files changed, 243 insertions(+), 269 deletions(-) diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index d54346ce00..4b28ca5849 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -39,8 +39,8 @@ "@angular/animations": "~10.0.0", "@angular/cli": "~10.0.0", "@angular/common": "~10.0.0", - "@angular/compiler": "~10.0.0", - "@angular/compiler-cli": "~10.0.0", + "@angular/compiler": "10.0.0", + "@angular/compiler-cli": "10.0.0", "@angular/core": "~10.0.0", "@angular/forms": "~10.0.0", "@angular/language-service": "~10.0.0", @@ -92,6 +92,10 @@ "dependencies": { "tslib": "^2.0.0" }, + "resolutions": { + "@angular/compiler": "10.0.0", + "@angular/compiler-cli": "10.0.0" + }, "config": { "commitizen": { "path": "cz-conventional-changelog" diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index 02971ebe83..d73d137f99 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -17,7 +17,8 @@ "paths": { "@abp/ng.core": ["packages/core/src/public-api.ts"], "@abp/ng.theme.shared": ["packages/theme-shared/src/public-api.ts"], - "@abp/ng.theme.shared/extensions": ["packages/theme-shared/src/public-api.ts"], + "@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"], + "@abp/ng.components/tree": ["packages/components/tree/src/public-api.ts"], "@abp/ng.theme.basic": ["packages/theme-basic/src/public-api.ts"], "@abp/ng.account": ["packages/account/src/public-api.ts"], "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index c94fdbb1bc..e93affe9c8 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -108,31 +108,31 @@ jest-preset-angular "^8.1.2" lodash "^4.17.10" -"@angular-devkit/architect@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1000.3.tgz#f23ea9d370edac8b0dd979d2574b12aa9923c3d4" - integrity sha512-8ZszTAkRvGGMXERFvyLT6SJPfJXjNNfHamA76uDPTBXy+EijJ1XVTUr1+SYEe73E4ovtxqxAnsApEFxS7/Ni5w== +"@angular-devkit/architect@0.1000.4": + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1000.4.tgz#2effe58361bea2a77d11671743568ab4d50b1554" + integrity sha512-n0DXdrecxXupxKf37sv5Oky5QpQXOHPgLDAmyAwevr31yLEwyZ3OQoVxaC68AMQJFYMlHYYMwi6gdIcFmL6W0g== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.4" rxjs "6.5.5" "@angular-devkit/architect@>=0.900.0 < 0.1000.0": - version "0.901.11" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.11.tgz#d429b7fc9952fe6a047a9645786a93eed60f8393" - integrity sha512-RmYOq1VEJdQLzwMno+C56WtgscAtoR/7i4tX5b5VxRa2RmQKTxowllYWwgrF5445VGUqzap9H6zJFXvlY2FA0w== + version "0.901.12" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.12.tgz#e0f78b7282f851d27af6802dc7a261022421b2d5" + integrity sha512-gLlsxa+3JPV1m1gRvRMujOs4xKox6I5BkYmOD1zfu+dB6y3LuBAvHfXA6FaTDVOMBrmSlWnE4PmOmB6xd7wxMA== dependencies: - "@angular-devkit/core" "9.1.11" + "@angular-devkit/core" "9.1.12" rxjs "6.5.4" "@angular-devkit/build-angular@~0.1000.0": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1000.3.tgz#c5af97a1868adb2d0be9cb97e5d8b3c3c8428d4d" - integrity sha512-r3KJj39AwkYYzbixSM095l4fOGvhyByr0XvmAEu0l5dGGdL4tNXywvgXkNhEVRDo0jZYpTMegiTqzOik/9YCDw== - dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/build-optimizer" "0.1000.3" - "@angular-devkit/build-webpack" "0.1000.3" - "@angular-devkit/core" "10.0.3" + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1000.4.tgz#447ebfc16afaa7638099e2f183144ebdc0e2a1a4" + integrity sha512-jAl2xsj1g4RdQ4MBLSZ8Dsj3MUKjYKK+e73DxVHSTo0166T1p/7gdgSehLHWVlavJLDVM+fjYdt4kP12Z7FBjQ== + dependencies: + "@angular-devkit/architect" "0.1000.4" + "@angular-devkit/build-optimizer" "0.1000.4" + "@angular-devkit/build-webpack" "0.1000.4" + "@angular-devkit/core" "10.0.4" "@babel/core" "7.9.6" "@babel/generator" "7.9.6" "@babel/plugin-transform-runtime" "7.9.6" @@ -140,8 +140,8 @@ "@babel/runtime" "7.9.6" "@babel/template" "7.8.6" "@jsdevtools/coverage-istanbul-loader" "3.0.3" - "@ngtools/webpack" "10.0.3" - ajv "6.12.2" + "@ngtools/webpack" "10.0.4" + ajv "6.12.3" autoprefixer "9.8.0" babel-loader "8.1.0" browserslist "^4.9.1" @@ -196,86 +196,86 @@ worker-plugin "4.0.3" "@angular-devkit/build-ng-packagr@~0.1000.0": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.1000.3.tgz#b9f10e6e1fcc821c7b35a07903f6f7e6e3f41f97" - integrity sha512-k5dEjx8g+ZLLwlViue7931PNxbAbePJ1NdAmXodg2U0W4l5g22egiUFG1DGsMm7p/3LlZGv3q9Kwdg8Ox1acVg== + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.1000.4.tgz#a449169210b994491cf6048d07b246daf74eb1c7" + integrity sha512-YcqXv0+mNEAUtFI2XtoabXNxlTfgIsFobeoGwdfe1tVn4b465FIgqFDiNFEgaZ4UZbdbLuk0yrVHpk/f8X/wzw== dependencies: - "@angular-devkit/architect" "0.1000.3" + "@angular-devkit/architect" "0.1000.4" rxjs "6.5.5" -"@angular-devkit/build-optimizer@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.3.tgz#05f7a853a5907ad3381a598896a2e053e47e925d" - integrity sha512-6mFoubg08UCWC0fE2mGoawEt2R1VlGStvUNAP2PRCjoj1ZySa1NnVYoKk65cyAAA3K2o7vSoDZesNq1uABjZbg== +"@angular-devkit/build-optimizer@0.1000.4": + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.4.tgz#bdeb610b042533dbbc3c14c52f81f2f67bfb6513" + integrity sha512-usscQipxIU2yJVmBdyIDPmJZ+ltOjsX4VbMkGM2IdJHCIi9uEk2E3+YLxzyU5Xl5tXEK61AQt4YWUimt8Kg0GA== dependencies: loader-utils "2.0.0" source-map "0.7.3" tslib "2.0.0" webpack-sources "1.4.3" -"@angular-devkit/build-webpack@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1000.3.tgz#2709ccfc5201c2be9301551ef1d815c6aafedfbc" - integrity sha512-+vmn9d9THFubSWS28K1+nElUfOrhT576ptVZMd0a5S24momV8loW3J8iBOBfnGal/P86ZCAyP46kSirlAzH9Jg== +"@angular-devkit/build-webpack@0.1000.4": + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1000.4.tgz#781e7d67dbc80932e933010fea96339e10a7eec8" + integrity sha512-ZPXrm1+UyKbMZtnU+b5jdtZPFDYs8Pu5lHhJDzX6bk0TxtuXws+nwaeUs6WkfwXft8cksxK2WwGr57XX3OT7nw== dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/core" "10.0.3" + "@angular-devkit/architect" "0.1000.4" + "@angular-devkit/core" "10.0.4" rxjs "6.5.5" -"@angular-devkit/core@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-10.0.3.tgz#7654766c9f2d3c510beaad8bca3e2a71643c6b8e" - integrity sha512-m27ogjq44j80x64RnEswSvy8UewUqeCVJBbEuY6fzrWoaiCf12sgPlrSCwjwfhtQrLgl1e/i9zYA7U6ulGRXyg== +"@angular-devkit/core@10.0.4": + version "10.0.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-10.0.4.tgz#105df3e7a7df56005f3637121ef010dd3849f5eb" + integrity sha512-Pe60BJCcAVtOkhXn2c+ql1NfyMCu421jWmKwl1KsW1DXclpGB+UIa560ICBgEwla6DyltMKEN/lX+P4WOskpkQ== dependencies: - ajv "6.12.2" + ajv "6.12.3" fast-json-stable-stringify "2.1.0" magic-string "0.25.7" rxjs "6.5.5" source-map "0.7.3" -"@angular-devkit/core@8.3.28", "@angular-devkit/core@^8.0.3": - version "8.3.28" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.28.tgz#0a733132821d916788a8ed34eff0f9149a9f3a15" - integrity sha512-tM0oPjzftJMdfuAuZF2EspVKtJyhtS2IQ02S7eKTZqkh5p/oVv710aND2D2IN5qdFrzpUSearx6D+Mr/Jo8jrA== +"@angular-devkit/core@8.3.29", "@angular-devkit/core@^8.0.3": + version "8.3.29" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.29.tgz#3477edd6458653f83e6d78684b100c1bef81382f" + integrity sha512-4jdja9QPwR6XG14ZSunyyOWT3nE2WtZC5IMDIBZADxujXvhzOU0n4oWpy6/JVHLUAxYNNgzLz+/LQORRWndcPg== dependencies: - ajv "6.10.2" + ajv "6.12.3" fast-json-stable-stringify "2.0.0" magic-string "0.25.3" rxjs "6.4.0" source-map "0.7.3" -"@angular-devkit/core@9.1.11", "@angular-devkit/core@^9.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.11.tgz#a2de3de721488164297bfccefcb08f28a7a9108e" - integrity sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ== +"@angular-devkit/core@9.1.12", "@angular-devkit/core@^9.0.0": + version "9.1.12" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.12.tgz#7cebce51918fe7f3462d8c58c9fbd5a3e2c3b3e7" + integrity sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA== dependencies: - ajv "6.12.0" + ajv "6.12.3" fast-json-stable-stringify "2.1.0" magic-string "0.25.7" rxjs "6.5.4" source-map "0.7.3" -"@angular-devkit/schematics@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-10.0.3.tgz#84e1a337692ba36cbae145b5e678592878df722f" - integrity sha512-TjA2ZSPCgUK9l4FiRTIQY7DceXMAvNzOMWffy9o3kv2HPtxG9kuBrQXk++Z99zpylK0cAsugV7t/5ANpUkrIiA== +"@angular-devkit/schematics@10.0.4": + version "10.0.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-10.0.4.tgz#73e0021b01e4c35d3bf54111824793527c5ec6e5" + integrity sha512-k9b73XkKQK8yFbq+dQqJnxAMG3mm/YR1KQlsQzOEVdw8cqqEALLoUXg/73hT5eUnSsNWmyDLBzEAyNtemmmI4w== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.4" ora "4.0.4" rxjs "6.5.5" "@angular-devkit/schematics@^8.0.6": - version "8.3.28" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.28.tgz#80b93755e7712f838fe6a3a4a5f3f44312188a0e" - integrity sha512-WFktBtTynViexVrgHiQV9rKfNS2YNONpi25A8+5tQR5kGKuQyHI3aEN0UMNA2sztWnPQ9AI8xz2uciQsHljgDw== + version "8.3.29" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.29.tgz#b3ba658b90fb3226a80ff12977be7dd583e99c49" + integrity sha512-AFJ9EK0XbcNlO5Dm9vr0OlBo1Nw6AaFXPR+DmHGBdcDDHxqEmYYLWfT+JU/8U2YFIdgrtlwvdtf6UQ3V2jdz1g== dependencies: - "@angular-devkit/core" "8.3.28" + "@angular-devkit/core" "8.3.29" rxjs "6.4.0" "@angular/animations@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-10.0.4.tgz#179e4e63e3f2468ee1fe961c12eb143dd8350b23" - integrity sha512-UzQiWhDHY6wixS1Nh+Jwpzq1weiLGXJPt3Pa4pETpt3Hg7MIUu62dik6OFWuGYQPbn9DJYH+CH+sRxN1GCVjww== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-10.0.5.tgz#41c1419f5233cf8adc2139461a552969e1f938c1" + integrity sha512-vHWn7NJiiDAok36GSBJ0y/nqjd4UFl9WGIEsvydUqnyBJ0scuqJa+hADrWwqkK7b/xVW9ARVG00NXmemEPUVGA== dependencies: tslib "^2.0.0" @@ -287,15 +287,15 @@ parse5 "^5.0.0" "@angular/cli@~10.0.0": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-10.0.3.tgz#0b8c5e263deaa63d05b6efda790a2000b8a24a85" - integrity sha512-ONK8YG20KuakQetY0lPKDAOA3uBoLurdpSfFspFkcECyDimwJYSEydi3FUnCxEexeoKvrQWcol+q+u9YPoHCyg== - dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" - "@schematics/angular" "10.0.3" - "@schematics/update" "0.1000.3" + version "10.0.4" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-10.0.4.tgz#5c13500f3135e43b9e6e53534fdfbc283abb83cc" + integrity sha512-hmr1fXT8+6wOoh7abIg154wdP9QCMPHMShWPEYHks6ARmlRfX5k+BykQMzgI8H6tMNLoR+DAlO5+pPouJEyilQ== + dependencies: + "@angular-devkit/architect" "0.1000.4" + "@angular-devkit/core" "10.0.4" + "@angular-devkit/schematics" "10.0.4" + "@schematics/angular" "10.0.4" + "@schematics/update" "0.1000.4" "@yarnpkg/lockfile" "1.1.0" ansi-colors "4.1.1" debug "4.1.1" @@ -313,16 +313,16 @@ uuid "8.1.0" "@angular/common@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-10.0.4.tgz#f585e0b1ef723bb96e54af831138184999490e3d" - integrity sha512-9DJMD8GgHz7i2fMz0f1IHZlDSIz83uE6fcH8SIq1iCSWT2dubRRpCX000VpIyhAgfkCgdNCYXQ7VGNsZceoagQ== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-10.0.5.tgz#f1c1b8671dbacc263d5d4f63606a82bc968644ff" + integrity sha512-AKmD/X13q3zzo5uTV/M4dWpAqJ136187rL4VweRc6b5t/oZ3VGcejjElZebgtoyGTye8luIP6fZ/LMb580lBlQ== dependencies: tslib "^2.0.0" -"@angular/compiler-cli@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-10.0.4.tgz#b241f8d41ca01dfed67b5ed4928ab0f13e2879af" - integrity sha512-uZKk6Ab4Pw8qcaXhpORuEoCbPSHWx3TWcs9OXIKIhzOOoMMe9OSt2SzOkHCrySSaik1IhQODnHww7sGRw5mxwQ== +"@angular/compiler-cli@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-10.0.0.tgz#6fe60e239b01fd10d3923a3de88877512e0e2d40" + integrity sha512-KusRkS1NKPrjhj3BXoxRgeejuJVy/ra4dcDV3hpscRmR8FSdim3rs2H+lYK2hZ26ISGsYUvC8cHNaMqP2U3X+g== dependencies: canonical-path "1.0.0" chokidar "^3.0.0" @@ -338,36 +338,36 @@ tslib "^2.0.0" yargs "15.3.0" -"@angular/compiler@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-10.0.4.tgz#786a5c1653a429233bee9f53614b5a82c62841ce" - integrity sha512-1rnEmSHJtrKC1QD+PyF36xwMnSqG4Slgi5+PYk3BxIa5vbWBibrYijtd/uCNhscfPSpfb06MVM2mRsrc+BmbQg== +"@angular/compiler@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-10.0.0.tgz#15f0a0e5760ebc6fcbc06b0b3bb5f110282cf03d" + integrity sha512-meyJKPLLhkgjxF4dvdJq/sw7MI7KvpgYADoFt2K/5dt48ExXRj+kDlyQC6FAwm9mbgSea/A+JpDG8HMPU7AK5Q== dependencies: tslib "^2.0.0" "@angular/core@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-10.0.4.tgz#700eda7711b50796298679d245e091cb54155071" - integrity sha512-lA8RDagJ/O0gUX95h00+nnUZs/1QmmhAVWVtHcuI12ueC836tJhLtPGnEx9ib9NXrgRyNwb8lO1xJPmmuQgdQQ== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-10.0.5.tgz#52a11d1b6ce065f6c26d919ab3bea233f51902be" + integrity sha512-zoIxIi/UaRImeC1uD+eIKrdcihvQR/zfbqKgK67IPy/tghijxge1Tbl8FWIAT0ncAj0XH/JHsWnVdUV/TUI+CA== dependencies: tslib "^2.0.0" "@angular/forms@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-10.0.4.tgz#d37fe47d5c91b0ef249066e904338905b2687847" - integrity sha512-SJSYZCfHua9fi/dks1q+ad9OGBblRUn3Q1V4B7r99fJYr39qRiIHcegikhY4h8H3Wk1bJRGJG7iXmxJhjWXK3Q== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-10.0.5.tgz#c1e3d7218770e56ab0060b28dbf33d76a481a1c5" + integrity sha512-Bv+KEPjqd+ndl/Z0w1uWsBmvb8Je1JObHRjbKAcW7c4r1CVobm+QJv6r6MDL+FOs78lnrm8baekGLQyHH1uO7g== dependencies: tslib "^2.0.0" "@angular/language-service@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-10.0.4.tgz#fe61e5a82c494a3aaabcca4997aff94fa891d12e" - integrity sha512-m5dWL1Lg9M4kDA/kjpdyKsrx9arbIebrgsSzr/QG7cPon6SQMRs9ECZBM+3GalkQghrPO1mLgnrUwFBUH2QNPw== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-10.0.5.tgz#2e4b24f7440257d3038ad717b19c5c0065684937" + integrity sha512-WJz5+XxOb0ORUERwNEUsX+0L7V4Tm7tz6i/mKAcz5SbE5eVfi+ywT+Zg7xMVRhyEpwDycqLXHK4piBOuGIjk+g== "@angular/localize@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-10.0.4.tgz#2c30eb604c931d98d52a2dea098ebb6a320431a3" - integrity sha512-MMMmgd6zFggWn01q/O8GYY/wRrbVxxR5H/XyQIqCjRQJgcrM0lwBnvVfFRGYa4oDHgik3Ri7Tbi1envhXSCMEA== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-10.0.5.tgz#cc51976e0ad3cdd05df0ce8448b85d1f2b9ff492" + integrity sha512-iyZ58H7+WBm9J1K2OLKgFFHno33KOu1FMQKaGdj/XCZaG1PphanOUivNxvxXo/6r71UW6uX/U17Rk//F8M0zPA== dependencies: "@babel/core" "7.8.3" glob "7.1.2" @@ -383,23 +383,23 @@ yargs "15.3.0" "@angular/platform-browser-dynamic@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.4.tgz#77844511154eeb5eec5e68566767c215654f34e8" - integrity sha512-RoUMqYhUwF6+Mvk/aH0IZQ1D0SDEi9k5EZlx9CJ3RvNuKygk7to+S4vMWVpGxFQlwdS3bytRLKi+Kki6f4nLkg== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.5.tgz#bf0e6af90677e08c38747b1d1b9bacb2463872e0" + integrity sha512-Qa6RM9aJuuw5E7BeGBG9/kCOtssI6Wyzbb/8awzZFF6Fjqom1kef185nwx7SgQs2cdrhqRAgjt0eDJqTfSVb+Q== dependencies: tslib "^2.0.0" "@angular/platform-browser@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-10.0.4.tgz#58131215e414be7ee9d4024e47f61b4a02166b28" - integrity sha512-iaZ8pFS5XUgPCO6/C47TzSFPzlzgleayq099cVOOx9z0t/SwUCSKt4AdAVhyQ8RTnx6l1JmmwBgRaXpScZlqzg== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-10.0.5.tgz#0b90263a3858385cfb3ab440358d19eb9e8e91d3" + integrity sha512-bmknyK18yMv+iLCqcrfcpq3/2Ls9mRA1lOe7YhSGpHxM969Rb6Av8vJNW+uv0y7+gTvxacmpiQO8iryr/xvzRg== dependencies: tslib "^2.0.0" "@angular/router@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-10.0.4.tgz#d68c2711b53f3bdc1508d9b7e90b181b4e09f42c" - integrity sha512-iDLWdmltU5pZ6M/fBKC5Kg2o9Aqb1YJ+oHXFu186BQAl2RNeNCmMQ0VaCxjpMgD/MoSxpuRuGQ6rRrCSFCxtcQ== + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-10.0.5.tgz#3e5f8be439b827b1d7bc980d673dec216a2f0769" + integrity sha512-hUGffVOjkdaN5IN9fSipm5ED+XGyfc9yOr5hYr/6YA3H4xaQzXZGlzrrDhAVltTyYalD2LJeXhzlvp59lXgcjg== dependencies: tslib "^2.0.0" @@ -2309,12 +2309,12 @@ jquery "3.5.0" replace-in-file "^4.1.3" -"@ngtools/webpack@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-10.0.3.tgz#2f8b2674108f7c36ffcdfc9e9aa024abfe3c7e04" - integrity sha512-0TuvYMCLtsApLtCHXeDBYGEoAQXzsRLpgFxPM5W7CGcj0ecthZO4NYrMAt+J8ky//KmbxqQSFHWmss2cbirIPA== +"@ngtools/webpack@10.0.4": + version "10.0.4" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-10.0.4.tgz#25c0a932319f8a26cbc849f4d13587f179fdc494" + integrity sha512-qzWkmVUUvr+2kiCtVLyopRwaBbExWtsGrgNVnJoAhtKKDflT4W33FpyJZJvxHwL6nfGGdb261Gfw7teIlCGjog== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.4" enhanced-resolve "4.1.1" rxjs "6.5.5" webpack-sources "1.4.3" @@ -2402,12 +2402,12 @@ "@octokit/types" "^5.0.0" "@octokit/endpoint@^6.0.1": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.4.tgz#da3eafdee1fabd6e5b6ca311efcba26f0dd99848" - integrity sha512-ZJHIsvsClEE+6LaZXskDvWIqD3Ao7+2gc66pRG5Ov4MQtMvCU9wGu1TItw9aGNmRuU9x3Fei1yb+uqGaQnm0nw== + version "6.0.5" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.5.tgz#43a6adee813c5ffd2f719e20cfd14a1fee7c193a" + integrity sha512-70K5u6zd45ItOny6aHQAsea8HHQjlQq85yqOMe+Aj8dkhN2qSJ9T+Q3YjUjEYfPRBcuUWNgMn62DQnP/4LAIiQ== dependencies: "@octokit/types" "^5.0.0" - is-plain-object "^3.0.0" + is-plain-object "^4.0.0" universal-user-agent "^6.0.0" "@octokit/plugin-enterprise-rest@^6.0.1": @@ -2454,15 +2454,15 @@ once "^1.4.0" "@octokit/request@^5.2.0": - version "5.4.6" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.6.tgz#e8cc8d4cfc654d30428ea92aaa62168fd5ead7eb" - integrity sha512-9r8Sn4CvqFI9LDLHl9P17EZHwj3ehwQnTpTE+LEneb0VBBqSiI/VS4rWIBfBhDrDs/aIGEGZRSB0QWAck8u+2g== + version "5.4.7" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.7.tgz#fd703ee092e0463ceba49ff7a3e61cb4cf8a0fde" + integrity sha512-FN22xUDP0i0uF38YMbOfx6TotpcENP5W8yJM1e/LieGXn6IoRxDMnBf7tx5RKSW4xuUZ/1P04NFZy5iY3Rax1A== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" "@octokit/types" "^5.0.0" deprecation "^2.0.0" - is-plain-object "^3.0.0" + is-plain-object "^4.0.0" node-fetch "^2.3.0" once "^1.4.0" universal-user-agent "^6.0.0" @@ -2497,9 +2497,9 @@ "@types/node" ">= 8" "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.1.0.tgz#4377a3f39edad3e60753fb5c3c310756f1ded57f" - integrity sha512-OFxUBgrEllAbdEmWp/wNmKIu5EuumKHG4sgy56vjZ8lXPgMhF05c76hmulfOdFHHYRpPj49ygOZJ8wgVsPecuA== + version "5.2.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.2.0.tgz#d075dc23bf293f540739250b6879e2c1be2fc20c" + integrity sha512-XjOk9y4m8xTLIKPe1NFxNWBdzA2/z3PFFA/bwf4EoH6oS8hM0Y46mEa4Cb+KCyj/tFDznJFahzQ0Aj3o1FYq4A== dependencies: "@types/node" ">= 8" @@ -2545,21 +2545,21 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@schematics/angular@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.0.3.tgz#a4d54389918a7f9c82345d4f639d930f22b62c6b" - integrity sha512-Or2pCqjpPbAvmbxtfMosGwQbNbSL4xodK5Key7678ZAPGB+rcxrVkBI9yxEJ/qzF/LrmMoKqy0JCmVLK7Grpog== +"@schematics/angular@10.0.4": + version "10.0.4" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.0.4.tgz#5a84fe6f4e8d8e4e7e2925c05b7689f70dab597f" + integrity sha512-wTpuVfPKALOcOHfD7Y222r/IfqJgpqASyoXdkzf7dZVrLXjNy/XrKuc2Vfn85Ce9lnme0xT5ww+KjbSRc0cSUw== dependencies: - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" + "@angular-devkit/core" "10.0.4" + "@angular-devkit/schematics" "10.0.4" -"@schematics/update@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1000.3.tgz#d361f23ae49cd3ac9b547b6f4c2470d7ca86aabd" - integrity sha512-Nncdklmzi1tyzkoAh7GlSslxriRhftlmfqPVmFHrrPRttYACtT/QH5qcWsrPgTPpHGINYEHrPjpeljsMoMchBQ== +"@schematics/update@0.1000.4": + version "0.1000.4" + resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1000.4.tgz#e0780c5313ee90480f8a26b66c0104f664df64a4" + integrity sha512-NkIUsqnEqG6jhW6b1xXYmgW02WYAN9/IoswnZnaQrbGzxroNVQKURiV4AP22sZLCr7gQ9NJwjXxO5rxbzbY8wg== dependencies: - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" + "@angular-devkit/core" "10.0.4" + "@angular-devkit/schematics" "10.0.4" "@yarnpkg/lockfile" "1.1.0" ini "1.3.5" npm-package-arg "^8.0.0" @@ -2721,19 +2721,19 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*", "@types/node@>= 8": - version "14.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806" - integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw== + version "14.0.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== "@types/node@^12.11.1": - version "12.12.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" - integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== + version "12.12.53" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.53.tgz#be0d375933c3d15ef2380dafb3b0350ea7021129" + integrity sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ== "@types/node@^8.0.31": - version "8.10.61" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.61.tgz#d299136ce54bcaf1abaa4a487f9e4bedf6b0d393" - integrity sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q== + version "8.10.62" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.62.tgz#8d0d0db44a46ae7679d92e2e36d41bf1c3625d6a" + integrity sha512-76fupxOYVxk36kb7O/6KtrAPZ9jnSK3+qisAX4tQMEuGNdlvl7ycwatlHqjoE6jHfVtXFM3pCrCixZOidc5cuw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -3091,41 +3091,11 @@ ajv-errors@^1.0.0: integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.1.tgz#b83ca89c5d42d69031f424cad49aada0236c6957" - integrity sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA== - -ajv@6.10.2: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@6.12.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@6.12.2: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: +ajv@6.12.3, ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.5.5: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== @@ -4116,9 +4086,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001061, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097: - version "1.0.30001102" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001102.tgz#3275e7a8d09548f955f665e532df88de0b63741a" - integrity sha512-fOjqRmHjRXv1H1YD6QVLb96iKqnu17TjcLSaX64TwhGYed0P1E1CCWZ9OujbbK4Z/7zax7zAzvQidzdtjx8RcA== + version "1.0.30001107" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a" + integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ== canonical-path@1.0.0: version "1.0.0" @@ -4201,7 +4171,7 @@ chartjs-color@^2.1.0: chartjs-color-string "^0.6.0" color-convert "^1.9.3" -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.2.1, chokidar@^3.4.0: +"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.2.1, chokidar@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== @@ -4305,9 +4275,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-spinners@^2.0.0, cli-spinners@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5" - integrity sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w== + version "2.4.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" + integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== cli-width@^2.0.0: version "2.2.1" @@ -5051,13 +5021,12 @@ css-select@^2.0.0: nth-check "^1.0.2" css-selector-tokenizer@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" - integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== dependencies: cssesc "^3.0.0" fastparse "^1.1.2" - regexpu-core "^4.6.0" css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" @@ -5291,9 +5260,9 @@ data-urls@^2.0.0: whatwg-url "^8.0.0" date-fns@^2.10.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba" - integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw== + version "2.15.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f" + integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ== dateformat@^3.0.0: version "3.0.3" @@ -5697,9 +5666,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.488: - version "1.3.499" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.499.tgz#06949f19877dafa42915e57dfeb4c1cfb86a8649" - integrity sha512-y7FwtQm/8xuLMnYQfBQDYzCpNn+VkSnf4c3Km5TWMNXg7JA5RQBuxmcLaKdDVcIK0K5xGIa7TlxpRt4BdNxNoA== + version "1.3.510" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.510.tgz#dee781ff8b595c0deb60172b75d50b6889757eda" + integrity sha512-sLtGB0znXdmo6lM8hy5wTVo+fLqvIuO8hEpgc0DvPmFZqvBu/WB7AarEwhxVKjf3rVbws/rC8Xf+AlsOb36lJQ== elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.3" @@ -5782,9 +5751,9 @@ env-paths@^2.2.0: integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== envinfo@^7.3.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236" - integrity sha512-hQBkDf2iO4Nv0CNHpCuSBeaSrveU6nThVxFGTrq/eDlV716UQk09zChaJae4mZRsos1x4YLY2TaH3LHUae3ZmQ== + version "7.7.2" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.2.tgz#098f97a0e902f8141f9150553c92dbb282c4cabe" + integrity sha512-k3Eh5bKuQnZjm49/L7H4cHzs2FlL5QjbTB3JrPxoTI8aJG7hVMe4uKyJxSYH4ahseby2waUwk5OaKX/nAsaYgg== err-code@^1.0.0: version "1.1.2" @@ -5957,9 +5926,9 @@ eventemitter3@^4.0.0: integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== eventsource@^1.0.7: version "1.0.7" @@ -6158,11 +6127,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -7727,10 +7691,10 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-plain-object@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" - integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== +is-plain-object@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-4.1.1.tgz#1a14d6452cbd50790edc7fdaa0aed5a40a35ebb5" + integrity sha512-5Aw8LLVsDlZsETVMhoMXzqsXwQqr/0vlnBYzIXJbYo2F4yYlhLHs+Ez7Bod7IIQKWkJbJfxrWD7pA1Dw1TKrwA== is-reference@^1.1.2: version "1.2.1" @@ -7894,10 +7858,10 @@ jasmine-core@~2.8.0: resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4= -jasmine-core@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" - integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== +jasmine-core@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20" + integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw== jasmine@2.8.0: version "2.8.0" @@ -7909,12 +7873,12 @@ jasmine@2.8.0: jasmine-core "~2.8.0" jasmine@^3.3.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" - integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== + version "3.6.1" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.6.1.tgz#a20456b309a669b547a3c24bb2120f16f70cfc65" + integrity sha512-Jqp8P6ZWkTVFGmJwBK46p+kJNrZCdqkQ4GL+PGuBXZwK1fM4ST9BizkYgIwCFqYYqnTizAy6+XG2Ej5dFrej9Q== dependencies: - glob "^7.1.4" - jasmine-core "~3.5.0" + fast-glob "^2.2.6" + jasmine-core "~3.6.0" jasminewd2@^2.1.0: version "2.2.0" @@ -8865,9 +8829,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" macos-release@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.0.tgz#837b39fc01785c3584f103c5599e0f0c8068b49e" - integrity sha512-ko6deozZYiAkqa/0gmcsz+p4jSy3gY7/ZsCEokPaYd8k+6/aXGkiTgr61+Owup7Sf+xjqW8u2ElhoM9SEcEfuA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" + integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== magic-string@0.25.3: version "0.25.3" @@ -9412,9 +9376,9 @@ nanomatch@^1.2.9: to-regex "^3.0.1" native-request@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.5.tgz#da67637c0a0ed0758243d1a688d647612f2d5a0a" - integrity sha512-7wU3DvBGAJQxWuMR3F62zrhB7hxNj2DdlC/eBVrCgavc6+ZpFZOqS/PsR7QyUPLMkFk0GvvzoeeOAZGLLnObnA== + version "1.0.7" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.7.tgz#ff742dc555b4c8f2f1c14b548639ba174e573856" + integrity sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ== natural-compare@^1.4.0: version "1.4.0" @@ -9437,14 +9401,14 @@ next-tick@~1.0.0: integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= ng-packagr@^10.0.0: - version "10.0.2" - resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-10.0.2.tgz#31aed1cc373a7b84baaef73c1dae29ccc667a57f" - integrity sha512-nmw6nQLwPBVIdU1M0Omo6BLeg5Iugj3hmq7pd+Un7hZ33T+D8vfB88jXUf/DklRPLIwRWlu+5evQNj76VPhYUw== + version "10.0.3" + resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-10.0.3.tgz#ef3fa4c3dbc826695b8e12b307172e23504e1229" + integrity sha512-M10y3eeAbIaU+NvNZEeGnuoWxZis0GFj9EKetYmmsXMjHxsPqEAZ1PUkKioIo0RPXuR1N4QGW0MSegHp5kBFeg== dependencies: "@rollup/plugin-commonjs" "^14.0.0" "@rollup/plugin-json" "^4.0.0" "@rollup/plugin-node-resolve" "^8.0.0" - ajv "^6.10.2" + ajv "^6.12.3" autoprefixer "^9.6.5" browserslist "^4.7.0" chalk "^4.0.0" @@ -9590,9 +9554,9 @@ node-notifier@^6.0.0: which "^1.3.1" node-releases@^1.1.58: - version "1.1.59" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e" - integrity sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw== + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== node-sass-tilde-importer@^1.0.0: version "1.0.2" @@ -10241,9 +10205,9 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + version "5.0.1" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878" + integrity sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" @@ -10458,9 +10422,9 @@ pnp-webpack-plugin@1.6.4: ts-pnp "^1.1.6" portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== + version "1.0.27" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.27.tgz#a41333c116b5e5f3d380f9745ac2f35084c4c758" + integrity sha512-bJ3U3MThKnyJ9Dx1Idtm5pQmxXqw08+XOHhi/Lie8OF1OlhVaBFhsntAIhkZYjfDcCzszSr0w1yCbccThhzgxQ== dependencies: async "^2.6.2" debug "^3.1.1" @@ -10625,14 +10589,14 @@ postcss-modules-extract-imports@^2.0.0: postcss "^7.0.5" postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: icss-utils "^4.1.1" - postcss "^7.0.16" + postcss "^7.0.32" postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" + postcss-value-parser "^4.1.0" postcss-modules-scope@^2.2.0: version "2.2.0" @@ -10813,7 +10777,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -10836,7 +10800,7 @@ postcss@7.0.31: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.29, postcss@^7.0.30, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.29, postcss@^7.0.30, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== @@ -11358,11 +11322,16 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== -regenerator-runtime@0.13.5, regenerator-runtime@^0.13.4: +regenerator-runtime@0.13.5: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regenerator-transform@^0.14.2: version "0.14.5" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" @@ -11391,7 +11360,7 @@ regexp.prototype.flags@^1.2.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -regexpu-core@^4.6.0, regexpu-core@^4.7.0: +regexpu-core@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== @@ -11460,19 +11429,19 @@ replace-in-file@^4.1.3: glob "^7.1.6" yargs "^15.0.2" -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.17.15" + lodash "^4.17.19" request-promise-native@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: - request-promise-core "1.1.3" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" @@ -11708,9 +11677,9 @@ rollup@2.10.9: fsevents "~2.1.2" rollup@^2.8.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.21.0.tgz#d2e114533812043d5c9b7b0a83f1b2a242e4e1d6" - integrity sha512-BEGgy+wSzux7Ycq58pRiWEOBZaXRXTuvzl1gsm7gqmsAHxkWf9nyA5V2LN9fGSHhhDQd0/C13iRzSh4bbIpWZQ== + version "2.23.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" + integrity sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg== optionalDependencies: fsevents "~2.1.2" @@ -12733,9 +12702,9 @@ symbol-tree@^3.2.2: integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== symlink-manager@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/symlink-manager/-/symlink-manager-1.5.0.tgz#106576eecfb611b6c320d3f643d714583248b33f" - integrity sha512-+5n/FYEhoxsLhMqxjTn74676xox3Nkvw71lMgk0jjeTwHnqiqTjp16IPONy6Rld25dCtrTgcDJ7lYkx+b1Diog== + version "1.5.1" + resolved "https://registry.yarnpkg.com/symlink-manager/-/symlink-manager-1.5.1.tgz#75721cacf2247ad6817b0ca5c033b87c8d4599fd" + integrity sha512-LKRsow5vrpOpk7dOxfj9uWDfb2g+f7GoaJdvTnUm5NyodQuHZJxHZpqsHvoKIvMyrlpkL4qbC8HXx2DbkQ+w8Q== dependencies: arg "^4.1.0" chokidar "^3.0.2" @@ -13076,9 +13045,9 @@ trim-off-newlines@^1.0.0: integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= ts-jest@^26.0.0: - version "26.1.3" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.3.tgz#aac928a05fdf13e3e6dfbc8caec3847442667894" - integrity sha512-beUTSvuqR9SmKQEylewqJdnXWMVGJRFqSz2M8wKJe7GBMmLZ5zw6XXKSJckbHNMxn+zdB3guN2eOucSw2gBMnw== + version "26.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.4.tgz#87d41a96016a8efe4b8cc14501d3785459af6fa6" + integrity sha512-Nd7diUX6NZWfWq6FYyvcIPR/c7GbEF75fH1R6coOp3fbNzbRJBZZAn0ueVS0r8r9ral1VcrpneAFAwB3TsVS1Q== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -13111,9 +13080,9 @@ ts-pnp@^1.1.6: integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== ts-toolbelt@^6.9.9: - version "6.13.11" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.13.11.tgz#46260d1cff0f2d635931df8d32239b73f2ed4f8d" - integrity sha512-QmvHwAofS1MAn1ndXCn2AVhxJLipaj4tK26SrhBTiydfgKtbLlU2efpIP/8Jocrc9ynNslZqrhqAesruJMlxCQ== + version "6.13.34" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.13.34.tgz#3bd6e1e333a75c5ddab584d293f35494cb8c9222" + integrity sha512-ikZwSUPO+fW+OUnyER1KCG58khTi51Di3+iVdhkK7UshqkL0Ir749B2TQ44o/khkKvDSOMzF8NuVkQLycDXnsg== tsickle@^0.38.1: version "0.38.1" @@ -13572,14 +13541,14 @@ watchpack-chokidar2@^2.0.0: chokidar "^2.1.8" watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== + version "1.7.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" + integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: - chokidar "^3.4.0" + chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" wbuf@^1.1.0, wbuf@^1.7.3: From 226d58440ba03f914e9d530a5be6b22cc753bbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 28 Jul 2020 16:21:05 +0300 Subject: [PATCH 38/38] Introduce ApiTypeNameHelper --- .../Volo/Abp/Reflection/TypeHelper.cs | 4 ++ .../Abp/Http/Modeling/ApiTypeNameHelper.cs | 38 +++++++++++++++++++ .../MethodParameterApiDescriptionModel.cs | 2 +- .../Modeling/ParameterApiDescriptionModel.cs | 6 +-- .../Modeling/PropertyApiDescriptionModel.cs | 24 +----------- .../ReturnValueApiDescriptionModel.cs | 4 +- 6 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 0ba2a0d277..cf3805249d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -295,6 +295,10 @@ namespace Volo.Abp.Reflection { return "number"; } + else if (type == typeof(object)) + { + return "object"; + } return type.FullName ?? type.Name; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs new file mode 100644 index 0000000000..46eb9e903d --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs @@ -0,0 +1,38 @@ +using System; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Http.Modeling +{ + public static class ApiTypeNameHelper + { + public static string GetTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetTypeName(keyType)}:{GetTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetTypeName(itemType)}]"; + } + + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + public static string GetSimpleTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetSimpleTypeName(keyType)}:{GetSimpleTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetSimpleTypeName(itemType)}]"; + } + + return TypeHelper.GetSimplifiedName(type); + } + } +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 603d3994e0..b4e272c2f3 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -31,7 +31,7 @@ namespace Volo.Abp.Http.Modeling Name = parameterInfo.Name, TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType), - TypeSimple = TypeHelper.GetSimplifiedName(parameterInfo.ParameterType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(parameterInfo.ParameterType), IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 0450861a41..675ba8bf54 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling private ParameterApiDescriptionModel() { - + } public static ParameterApiDescriptionModel Create(string name, string nameOnMethod, Type type, bool isOptional = false, object defaultValue = null, string[] constraintTypes = null, string bindingSourceId = null, string descriptorName = null) @@ -36,7 +36,7 @@ namespace Volo.Abp.Http.Modeling Name = name, NameOnMethod = nameOnMethod, Type = type != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(type) : null, - TypeSimple = type != null ? TypeHelper.GetSimplifiedName(type) : null, + TypeSimple = type != null ? ApiTypeNameHelper.GetSimpleTypeName(type) : null, IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, @@ -45,4 +45,4 @@ namespace Volo.Abp.Http.Modeling }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs index 80a3cee91a..610b6bd347 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using Volo.Abp.Reflection; namespace Volo.Abp.Http.Modeling { @@ -16,30 +15,11 @@ namespace Volo.Abp.Http.Modeling //TODO: Validation rules for this property public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo) { - string typeName; - string simpleTypeName; - - if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) - { - typeName = $"{{{TypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{TypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}"; - simpleTypeName = $"{{{TypeHelper.GetSimplifiedName(keyType)}:{TypeHelper.GetSimplifiedName(valueType)}}}"; - } - else if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) - { - typeName = $"[{TypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; - simpleTypeName = $"[{TypeHelper.GetSimplifiedName(itemType)}]"; - } - else - { - typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType); - simpleTypeName = TypeHelper.GetSimplifiedName(propertyInfo.PropertyType); - } - return new PropertyApiDescriptionModel { Name = propertyInfo.Name, - Type = typeName, - TypeSimple = simpleTypeName + Type = ApiTypeNameHelper.GetTypeName(propertyInfo.PropertyType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType) }; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 2e0862ccce..83fa4bb6be 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -23,8 +23,8 @@ namespace Volo.Abp.Http.Modeling return new ReturnValueApiDescriptionModel { Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(unwrappedType), - TypeSimple = TypeHelper.GetSimplifiedName(unwrappedType) + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(unwrappedType) }; } } -} \ No newline at end of file +}