diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs index 6367bd24ff..eb8c4eb804 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs @@ -62,12 +62,6 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling return CreateEntityNotFoundError(exception as EntityNotFoundException); } - if (exception is AbpAuthorizationException) - { - var authorizationException = exception as AbpAuthorizationException; - return new RemoteServiceErrorInfo(authorizationException.Message); - } - var errorInfo = new RemoteServiceErrorInfo(); if (exception is IUserFriendlyException) diff --git a/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj index a2d098f970..3bea3ea77f 100644 --- a/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj +++ b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj @@ -15,7 +15,12 @@ - + + + + + + diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureErrorCodes.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureErrorCodes.cs new file mode 100644 index 0000000000..a5a4cf3a58 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureErrorCodes.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Features +{ + public static class AbpFeatureErrorCodes + { + public const string FeatureIsNotEnabled = "Volo.Feature:010001"; + + public const string AllOfTheseFeaturesMustBeEnabled = "Volo.Feature:010002"; + + public const string AtLeastOneOfTheseFeaturesMustBeEnabled = "Volo.Feature:010003"; + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs index f30017feed..70c6e7f37d 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -1,15 +1,18 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using Volo.Abp.Features.Localization; using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Validation; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Features { [DependsOn( - typeof(AbpLocalizationAbstractionsModule), + typeof(AbpLocalizationModule), typeof(AbpMultiTenancyModule), typeof(AbpValidationModule) )] @@ -29,6 +32,23 @@ namespace Volo.Abp.Features options.ValueProviders.Add(); options.ValueProviders.Add(); }); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Volo/Abp/Features/Localization"); + }); + + Configure(options => + { + options.MapCodeNamespace("Volo.Feature", typeof(AbpFeatureResource)); + }); } private static void AutoAddDefinitionProviders(IServiceCollection services) diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs index 8850408df0..ba2b640031 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs @@ -9,8 +9,8 @@ namespace Volo.Abp.Features public static class FeatureCheckerExtensions { public static async Task GetAsync( - [NotNull] this IFeatureChecker featureChecker, - [NotNull] string name, + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name, T defaultValue = default) where T : struct { @@ -56,10 +56,11 @@ namespace Volo.Abp.Features { if (!(await featureChecker.IsEnabledAsync(featureName))) { - throw new AbpAuthorizationException("Feature is not enabled: " + featureName); + throw new AbpAuthorizationException(code: AbpFeatureErrorCodes.FeatureIsNotEnabled).WithData( + "FeatureName", featureName); } } - + public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) { if (featureNames.IsNullOrEmpty()) @@ -73,10 +74,8 @@ namespace Volo.Abp.Features { if (!(await featureChecker.IsEnabledAsync(featureName))) { - throw new AbpAuthorizationException( - "Required features are not enabled. All of these features must be enabled: " + - string.Join(", ", featureNames) - ); + throw new AbpAuthorizationException(code: AbpFeatureErrorCodes.AllOfTheseFeaturesMustBeEnabled) + .WithData("FeatureNames", string.Join(", ", featureNames)); } } } @@ -90,11 +89,9 @@ namespace Volo.Abp.Features } } - throw new AbpAuthorizationException( - "Required features are not enabled. At least one of these features must be enabled: " + - string.Join(", ", featureNames) - ); + throw new AbpAuthorizationException(code: AbpFeatureErrorCodes.AtLeastOneOfTheseFeaturesMustBeEnabled) + .WithData("FeatureNames", string.Join(", ", featureNames)); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/AbpFeatureResource.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/AbpFeatureResource.cs new file mode 100644 index 0000000000..5871d550ad --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/AbpFeatureResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Features.Localization +{ + [LocalizationResourceName("AbpFeature")] + public class AbpFeatureResource + { + + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/en.json b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/en.json new file mode 100644 index 0000000000..de03dc11d0 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/en.json @@ -0,0 +1,8 @@ +{ + "culture": "en", + "texts": { + "Volo.Feature:010001": "Feature is not enabled: {FeatureName}", + "Volo.Feature:010002": "Required features are not enabled. All of these features must be enabled: {FeatureNames}", + "Volo.Feature:010003": "Required features are not enabled. At least one of these features must be enabled: {FeatureNames}" + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/tr.json b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/tr.json new file mode 100644 index 0000000000..9277b63530 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/tr.json @@ -0,0 +1,8 @@ +{ + "culture": "tr", + "texts": { + "Volo.Feature:010001": "Özellik etkinleştirilmedi: {FeatureName}", + "Volo.Feature:010002": "Gerekli özellikler etkinleştirilmedi. Bu özelliklerin tümü etkinleştirilmelidir: {FeatureNames}", + "Volo.Feature:010003": "Gerekli özellikler etkinleştirilmedi. Bu özelliklerden en az birinin etkinleştirilmesi gerekir: {FeatureNames}" + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/zh-Hans.json b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/zh-Hans.json new file mode 100644 index 0000000000..4c6d99c281 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "culture": "zh-Hans", + "texts": { + "Volo.Feature:010001": "功能未启用: {FeatureName}", + "Volo.Feature:010002": "必要的功能未启用. 这些功能需要启用: {FeatureNames}", + "Volo.Feature:010003": "必要的功能未启用. 需要启用这些功能中的一项:{FeatureNames}" + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs index ad5b0b65a3..c0a8c4d4f8 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.Serialization; using Microsoft.Extensions.Logging; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Logging; namespace Volo.Abp.Authorization @@ -9,7 +10,7 @@ namespace Volo.Abp.Authorization /// This exception is thrown on an unauthorized request. /// [Serializable] - public class AbpAuthorizationException : AbpException, IHasLogLevel + public class AbpAuthorizationException : AbpException, IHasLogLevel, IHasErrorCode { /// /// Severity of the exception. @@ -17,6 +18,11 @@ namespace Volo.Abp.Authorization /// public LogLevel LogLevel { get; set; } + /// + /// Error code. + /// + public string Code { get; } + /// /// Creates a new object. /// @@ -54,5 +60,24 @@ namespace Volo.Abp.Authorization { LogLevel = LogLevel.Warning; } + + /// + /// Creates a new object. + /// + /// Exception message + /// Exception code + /// Inner exception + public AbpAuthorizationException(string message = null, string code = null, Exception innerException = null) + : base(message, innerException) + { + Code = code; + LogLevel = LogLevel.Warning; + } + + public AbpAuthorizationException WithData(string name, object value) + { + Data[name] = value; + return this; + } } -} \ 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 index 3edc355ca2..713b541431 100644 --- a/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj +++ b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj @@ -9,6 +9,7 @@ + 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 index 7b0927b80d..f5ff20e6f2 100644 --- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs @@ -1,10 +1,12 @@ using Volo.Abp.Autofac; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Modularity; namespace Volo.Abp.Features { [DependsOn( typeof(AbpFeaturesModule), + typeof(AbpExceptionHandlingModule), typeof(AbpTestBaseModule), typeof(AbpAutofacModule) )] @@ -12,7 +14,7 @@ namespace Volo.Abp.Features { public override void ConfigureServices(ServiceConfigurationContext context) { - + } } } diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureCheckerExtensions_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureCheckerExtensions_Tests.cs new file mode 100644 index 0000000000..a3ea2cc710 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureCheckerExtensions_Tests.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.Authorization; +using Volo.Abp.Localization; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureCheckerExtensions_Tests : FeatureTestBase + { + private readonly IFeatureChecker _featureChecker; + private readonly IExceptionToErrorInfoConverter _exceptionToErrorInfoConverter; + + public FeatureCheckerExtensions_Tests() + { + _featureChecker = GetRequiredService(); + _exceptionToErrorInfoConverter = GetRequiredService(); + } + + [Fact] + public async Task CheckEnabledAsync() + { + using (CultureHelper.Use("zh-Hans")) + { + var ex = await Assert.ThrowsAsync(async () => + await _featureChecker.CheckEnabledAsync("BooleanTestFeature1")); + + var errorInfo = _exceptionToErrorInfoConverter.Convert(ex, false); + errorInfo.Message.ShouldBe("功能未启用: BooleanTestFeature1"); + } + } + + [Fact] + public async Task CheckEnabled_RequiresAll() + { + using (CultureHelper.Use("zh-Hans")) + { + var ex = await Assert.ThrowsAsync(async () => + await _featureChecker.CheckEnabledAsync(true, "BooleanTestFeature1", "BooleanTestFeature2")); + + var errorInfo = _exceptionToErrorInfoConverter.Convert(ex, false); + errorInfo.Message.ShouldBe("必要的功能未启用. 这些功能需要启用: BooleanTestFeature1, BooleanTestFeature2"); + } + } + + [Fact] + public async Task CheckEnabled_Not_RequiresAll() + { + using (CultureHelper.Use("zh-Hans")) + { + var ex = await Assert.ThrowsAsync(async () => + await _featureChecker.CheckEnabledAsync(false, "BooleanTestFeature1", "BooleanTestFeature2")); + + var errorInfo = _exceptionToErrorInfoConverter.Convert(ex, false); + errorInfo.Message.ShouldBe("必要的功能未启用. 需要启用这些功能中的一项:BooleanTestFeature1, BooleanTestFeature2"); + } + } + } +}