diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index fcedcd0f89..55a60dca89 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -24,7 +24,7 @@ This sample aims to demonstrate a simple yet complete microservice solution; The diagram below shows the system: -![microservice-sample-diagram](../images/microservice-sample-diagram.png) +![microservice-sample-diagram-2](../images/microservice-sample-diagram-2.png) ### Source Code @@ -32,7 +32,7 @@ You can get the source code from [the GitHub repository](https://github.com/abpf ### Status -This sample is still in development, not completed yet. +Initial version of this sample has been completed. Additional improvement are still in development. ## Running the Solution @@ -50,6 +50,20 @@ Running as docker containers is easier since all dependencies are pre-configured - Open a command line in the `samples/MicroserviceDemo` folder of the repository. +- Pull images from Docker Hub: + + ``` + docker-compose -f docker-compose.yml -f docker-compose.migrations.yml pull + ``` + +- If you want to build images locally you may skip the above step and instead use build command: + + ``` + docker-compose -f docker-compose.yml -f docker-compose.migrations.yml build + ``` + + Building images may take a **long time** depending on your machine. + - Restore SQL Server databases: ``` @@ -62,8 +76,6 @@ Running as docker containers is easier since all dependencies are pre-configured docker-compose up -d ``` - At the first run, it will take a **long time** because it will build all docker images. - - Add this line to the end of your `hosts` file: ``` diff --git a/docs/en/images/microservice-sample-diagram-2.png b/docs/en/images/microservice-sample-diagram-2.png new file mode 100644 index 0000000000..17aea07098 Binary files /dev/null and b/docs/en/images/microservice-sample-diagram-2.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 08104ef769..f9c3730467 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -222,9 +222,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityModel", "src\Volo.Abp.IdentityModel\Volo.Abp.IdentityModel.csproj", "{64D99E19-EE25-465A-82E5-17B25F4C4E18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features.Tests", "test\Volo.Abp.Features.Tests\Volo.Abp.Features.Tests.csproj", "{575BEFA1-19C2-49B1-8D31-B5D4472328DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -640,6 +644,14 @@ Global {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Debug|Any CPU.Build.0 = Debug|Any CPU {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.ActiveCfg = Release|Any CPU {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.Build.0 = Release|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.Build.0 = Release|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -749,6 +761,8 @@ Global {64D99E19-EE25-465A-82E5-17B25F4C4E18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {E803DDB8-81EA-454B-9A66-9C2941100B67} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {88F6D091-CA16-4B71-9499-8D5B8FA2E712} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {575BEFA1-19C2-49B1-8D31-B5D4472328DE} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs index 46e13bcc3d..cbddb7c3c7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs @@ -14,19 +14,17 @@ namespace Volo.Abp.AspNetCore.Mvc.Client ConfigurationClient = configurationClient; } - public async Task CheckAsync(string name) + public async Task IsGrantedAsync(string name) { var configuration = await ConfigurationClient.GetAsync(); - return new PermissionGrantInfo( - name, - configuration.Auth.GrantedPolicies.ContainsKey(name) - ); + return configuration.Auth.GrantedPolicies.ContainsKey(name); } - public Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { - return CheckAsync(name); + /* This provider always works for the current principal. */ + return IsGrantedAsync(name); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs new file mode 100644 index 0000000000..ed82ed7f62 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FlagIconCss +{ + public class FlagIconCssStyleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icon.min.css"); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 4de46b2f0b..59c7de8840 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Auditing; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; @@ -26,6 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddFilters(MvcOptions options) { options.Filters.AddService(typeof(AbpAuditActionFilter)); + options.Filters.AddService(typeof(AbpFeatureActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs new file mode 100644 index 0000000000..577acda339 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class AbpFeatureActionFilter : IAsyncActionFilter, ITransientDependency + { + private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; + + public AbpFeatureActionFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) + { + _methodInvocationAuthorizationService = methodInvocationAuthorizationService; + } + + public async Task OnActionExecutionAsync( + ActionExecutingContext context, + ActionExecutionDelegate next) + { + if (!context.ActionDescriptor.IsControllerAction()) + { + await next(); + return; + } + + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + + using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.FeatureChecking)) + { + await _methodInvocationAuthorizationService.CheckAsync( + new MethodInvocationFeatureCheckerContext(methodInfo) + ); + + await next(); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index 5a330eb7a9..0a03a0dc67 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -12,7 +12,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow { public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency { - private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly UnitOfWorkDefaultOptions _defaultOptions; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index 57472d7cf1..880673f86b 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Localization; @@ -16,6 +18,7 @@ namespace Volo.Abp.Authorization public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded); + AutoAddDefinitionProviders(context.Services); } public override void ConfigureServices(ServiceConfigurationContext context) @@ -31,5 +34,23 @@ namespace Volo.Abp.Authorization options.ValueProviders.Add(); }); } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs index 77c8f4b665..e3f4425289 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using System.Threading.Tasks; +using Volo.Abp.Threading; namespace Volo.Abp.Authorization.Permissions { @@ -11,14 +12,14 @@ namespace Volo.Abp.Authorization.Permissions /// public class AlwaysAllowPermissionChecker : IPermissionChecker { - public Task CheckAsync(string name) + public Task IsGrantedAsync(string name) { - return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow")); + return TaskCache.TrueResult; } - public Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { - return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow")); + return TaskCache.TrueResult; } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs index b9eac0bab0..87b391443d 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs @@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value; if (clientId == null) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } - if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId)) - { - return new PermissionValueProviderGrantInfo(true, clientId); - } - - return PermissionValueProviderGrantInfo.NonGranted; + return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined; } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs index 833e09281c..b394a29f52 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs @@ -6,8 +6,8 @@ namespace Volo.Abp.Authorization.Permissions { public interface IPermissionChecker { - Task CheckAsync([NotNull]string name); + Task IsGrantedAsync([NotNull]string name); - Task CheckAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name); + Task IsGrantedAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs index d0a41b3390..ef92125595 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs @@ -1,8 +1,6 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Authorization.Permissions +namespace Volo.Abp.Authorization.Permissions { - public interface IPermissionDefinitionProvider : ISingletonDependency + public interface IPermissionDefinitionProvider { void Define(IPermissionDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs index 73ba3bfd17..a9b5e78337 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; namespace Volo.Abp.Authorization.Permissions { - public interface IPermissionValueProvider : ISingletonDependency + public interface IPermissionValueProvider { string Name { get; } - Task CheckAsync(PermissionValueCheckContext context); + //TODO: Rename to GetResult? (CheckAsync throws exception by naming convention) + Task CheckAsync(PermissionValueCheckContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs index b5c5cb5a4b..57c76ac6c9 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; namespace Volo.Abp.Authorization.Permissions { @@ -16,7 +17,7 @@ namespace Volo.Abp.Authorization.Permissions public Task IsGrantedAsync(string name, string providerName, string providerKey) { - return Task.FromResult(false); + return TaskCache.FalseResult; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs index 7113571c16..d69ac81e78 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs @@ -41,12 +41,12 @@ namespace Volo.Abp.Authorization.Permissions ); } - public virtual Task CheckAsync(string name) + public virtual Task IsGrantedAsync(string name) { - return CheckAsync(PrincipalAccessor.Principal, name); + return IsGrantedAsync(PrincipalAccessor.Principal, name); } - public virtual async Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public virtual async Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { Check.NotNull(name, nameof(name)); @@ -55,6 +55,8 @@ namespace Volo.Abp.Authorization.Permissions claimsPrincipal ); + var isGranted = false; + foreach (var provider in ValueProviders) { if (context.Permission.Providers.Any() && @@ -64,13 +66,18 @@ namespace Volo.Abp.Authorization.Permissions } var result = await provider.CheckAsync(context); - if (result.IsGranted) + + if (result == PermissionGrantResult.Granted) + { + isGranted = true; + } + else if (result == PermissionGrantResult.Prohibited) { - return new PermissionGrantInfo(context.Permission.Name, true, provider.Name, result.ProviderKey); + return false; } } - return new PermissionGrantInfo(context.Permission.Name, false); + return isGranted; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs deleted file mode 100644 index f9891862ff..0000000000 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Volo.Abp.Authorization.Permissions -{ - public static class PermissionCheckerExtensions - { - public static async Task IsGrantedAsync(this IPermissionChecker permissionChecker, string name) - { - return (await permissionChecker.CheckAsync(name)).IsGranted; - } - - public static async Task IsGrantedAsync(this IPermissionChecker permissionChecker, ClaimsPrincipal principal, string name) - { - return (await permissionChecker.CheckAsync(principal, name)).IsGranted; - } - - //TODO: Add sync extensions - } -} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index bb23757de5..de4ca1221f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -22,7 +22,7 @@ namespace Volo.Abp.Authorization.Permissions /// A list of allowed providers to get/set value of this permission. /// An empty list indicates that all providers are allowed. /// - public List Providers { get; } + public List Providers { get; } //TODO: Rename to AllowedProviders? public ILocalizableString DisplayName { @@ -53,7 +53,9 @@ namespace Volo.Abp.Authorization.Permissions set => Properties[name] = value; } - protected internal PermissionDefinition([NotNull] string name, ILocalizableString displayName = null) + protected internal PermissionDefinition( + [NotNull] string name, + ILocalizableString displayName = null) { Name = Check.NotNull(name, nameof(name)); DisplayName = displayName ?? new FixedLocalizableString(name); @@ -63,7 +65,9 @@ namespace Volo.Abp.Authorization.Permissions _children = new List(); } - public virtual PermissionDefinition AddChild([NotNull] string name, ILocalizableString displayName = null) + public virtual PermissionDefinition AddChild( + [NotNull] string name, + ILocalizableString displayName = null) { var child = new PermissionDefinition(name, displayName) { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs index 4915721af0..bb58089c82 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs @@ -10,9 +10,6 @@ namespace Volo.Abp.Authorization.Permissions { public class PermissionDefinitionManager : IPermissionDefinitionManager, ISingletonDependency { - protected List Providers => _lazyProviders.Value; - private readonly Lazy> _lazyProviders; - protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value; private readonly Lazy> _lazyPermissionGroupDefinitions; @@ -30,9 +27,15 @@ namespace Volo.Abp.Authorization.Permissions _serviceProvider = serviceProvider; Options = options.Value; - _lazyProviders = new Lazy>(CreatePermissionProviders, true); - _lazyPermissionDefinitions = new Lazy>(CreatePermissionDefinitions, true); - _lazyPermissionGroupDefinitions = new Lazy>(CreatePermissionGroupDefinitions, true); + _lazyPermissionDefinitions = new Lazy>( + CreatePermissionDefinitions, + isThreadSafe: true + ); + + _lazyPermissionGroupDefinitions = new Lazy>( + CreatePermissionGroupDefinitions, + isThreadSafe: true + ); } public virtual PermissionDefinition Get(string name) @@ -64,14 +67,6 @@ namespace Volo.Abp.Authorization.Permissions return PermissionGroupDefinitions.Values.ToImmutableList(); } - protected virtual List CreatePermissionProviders() - { - return Options - .DefinitionProviders - .Select(p => _serviceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) - .ToList(); - } - protected virtual Dictionary CreatePermissionDefinitions() { var permissions = new Dictionary(); @@ -87,7 +82,9 @@ namespace Volo.Abp.Authorization.Permissions return permissions; } - protected virtual void AddPermissionToDictionaryRecursively(Dictionary permissions, PermissionDefinition permission) + protected virtual void AddPermissionToDictionaryRecursively( + Dictionary permissions, + PermissionDefinition permission) { if (permissions.ContainsKey(permission.Name)) { @@ -106,9 +103,17 @@ namespace Volo.Abp.Authorization.Permissions { var context = new PermissionDefinitionContext(); - foreach (var provider in Providers) + using (var scope = _serviceProvider.CreateScope()) { - provider.Define(context); + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(context); + } } return context.Groups; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs index df1674dc2e..0584ffe426 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs @@ -1,6 +1,8 @@ -namespace Volo.Abp.Authorization.Permissions +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.Permissions { - public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider + public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency { public abstract void Define(IPermissionDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs new file mode 100644 index 0000000000..0b8b23e426 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Authorization.Permissions +{ + public enum PermissionGrantResult + { + Undefined, + Granted, + Prohibited + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index a52bd4f601..623db24c8a 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -37,7 +37,9 @@ namespace Volo.Abp.Authorization.Permissions set => Properties[name] = value; } - protected internal PermissionGroupDefinition(string name, ILocalizableString displayName = null) + protected internal PermissionGroupDefinition( + string name, + ILocalizableString displayName = null) { Name = name; DisplayName = displayName ?? new FixedLocalizableString(Name); @@ -46,7 +48,9 @@ namespace Volo.Abp.Authorization.Permissions _permissions = new List(); } - public virtual PermissionDefinition AddPermission(string name, ILocalizableString displayName = null) + public virtual PermissionDefinition AddPermission( + string name, + ILocalizableString displayName = null) { var permission = new PermissionDefinition(name, displayName); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs index 83602a0c46..bc39384938 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs @@ -11,7 +11,9 @@ namespace Volo.Abp.Authorization.Permissions [CanBeNull] public ClaimsPrincipal Principal { get; } - public PermissionValueCheckContext([NotNull] PermissionDefinition permission, [CanBeNull] ClaimsPrincipal principal) + public PermissionValueCheckContext( + [NotNull] PermissionDefinition permission, + [CanBeNull] ClaimsPrincipal principal) { Check.NotNull(permission, nameof(permission)); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs index 066d876637..91351593a5 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.Authorization.Permissions { - public abstract class PermissionValueProvider : IPermissionValueProvider + public abstract class PermissionValueProvider : IPermissionValueProvider, ISingletonDependency { public abstract string Name { get; } @@ -13,6 +14,6 @@ namespace Volo.Abp.Authorization.Permissions PermissionStore = permissionStore; } - public abstract Task CheckAsync(PermissionValueCheckContext context); + public abstract Task CheckAsync(PermissionValueCheckContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs index 1d200ac49a..a008190fbd 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs @@ -16,23 +16,23 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var roles = context.Principal?.FindAll(AbpClaimTypes.Role).Select(c => c.Value).ToArray(); if (roles == null || !roles.Any()) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } foreach (var role in roles) { if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, role)) { - return new PermissionValueProviderGrantInfo(true, role); + return PermissionGrantResult.Granted; } } - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs index 9f6022907e..f04a85910f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs @@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value; if (userId == null) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } - if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId)) - { - return new PermissionValueProviderGrantInfo(true, userId); - } - - return PermissionValueProviderGrantInfo.NonGranted; + return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined; } } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs index 35aa6d0811..4c8033a0cf 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs @@ -12,6 +12,7 @@ namespace Volo.Abp.Aspects public const string Validation = "AbpValidation"; public const string UnitOfWork = "AbpUnitOfWork"; public const string Authorization = "AbpAuthorization"; + public const string FeatureChecking = "AbpFeatureChecking"; public static void AddApplied(object obj, params string[] concerns) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs b/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs index a9525b9064..c7a4e7c0fe 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs @@ -8,17 +8,11 @@ namespace Volo.Abp [Serializable] public class NameValue : NameValue { - /// - /// Creates a new . - /// public NameValue() { } - /// - /// Creates a new . - /// public NameValue(string name, string value) { Name = name; @@ -42,17 +36,11 @@ namespace Volo.Abp /// public T Value { get; set; } - /// - /// Creates a new . - /// public NameValue() { } - /// - /// Creates a new . - /// public NameValue(string name, T value) { Name = name; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs new file mode 100644 index 0000000000..0cb57ca0c5 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Threading +{ + public static class TaskCache + { + public static Task TrueResult { get; } + public static Task FalseResult { get; } + + static TaskCache() + { + TrueResult = Task.FromResult(true); + FalseResult = Task.FromResult(false); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj index 57f909667b..243c94e66a 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj +++ b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj @@ -17,6 +17,7 @@ + diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs index 84b609b99f..fcdc29c454 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.Application.Services; using Volo.Abp.Authorization; using Volo.Abp.Domain; +using Volo.Abp.Features; using Volo.Abp.Http; using Volo.Abp.Http.Modeling; using Volo.Abp.Modularity; @@ -20,7 +21,8 @@ namespace Volo.Abp.Application typeof(AbpValidationModule), typeof(AbpAuthorizationModule), typeof(AbpHttpAbstractionsModule), - typeof(AbpSettingsModule) + typeof(AbpSettingsModule), + typeof(AbpFeaturesModule) )] public class AbpDddApplicationModule : AbpModule { @@ -30,8 +32,8 @@ namespace Volo.Abp.Application { options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService)); - options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); //TODO: Move to it's own module if possible? - options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled)); //TODO: Move to it's own module if possible? + options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); + options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled)); }); } } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs index 63a749e259..b4f53aff0a 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs @@ -9,6 +9,7 @@ using Volo.Abp.Aspects; using Volo.Abp.Auditing; using Volo.Abp.Authorization; using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; @@ -51,6 +52,8 @@ namespace Volo.Abp.Application.Services public IAuthorizationService AuthorizationService { get; set; } + public IFeatureChecker FeatureChecker { get; set; } + protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; protected ILogger Logger => _lazyLogger.Value; diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj index 52d430bbaa..5e9c7a91fb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj @@ -16,9 +16,9 @@ - + - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs index 35afda3d1e..542bf9e19e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using MySql.Data.EntityFrameworkCore.Infraestructure; using System; +using Microsoft.EntityFrameworkCore.Infrastructure; using Volo.Abp.EntityFrameworkCore.DependencyInjection; namespace Volo.Abp.EntityFrameworkCore @@ -10,15 +10,15 @@ namespace Volo.Abp.EntityFrameworkCore { public static DbContextOptionsBuilder UseMySQL( [NotNull] this AbpDbContextConfigurationContext context, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) { if (context.ExistingConnection != null) { - return context.DbContextOptions.UseMySQL(context.ExistingConnection, mySQLOptionsAction); + return context.DbContextOptions.UseMySql(context.ExistingConnection, mySQLOptionsAction); } else { - return context.DbContextOptions.UseMySQL(context.ConnectionString, mySQLOptionsAction); + return context.DbContextOptions.UseMySql(context.ConnectionString, mySQLOptionsAction); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs index f71bc631ec..3e2dd911be 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -using MySql.Data.EntityFrameworkCore.Infraestructure; using System; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace Volo.Abp.EntityFrameworkCore { @@ -8,7 +8,7 @@ namespace Volo.Abp.EntityFrameworkCore { public static void UseMySQL( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) { options.Configure(context => { @@ -18,7 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore public static void UseMySQL( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) where TDbContext : AbpDbContext { options.Configure(context => diff --git a/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj new file mode 100644 index 0000000000..e626a34a9f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj @@ -0,0 +1,21 @@ + + + + + + netstandard2.0 + Volo.Abp.Features + Volo.Abp.Features + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs new file mode 100644 index 0000000000..9065651b3b --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Features +{ + [DependsOn( + typeof(AbpLocalizationAbstractionsModule), + typeof(AbpMultiTenancyAbstractionsModule) + )] + public class AbpFeaturesModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded); + AutoAddDefinitionProviders(context.Services); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.Configure(options => + { + options.ValueProviders.Add(); + options.ValueProviders.Add(); + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs new file mode 100644 index 0000000000..d60a55586a --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public class DefaultValueFeatureValueProvider : FeatureValueProvider + { + public const string ProviderName = "Default"; + + public override string Name => ProviderName; + + public DefaultValueFeatureValueProvider(IFeatureStore settingStore) + : base(settingStore) + { + + } + + public override Task GetOrNullAsync(FeatureDefinition setting) + { + return Task.FromResult(setting.DefaultValue); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs new file mode 100644 index 0000000000..194f0eaf90 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Features +{ + [AttributeUsage(AttributeTargets.Method)] + public class DisableFeatureCheckAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs new file mode 100644 index 0000000000..bc9f8565ed --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Features +{ + public class FeatureChecker : FeatureCheckerBase + { + protected FeatureOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + protected IFeatureDefinitionManager FeatureDefinitionManager { get; } + protected List Providers => _providers.Value; + + private readonly Lazy> _providers; + + public FeatureChecker( + IOptions options, + IServiceProvider serviceProvider, + IFeatureDefinitionManager featureDefinitionManager) + { + ServiceProvider = serviceProvider; + FeatureDefinitionManager = featureDefinitionManager; + + Options = options.Value; + + _providers = new Lazy>( + () => Options + .ValueProviders + .Select(type => ServiceProvider.GetRequiredService(type) as IFeatureValueProvider) + .ToList(), + true + ); + } + + public override async Task GetOrNullAsync(string name) + { + var featureDefinition = FeatureDefinitionManager.Get(name); + var providers = Enumerable + .Reverse(Providers); + + if (featureDefinition.AllowedProviders.Any()) + { + providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name)); + } + + return await GetOrNullValueFromProvidersAsync(providers, featureDefinition); + } + + protected virtual async Task GetOrNullValueFromProvidersAsync( + IEnumerable providers, + FeatureDefinition feature) + { + foreach (var provider in providers) + { + var value = await provider.GetOrNullAsync(feature); + if (value != null) + { + return value; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs new file mode 100644 index 0000000000..a24e0901f7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureCheckerBase : IFeatureChecker, ITransientDependency + { + public abstract Task GetOrNullAsync(string name); + + public virtual async Task IsEnabledAsync(string name) + { + var value = await GetOrNullAsync(name); + if (value == null) + { + return false; + } + + try + { + return bool.Parse(value); + } + catch (Exception ex) + { + throw new AbpException( + $"The value '{value}' for the feature '{name}' should be a boolean, but was not!", + ex + ); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs new file mode 100644 index 0000000000..9d7f608c4b --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization; +using Volo.Abp.Threading; + +namespace Volo.Abp.Features +{ + public static class FeatureCheckerExtensions + { + public static async Task GetAsync( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name, + T defaultValue = default) + where T : struct + { + Check.NotNull(featureChecker, nameof(featureChecker)); + Check.NotNull(name, nameof(name)); + + var value = await featureChecker.GetOrNullAsync(name); + return value?.To() ?? defaultValue; + } + + public static string GetOrNull( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name) + { + Check.NotNull(featureChecker, nameof(featureChecker)); + return AsyncHelper.RunSync(() => featureChecker.GetOrNullAsync(name)); + } + + public static T Get( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name, + T defaultValue = default) + where T : struct + { + return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue)); + } + + public static bool IsEnabled( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name) + { + return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name)); + } + + public static async Task IsEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + if (featureNames.IsNullOrEmpty()) + { + return true; + } + + if (requiresAll) + { + foreach (var featureName in featureNames) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + return false; + } + } + + return true; + } + + foreach (var featureName in featureNames) + { + if (await featureChecker.IsEnabledAsync(featureName)) + { + return true; + } + } + + return false; + } + + public static bool IsEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(requiresAll, featureNames)); + } + + public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, string featureName) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + throw new AbpAuthorizationException("Feature is not enabled: " + featureName); + } + } + + public static void CheckEnabled(this IFeatureChecker featureChecker, string featureName) + { + if (!featureChecker.IsEnabled(featureName)) + { + throw new AbpAuthorizationException("Feature is not enabled: " + featureName); + } + } + + public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + if (featureNames.IsNullOrEmpty()) + { + return; + } + + if (requiresAll) + { + foreach (var featureName in featureNames) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + throw new AbpAuthorizationException( + "Required features are not enabled. All of these features must be enabled: " + + string.Join(", ", featureNames) + ); + } + } + } + else + { + foreach (var featureName in featureNames) + { + if (await featureChecker.IsEnabledAsync(featureName)) + { + return; + } + } + + throw new AbpAuthorizationException( + "Required features are not enabled. At least one of these features must be enabled: " + + string.Join(", ", featureNames) + ); + } + } + + public static void CheckEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + AsyncHelper.RunSync(() => featureChecker.CheckEnabledAsync(requiresAll, featureNames)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs new file mode 100644 index 0000000000..6a1c030b21 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs @@ -0,0 +1,164 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public class FeatureDefinition + { + /// + /// Unique name of the feature. + /// + [NotNull] + public string Name { get; } + + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + [CanBeNull] + public ILocalizableString Description { get; set; } + + /// + /// Parent of this feature, if one exists. + /// If set, this feature can be enabled only if the parent is enabled. + /// + public FeatureDefinition Parent { get; private set; } + + /// + /// List of child features. + /// + public IReadOnlyList Children => _children.ToImmutableList(); + private readonly List _children; + + /// + /// Default value of the feature. + /// + [CanBeNull] + public string DefaultValue { get; set; } + + /// + /// Can clients see this feature and it's value. + /// Default: true. + /// + public bool IsVisibleToClients { get; set; } + + /// + /// A list of allowed providers to get/set value of this feature. + /// An empty list indicates that all providers are allowed. + /// + public List AllowedProviders { get; } + + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + + //TODO: Implement input type like old ABP! + + public FeatureDefinition( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + bool isVisibleToClients = true) + { + Name = name; + DefaultValue = defaultValue; + IsVisibleToClients = isVisibleToClients; + DisplayName = displayName ?? new FixedLocalizableString(name); + Description = description; + + Properties = new Dictionary(); + AllowedProviders = new List(); + _children = new List(); + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureDefinition WithProviders(params string[] providers) + { + if (!providers.IsNullOrEmpty()) + { + AllowedProviders.AddRange(providers); + } + + return this; + } + + /// + /// Adds a child feature. + /// + /// Returns a newly created child feature + public FeatureDefinition CreateChild( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + bool isVisibleToClients = true) + { + var feature = new FeatureDefinition( + name, + defaultValue, + displayName, + description, + isVisibleToClients) + { + Parent = this + }; + + _children.Add(feature); + return feature; + } + + public void RemoveChild(string name) + { + var featureToRemove = _children.FirstOrDefault(f => f.Name == name); + if (featureToRemove == null) + { + throw new AbpException($"Could not find a feature named '{name}' in the Children of this feature '{Name}'."); + } + + featureToRemove.Parent = null; + _children.Remove(featureToRemove); + } + + public override string ToString() + { + return $"[{nameof(FeatureDefinition)}: {Name}]"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs new file mode 100644 index 0000000000..be9c1beec9 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionContext : IFeatureDefinitionContext + { + internal Dictionary Groups { get; } + + public FeatureDefinitionContext() + { + Groups = new Dictionary(); + } + + public FeatureGroupDefinition AddGroup(string name, ILocalizableString displayName = null) + { + Check.NotNull(name, nameof(name)); + + if (Groups.ContainsKey(name)) + { + throw new AbpException($"There is already an existing permission group with name: {name}"); + } + + return Groups[name] = new FeatureGroupDefinition(name, displayName); + } + + public FeatureGroupDefinition GetGroupOrNull(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + return null; + } + + return Groups[name]; + } + + public void RemoveGroup(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + throw new AbpException($"Undefined feature group: '{name}'."); + } + + Groups.Remove(name); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs new file mode 100644 index 0000000000..13d15ac95e --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency + { + protected IDictionary FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; + private readonly Lazy> _lazyFeatureGroupDefinitions; + + protected IDictionary FeatureDefinitions => _lazyFeatureDefinitions.Value; + private readonly Lazy> _lazyFeatureDefinitions; + + protected FeatureOptions Options { get; } + + private readonly IServiceProvider _serviceProvider; + + public FeatureDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Options = options.Value; + + _lazyFeatureDefinitions = new Lazy>( + CreateFeatureDefinitions, + isThreadSafe: true + ); + + _lazyFeatureGroupDefinitions = new Lazy>( + CreateFeatureGroupDefinitions, + isThreadSafe:true + ); + } + + public virtual FeatureDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var feature = GetOrNull(name); + + if (feature == null) + { + throw new AbpException("Undefined feature: " + name); + } + + return feature; + } + + public virtual IReadOnlyList GetAll() + { + return FeatureDefinitions.Values.ToImmutableList(); + } + + public virtual FeatureDefinition GetOrNull(string name) + { + return FeatureDefinitions.GetOrDefault(name); + } + + protected virtual Dictionary CreateFeatureDefinitions() + { + var features = new Dictionary(); + + foreach (var groupDefinition in FeatureGroupDefinitions.Values) + { + foreach (var feature in groupDefinition.Features) + { + AddFeatureToDictionaryRecursively(features, feature); + } + } + + return features; + } + + protected virtual void AddFeatureToDictionaryRecursively( + Dictionary features, + FeatureDefinition feature) + { + if (features.ContainsKey(feature.Name)) + { + throw new AbpException("Duplicate feature name: " + feature.Name); + } + + features[feature.Name] = feature; + + foreach (var child in feature.Children) + { + AddFeatureToDictionaryRecursively(features, child); + } + } + + protected virtual Dictionary CreateFeatureGroupDefinitions() + { + var context = new FeatureDefinitionContext(); + + using (var scope = _serviceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(context); + } + } + + return context.Groups; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs new file mode 100644 index 0000000000..f3c7a7f9b0 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency + { + public abstract void Define(IFeatureDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs new file mode 100644 index 0000000000..4d0ccbba62 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public class FeatureGroupDefinition + { + /// + /// Unique name of the group. + /// + public string Name { get; } + + public Dictionary Properties { get; } + + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + public IReadOnlyList Features => _features.ToImmutableList(); + private readonly List _features; + + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + protected internal FeatureGroupDefinition( + string name, + ILocalizableString displayName = null) + { + Name = name; + DisplayName = displayName ?? new FixedLocalizableString(Name); + + Properties = new Dictionary(); + _features = new List(); + } + + public virtual FeatureDefinition AddFeature( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + bool isVisibleToClients = true) + { + var feature = new FeatureDefinition( + name, + defaultValue, + displayName, + description, + isVisibleToClients + ); + + _features.Add(feature); + + return feature; + } + + public virtual List GetFeaturesWithChildren() + { + var features = new List(); + + foreach (var feature in _features) + { + AddFeatureToListRecursively(features, feature); + } + + return features; + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureGroupDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + + private void AddFeatureToListRecursively(List features, FeatureDefinition feature) + { + features.Add(feature); + + foreach (var child in feature.Children) + { + AddFeatureToListRecursively(features, child); + } + } + + public override string ToString() + { + return $"[{nameof(FeatureGroupDefinition)} {Name}]"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs new file mode 100644 index 0000000000..2f684d99ef --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.Threading; + +namespace Volo.Abp.Features +{ + public class FeatureInterceptor : AbpInterceptor, ITransientDependency + { + private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; + + public FeatureInterceptor( + IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) + { + _methodInvocationAuthorizationService = methodInvocationAuthorizationService; + } + + public override void Intercept(IAbpMethodInvocation invocation) + { + if (AbpCrossCuttingConcerns.IsApplied( + invocation.TargetObject, + AbpCrossCuttingConcerns.FeatureChecking)) + { + invocation.Proceed(); + return; + } + + AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); + invocation.Proceed(); + } + + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + if (AbpCrossCuttingConcerns.IsApplied( + invocation.TargetObject, + AbpCrossCuttingConcerns.FeatureChecking)) + { + await invocation.ProceedAsync(); + return; + } + + AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); + await invocation.ProceedAsync(); + } + + protected virtual Task CheckFeaturesAsync(IAbpMethodInvocation invocation) + { + return _methodInvocationAuthorizationService.CheckAsync( + new MethodInvocationFeatureCheckerContext( + invocation.Method + ) + ); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs new file mode 100644 index 0000000000..4ad03c9aff --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Reflection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public static class FeatureInterceptorRegistrar + { + public static void RegisterIfNeeded(IOnServiceRegistredContext context) + { + if (ShouldIntercept(context.ImplementationType)) + { + context.Interceptors.TryAdd(); + } + } + + private static bool ShouldIntercept(Type type) + { + return type.IsDefined(typeof(RequiresFeatureAttribute), true) || + AnyMethodRequiresFeatureAttribute(type); + } + + private static bool AnyMethodRequiresFeatureAttribute(Type implementationType) + { + return implementationType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Any(HasRequiresFeatureAttribute); + } + + private static bool HasRequiresFeatureAttribute(MemberInfo methodInfo) + { + return methodInfo.IsDefined(typeof(RequiresFeatureAttribute), true); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs new file mode 100644 index 0000000000..f988acf34c --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs @@ -0,0 +1,17 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.Features +{ + public class FeatureOptions + { + public ITypeList DefinitionProviders { get; } + + public ITypeList ValueProviders { get; } + + public FeatureOptions() + { + DefinitionProviders = new TypeList(); + ValueProviders = new TypeList(); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs new file mode 100644 index 0000000000..22b7b6ab89 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.Features +{ + [Serializable] + public class FeatureValue : NameValue + { + public FeatureValue() + { + + } + + public FeatureValue(string name, string value) + { + Name = name; + Value = value; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs new file mode 100644 index 0000000000..b93b6903e2 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureValueProvider : IFeatureValueProvider, ISingletonDependency + { + public abstract string Name { get; } + + protected IFeatureStore FeatureStore { get; } + + protected FeatureValueProvider(IFeatureStore featureStore) + { + FeatureStore = featureStore; + } + + public abstract Task GetOrNullAsync(FeatureDefinition feature); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs new file mode 100644 index 0000000000..2676506733 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IFeatureChecker + { + Task GetOrNullAsync([NotNull] string name); + + Task IsEnabledAsync(string name); + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs new file mode 100644 index 0000000000..03fc14c579 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionContext + { + FeatureGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null); + + FeatureGroupDefinition GetGroupOrNull(string name); + + void RemoveGroup(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs new file mode 100644 index 0000000000..c0172d7bc3 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionManager + { + [NotNull] + FeatureDefinition Get([NotNull] string name); + + IReadOnlyList GetAll(); + + FeatureDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs new file mode 100644 index 0000000000..30e2f6b9f7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionProvider + { + void Define(IFeatureDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs new file mode 100644 index 0000000000..58cce989c7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureStore + { + Task GetOrNullAsync( + [NotNull] string name, + [CanBeNull] string providerName, + [CanBeNull] string providerKey + ); + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs new file mode 100644 index 0000000000..67eee5228e --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureValueProvider + { + string Name { get; } + + Task GetOrNullAsync([NotNull] FeatureDefinition feature); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs new file mode 100644 index 0000000000..7d6be55c47 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IMethodInvocationFeatureCheckerService + { + Task CheckAsync( + MethodInvocationFeatureCheckerContext context + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs new file mode 100644 index 0000000000..81bbecfe68 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace Volo.Abp.Features +{ + public class MethodInvocationFeatureCheckerContext + { + public MethodInfo Method { get; } + + public MethodInvocationFeatureCheckerContext(MethodInfo method) + { + Method = method; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs new file mode 100644 index 0000000000..28ac25fdd5 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class MethodInvocationFeatureCheckerService : IMethodInvocationFeatureCheckerService, ITransientDependency + { + private readonly IFeatureChecker _featureChecker; + + public MethodInvocationFeatureCheckerService( + IFeatureChecker featureChecker) + { + _featureChecker = featureChecker; + } + + public async Task CheckAsync(MethodInvocationFeatureCheckerContext context) + { + if (IsFeatureCheckDisabled(context)) + { + return; + } + + foreach (var requiresFeatureAttribute in GetRequiredFeatureAttributes(context)) + { + await _featureChecker.CheckEnabledAsync(requiresFeatureAttribute.RequiresAll, requiresFeatureAttribute.Features); + } + } + + protected virtual bool IsFeatureCheckDisabled(MethodInvocationFeatureCheckerContext context) + { + return context.Method + .GetCustomAttributes(true) + .OfType() + .Any(); + } + + protected virtual RequiresFeatureAttribute[] GetRequiredFeatureAttributes(MethodInvocationFeatureCheckerContext context) + { + var classAttributes = context.Method.DeclaringType + .GetCustomAttributes(true) + .OfType(); + + var methodAttributes = context.Method + .GetCustomAttributes(true) + .OfType(); + + return classAttributes.Union(methodAttributes).ToArray(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs new file mode 100644 index 0000000000..d9b839a664 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + [Dependency(TryRegister = true)] + public class NullFeatureStore : IFeatureStore, ISingletonDependency + { + public ILogger Logger { get; set; } + + public NullFeatureStore() + { + Logger = NullLogger.Instance; + } + + public Task GetOrNullAsync(string name, string providerName, string providerKey) + { + return Task.FromResult((string) null); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs new file mode 100644 index 0000000000..2f5b62c128 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Volo.Abp.Features +{ + /// + /// This attribute can be used on a class/method to declare that given class/method is available + /// only if required feature(s) are enabled. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class RequiresFeatureAttribute : Attribute + { + /// + /// A list of features to be checked if they are enabled. + /// + public string[] Features { get; } + + /// + /// If this property is set to true, all of the must be enabled. + /// If it's false, at least one of the must be enabled. + /// Default: false. + /// + public bool RequiresAll { get; set; } + + /// + /// Creates a new instance of class. + /// + /// A list of features to be checked if they are enabled + public RequiresFeatureAttribute(params string[] features) + { + Features = features ?? Array.Empty(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs new file mode 100644 index 0000000000..73b07b6e88 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Features +{ + public class TenantFeatureValueProvider : FeatureValueProvider + { + public const string ProviderName = "Tenant"; + + public override string Name => ProviderName; + + protected ICurrentTenant CurrentTenant { get; } + + public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) + : base(featureStore) + { + CurrentTenant = currentTenant; + } + + public override async Task GetOrNullAsync(FeatureDefinition feature) + { + return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj index 05291caa45..18660be71e 100644 --- a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj @@ -14,7 +14,6 @@ - diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs index 58de6cdb99..5d33034993 100644 --- a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs @@ -1,10 +1,12 @@ using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Security; namespace Volo.Abp.MultiTenancy { [DependsOn( - typeof(AbpDataModule) + typeof(AbpDataModule), + typeof(AbpSecurityModule) )] public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule? { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs similarity index 100% rename from framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs rename to framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index 8c711e12c2..1951ab61b5 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -1,8 +1,10 @@ -using Volo.Abp.Localization; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security; -using Volo.Abp.Users; namespace Volo.Abp.Settings { @@ -13,6 +15,11 @@ namespace Volo.Abp.Settings )] public class AbpSettingsModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -23,5 +30,23 @@ namespace Volo.Abp.Settings options.ValueProviders.Add(); }); } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(ISettingDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs index 705f428792..8699fae972 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Settings { + [Dependency(TryRegister = true)] public class NullSettingStore : ISettingStore, ISingletonDependency { public ILogger Logger { get; set; } @@ -19,21 +19,5 @@ namespace Volo.Abp.Settings { return Task.FromResult((string) null); } - - public Task SetAsync(string name, string value, string providerName, string providerKey) - { - Logger.LogWarning($"Setting the value for {name} is not possible because current setting store is {nameof(NullSettingStore)}"); - return Task.CompletedTask; - } - - public Task> GetListAsync(string providerName, string providerKey) - { - return Task.FromResult(new List()); - } - - public Task DeleteAsync(string name, string providerName, string providerKey) - { - return Task.CompletedTask; - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs index 9f02389248..127c724dc1 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs @@ -40,7 +40,7 @@ namespace Volo.Abp.Settings /// A list of allowed providers to get/set value of this setting. /// An empty list indicates that all providers are allowed. /// - public List Providers { get; } + public List Providers { get; } //TODO: Rename to AllowedProviders /// /// Is this setting inherited from parent scopes. diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs index 7241d3654d..387ea77d05 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs @@ -10,22 +10,19 @@ namespace Volo.Abp.Settings { public class SettingDefinitionManager : ISettingDefinitionManager, ISingletonDependency { - protected Lazy> Providers { get; } - protected Lazy> SettingDefinitions { get; } protected SettingOptions Options { get; } - private readonly IServiceProvider _serviceProvider; + protected IServiceProvider ServiceProvider { get; } public SettingDefinitionManager( IOptions options, IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; Options = options.Value; - Providers = new Lazy>(CreateSettingProviders, true); SettingDefinitions = new Lazy>(CreateSettingDefinitions, true); } @@ -53,21 +50,21 @@ namespace Volo.Abp.Settings return SettingDefinitions.Value.GetOrDefault(name); } - protected virtual List CreateSettingProviders() - { - return Options - .DefinitionProviders - .Select(p => _serviceProvider.GetRequiredService(p) as ISettingDefinitionProvider) - .ToList(); - } - protected virtual IDictionary CreateSettingDefinitions() { var settings = new Dictionary(); - foreach (var provider in Providers.Value) + using (var scope = ServiceProvider.CreateScope()) { - provider.Define(new SettingDefinitionContext(settings)); + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ISettingDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(new SettingDefinitionContext(settings)); + } } return settings; diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs index 04e6fc7eb2..8cd4b28003 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Settings { - public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ISingletonDependency + public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ITransientDependency { public abstract void Define(ISettingDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs index 63abadae12..3629c9eadb 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.Settings Providers = new Lazy>( () => Options .ValueProviders - .Select(c => serviceProvider.GetRequiredService(c) as ISettingValueProvider) + .Select(type => serviceProvider.GetRequiredService(type) as ISettingValueProvider) .ToList(), true ); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 014cee2077..eb0fc1ef48 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -6,7 +6,6 @@ using Volo.Abp.AspNetCore.Mvc.Authorization; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization.Resource; using Volo.Abp.AspNetCore.TestBase; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Autofac; using Volo.Abp.Localization; using Volo.Abp.Localization.Resources.AbpValidation; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs new file mode 100644 index 0000000000..04e7fa908a --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FakeFeatureChecker : FeatureCheckerBase + { + public override Task GetOrNullAsync(string name) + { + return Task.FromResult(GetOrNull(name)); + } + + private static string GetOrNull(string name) + { + switch (name) + { + case "AllowedFeature": + return true.ToString(); + case "NotAllowedFeature": + return null; //or false, doesn't matter + } + + throw new ApplicationException($"Unknown feature: '{name}'"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs new file mode 100644 index 0000000000..1d2549323d --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + [Route("api/feature-test")] + public class FeatureTestController : AbpController + { + [HttpGet] + [Route("allowed-feature")] + [RequiresFeature("AllowedFeature")] + public Task AllowedFeatureAsync() + { + return Task.CompletedTask; + } + + [HttpGet] + [Route("not-allowed-feature")] + [RequiresFeature("NotAllowedFeature")] + public void NotAllowedFeature() + { + + } + + [HttpGet] + [Route("no-feature")] + public int NoFeature() + { + return 42; + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs new file mode 100644 index 0000000000..637dbe3d2b --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FeatureTestController_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Allow_Enabled_Features() + { + await GetResponseAsStringAsync( + "/api/feature-test/allowed-feature" + ); + } + + [Fact] + public async Task Should_Not_Allow_Not_Enabled_Features() + { + await GetResponseAsStringAsync( + "/api/feature-test/not-allowed-feature", + HttpStatusCode.Unauthorized + ); + } + + [Fact] + public async Task Should_Allow_Actions_With_No_Feature() + { + await GetResponseAsStringAsync( + "/api/feature-test/no-feature" + ); + } + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index 9b539e85be..51337ae871 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.TestServices; using Volo.Abp.Autofac; using Volo.Abp.Modularity; @@ -20,13 +19,5 @@ namespace Volo.Abp.Authorization } }); } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.DefinitionProviders.TryAdd(); - }); - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj new file mode 100644 index 0000000000..3632aa2bf3 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.2 + latest + Volo.Abp.Features.Tests + Volo.Abp.Features.Tests + true + false + false + false + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs new file mode 100644 index 0000000000..7b0927b80d --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs @@ -0,0 +1,18 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Features +{ + [DependsOn( + typeof(AbpFeaturesModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule) + )] + public class AbpFeaturesTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs new file mode 100644 index 0000000000..425f69337f --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs @@ -0,0 +1,23 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + [RequiresFeature("BooleanTestFeature1")] + public class ClassFeatureTestService : ITransientDependency + { + /* Since this class is used with the class reference, + * need to virtual keywords, otherwise dynamic proxy can not work. + */ + + [RequiresFeature("BooleanTestFeature2")] + public virtual int Feature2() + { + return 42; + } + + public virtual void NoAdditionalFeature() + { + + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs new file mode 100644 index 0000000000..7ea01268a0 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs @@ -0,0 +1,53 @@ +using Shouldly; +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureChecker_Tests : FeatureTestBase + { + private readonly IFeatureChecker _featureChecker; + private readonly ICurrentTenant _currentTenant; + + public FeatureChecker_Tests() + { + _featureChecker = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public async Task IsEnabledAsync() + { + //Tenant is unknown + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); + + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeTrue(); + } + + using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) + { + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); + } + } + + [Fact] + public async Task GetOrNullAsync() + { + //Tenant is unknown + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); + + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); + } + + using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) + { + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("34"); + } + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs new file mode 100644 index 0000000000..5a4bbc9ea1 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs @@ -0,0 +1,35 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionManager_Tests : FeatureTestBase + { + private readonly IFeatureDefinitionManager _featureDefinitionManager; + + public FeatureDefinitionManager_Tests() + { + _featureDefinitionManager = GetRequiredService(); + } + + [Fact] + public void Should_Get_Defined_Features() + { + _featureDefinitionManager.GetOrNull("BooleanTestFeature1").ShouldNotBeNull(); + _featureDefinitionManager.Get("BooleanTestFeature1").Name.ShouldBe("BooleanTestFeature1"); + + _featureDefinitionManager.GetOrNull("IntegerTestFeature1").ShouldNotBeNull(); + _featureDefinitionManager.Get("IntegerTestFeature1").Name.ShouldBe("IntegerTestFeature1"); + } + + [Fact] + public void Should_Not_Get_Undefined_Features() + { + _featureDefinitionManager.GetOrNull("UndefinedFeature").ShouldBeNull(); + Assert.Throws(() => + { + _featureDefinitionManager.Get("UndefinedFeature"); + }); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs new file mode 100644 index 0000000000..159e2ff241 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs @@ -0,0 +1,76 @@ +using Shouldly; +using System; +using System.Threading.Tasks; +using Volo.Abp.Authorization; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureInterceptor_Tests : FeatureTestBase + { + private readonly ClassFeatureTestService _classFeatureTestService; + private readonly IMethodFeatureTestService _methodFeatureTestService; + private readonly ICurrentTenant _currentTenant; + + public FeatureInterceptor_Tests() + { + _classFeatureTestService = GetRequiredService(); + _methodFeatureTestService = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Theory] + [InlineData(null)] //Features were not enabled for null tenantid + [InlineData(TestFeatureStore.Tenant2IdValue)] //Features were not enabled for Tenant 2 + public async Task Should_Not_Allow_To_Method_Calls_If_Related_Features_Were_Not_Enabled(string tenantIdValue) + { + using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) + { + Assert.Throws(() => + { + _classFeatureTestService.NoAdditionalFeature(); + }); + + Assert.Throws(() => + { + _classFeatureTestService.Feature2(); + }); + + await Assert.ThrowsAsync(async () => + { + await _methodFeatureTestService.Feature1Async(); + }); + } + } + + [Fact] + public async Task Should_Allow_To_Method_Calls_If_Related_Features_Were_Enabled() + { + //Features were enabled for Tenant 1 + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + _classFeatureTestService.NoAdditionalFeature(); + _classFeatureTestService.Feature2().ShouldBe(42); + (await _methodFeatureTestService.Feature1Async()).ShouldBe(42); + } + } + + [Theory] + [InlineData(null)] + [InlineData(TestFeatureStore.Tenant1IdValue)] + [InlineData(TestFeatureStore.Tenant2IdValue)] + public async Task Should_Allow_To_Method_Calls_For_Those_Have_No_RequiresFeature_Attributes(string tenantIdValue) + { + using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) + { + await _methodFeatureTestService.NonFeatureAsync(); + } + } + + private static Guid? ParseNullableGuid(string tenantIdValue) + { + return tenantIdValue == null ? (Guid?)null : new Guid(tenantIdValue); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs new file mode 100644 index 0000000000..b12aa9f526 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.Features +{ + public class FeatureTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs new file mode 100644 index 0000000000..7bc16b7e04 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IMethodFeatureTestService + { + Task Feature1Async(); + + Task NonFeatureAsync(); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs new file mode 100644 index 0000000000..e627e0c5f1 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class MethodFeatureTestService : ITransientDependency, IMethodFeatureTestService + { + /* Since this class is used over an interface (IMethodFeatureTestService), + * no need to virtual keywords, dynamic proxy can work. + */ + + [RequiresFeature("BooleanTestFeature1")] + public Task Feature1Async() + { + return Task.FromResult(42); + } + + public Task NonFeatureAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs new file mode 100644 index 0000000000..0554120309 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Features +{ + public class TestFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var group = context.AddGroup("Test Group"); + group.AddFeature("BooleanTestFeature1"); + group.AddFeature("BooleanTestFeature2"); + group.AddFeature("IntegerTestFeature1", defaultValue: "1"); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs new file mode 100644 index 0000000000..87fef33a8d --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class TestFeatureStore : IFeatureStore, ISingletonDependency + { + public const string Tenant1IdValue = "f460fcf7-f944-469a-967b-3b2463323dfe"; + public const string Tenant2IdValue = "e10428ad-4608-4c34-a304-6f82502156f2"; + + public static Guid Tenant1Id = new Guid(Tenant1IdValue); + public static Guid Tenant2Id = new Guid(Tenant2IdValue); + + private readonly List _settingRecords; + + public TestFeatureStore() + { + _settingRecords = new List + { + new SettingRecord("BooleanTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"), + new SettingRecord("BooleanTestFeature2", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"), + new SettingRecord("IntegerTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant2Id.ToString(), "34") + }; + } + + public Task GetOrNullAsync(string name, string providerName, string providerKey) + { + return Task.FromResult( + _settingRecords.FirstOrDefault(sr => + sr.Name == name && + sr.ProviderName == providerName && + sr.ProviderKey == providerKey + )?.Value + ); + } + + private class SettingRecord + { + public string Name { get; } + public string ProviderName { get; } + public string ProviderKey { get; } + public string Value { get; } + + public SettingRecord(string name, string providerName, string providerKey, string value) + { + Name = name; + ProviderName = providerName; + ProviderKey = providerKey; + Value = value; + } + } + } +} diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj index 47c93c6bf4..7f83c36c9a 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj +++ b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs index 1aefadf97c..09a50daa5d 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs +++ b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs @@ -1,8 +1,10 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; namespace Volo.Abp.Settings { [DependsOn( + typeof(AbpAutofacModule), typeof(AbpSettingsModule), typeof(AbpTestBaseModule) )] diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs index 6b95b0f755..c3c22f76f9 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs +++ b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs @@ -13,6 +13,11 @@ namespace Volo.Abp.Settings _settingProvider = GetRequiredService(); } + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + [Fact] public async Task Should_Get_Null_If_No_Value_Provided_And_No_Default_Value() { diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs index 6000d121f9..e6e961b8a7 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs @@ -32,8 +32,10 @@ namespace Volo.Blogging.Users } } - user.Update(eventData.Entity); - await UserRepository.UpdateAsync(user); + if (user.Update(eventData.Entity)) + { + await UserRepository.UpdateAsync(user); + } } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs similarity index 78% rename from framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs index 3c1fc47799..bf3265643d 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs @@ -1,8 +1,8 @@ using JetBrains.Annotations; -namespace Volo.Abp.Authorization.Permissions +namespace Volo.Abp.PermissionManagement { - public class PermissionValueProviderGrantInfo + public class PermissionValueProviderGrantInfo //TODO: Rename to PermissionGrantInfo { public static PermissionValueProviderGrantInfo NonGranted { get; } = new PermissionValueProviderGrantInfo(false); diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs index f7d430c214..7b1c7e15f1 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs @@ -20,7 +20,5 @@ namespace Volo.Abp.TenantManagement string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default); - - Task GetCountAsync(CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs index 0a13f77bf0..d2859f29a1 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs @@ -48,11 +48,6 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore .ToListAsync(cancellationToken); } - public virtual async Task GetCountAsync(CancellationToken cancellationToken = default) - { - return await DbSet.LongCountAsync(cancellationToken); - } - public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs index 05aa92757a..2b30dd5a26 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs @@ -28,11 +28,6 @@ namespace Volo.Abp.TenantManagement.MongoDb .FirstOrDefaultAsync(t => t.Name == name, cancellationToken); } - public virtual async Task GetCountAsync(CancellationToken cancellationToken = default) - { - return await GetMongoQueryable().LongCountAsync(cancellationToken); - } - public virtual async Task> GetListAsync( string sorting = null, int maxResultCount = int.MaxValue, diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs index b48299ffd1..06c75317fa 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Modularity; -using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.TenantManagement @@ -48,12 +47,6 @@ namespace Volo.Abp.TenantManagement tenant.ConnectionStrings.Count.ShouldBeGreaterThanOrEqualTo(2); } - [Fact] - public async Task GetCountAsync() - { - (await TenantRepository.GetCountAsync()).ShouldBeGreaterThan(0); - } - [Fact] public async Task GetListAsync() { diff --git a/npm/lerna.json b/npm/lerna.json index 7d85235b1a..ce492f2519 100644 --- a/npm/lerna.json +++ b/npm/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.5.1", + "version": "0.5.2", "packages": [ "packs/*" ], diff --git a/npm/packs/flag-icon-css/abp.resourcemapping.js b/npm/packs/flag-icon-css/abp.resourcemapping.js new file mode 100644 index 0000000000..bd26d124f5 --- /dev/null +++ b/npm/packs/flag-icon-css/abp.resourcemapping.js @@ -0,0 +1,6 @@ +module.exports = { + mappings: { + "@node_modules/flag-icon-css/css/*": "@libs/flag-icon-css/css", + "@node_modules/flag-icon-css/flags/1x1/*": "@libs/flag-icon-css/flags/1x1" + } +} \ No newline at end of file diff --git a/npm/packs/flag-icon-css/package.json b/npm/packs/flag-icon-css/package.json new file mode 100644 index 0000000000..9a151e3b41 --- /dev/null +++ b/npm/packs/flag-icon-css/package.json @@ -0,0 +1,10 @@ +{ + "version": "0.5.2", + "name": "@abp/flag-icon-css", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "flag-icon-css": "^3.3.0" + } +} diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 5a4aeb71e8..b6a589c0ba 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -60,6 +60,7 @@ $projects = ( "framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql", "framework/src/Volo.Abp.EventBus", "framework/src/Volo.Abp.EventBus.RabbitMQ", + "framework/src/Volo.Abp.Features", "framework/src/Volo.Abp.Guids", "framework/src/Volo.Abp.HangFire", "framework/src/Volo.Abp.Http", diff --git a/samples/MicroserviceDemo/docker-compose.migrations.yml b/samples/MicroserviceDemo/docker-compose.migrations.yml index 16d112e2b7..b91aa104c4 100644 --- a/samples/MicroserviceDemo/docker-compose.migrations.yml +++ b/samples/MicroserviceDemo/docker-compose.migrations.yml @@ -8,16 +8,16 @@ services: ports: - "1433" - migrations: - image: 'microservice-demo/migrations:${TAG:-latest}' - build: - context: ../.. - dockerfile: samples/MicroserviceDemo/databases/Dockerfile - depends_on: - - sqlserver + # migrations: + # image: 'volosoft/microservice-demo-migrations:${TAG:-latest}' + # build: + # context: ../.. + # dockerfile: samples/MicroserviceDemo/databases/Dockerfile + # depends_on: + # - sqlserver restore-database: - image: 'microservice-demo/restore-database:${TAG:-latest}' + image: 'volosoft/microservice-demo-restore-database:${TAG:-latest}' build: context: ../.. dockerfile: samples/MicroserviceDemo/databases/restore/Dockerfile diff --git a/samples/MicroserviceDemo/docker-compose.yml b/samples/MicroserviceDemo/docker-compose.yml index 9a3e4e8191..41ee157a43 100644 --- a/samples/MicroserviceDemo/docker-compose.yml +++ b/samples/MicroserviceDemo/docker-compose.yml @@ -31,7 +31,7 @@ services: - elasticsearch internal-gateway: - image: 'microservice-demo/internal-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-internal-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/InternalGateway.Host/Dockerfile @@ -44,7 +44,7 @@ services: - blogging-service backend-admin-app-gateway: - image: 'microservice-demo/backend-admin-app-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-backend-admin-app-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/Dockerfile @@ -56,7 +56,7 @@ services: - product-service public-website-gateway: - image: 'microservice-demo/public-website-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/Dockerfile @@ -67,7 +67,7 @@ services: - product-service blogging-service: - image: 'microservice-demo/blogging-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-blogging-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/BloggingService.Host/Dockerfile @@ -77,7 +77,7 @@ services: - redis identity-service: - image: 'microservice-demo/identity-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-identity-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/IdentityService.Host/Dockerfile @@ -88,7 +88,7 @@ services: - sqlserver product-service: - image: 'microservice-demo/product-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-product-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/ProductService.Host/Dockerfile @@ -98,7 +98,7 @@ services: - redis auth-server: - image: 'microservice-demo/auth-server:${TAG:-latest}' + image: 'volosoft/microservice-demo-auth-server:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/AuthServer.Host/Dockerfile @@ -109,7 +109,7 @@ services: - identity-service backend-admin-app: - image: 'microservice-demo/backend-admin-app:${TAG:-latest}' + image: 'volosoft/microservice-demo-backend-admin-app:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/BackendAdminApp.Host/Dockerfile @@ -118,7 +118,7 @@ services: - backend-admin-app-gateway public-website: - image: 'microservice-demo/public-website:${TAG:-latest}' + image: 'volosoft/microservice-demo-public-website:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/PublicWebSite.Host/Dockerfile