From cc1ee93c8873c726a9c93254da8b3dccd5dc3cf5 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Mon, 10 May 2021 22:17:31 +0800 Subject: [PATCH] add rules sopport --- aspnet-core/LINGYUN.MicroService.Common.sln | 31 ++++ .../Properties/launchSettings.json | 27 +++ ...UN.Abp.OssManagement.HttpApi.Client.csproj | 18 ++ .../AbpOssManagementHttpApiClientModule.cs | 20 +++ .../LINGYUNApiDescriptionFinder.cs | 162 ++++++++++++++++++ .../LINGYUN.Abp.Rules.NRules.csproj | 18 ++ .../Abp/Rules/NRules/AbpNRulesModule.cs | 22 +++ .../Rules/NRules/DefaultActionInterceptor.cs | 14 ++ .../Rules/NRules/DefaultDependencyResolver.cs | 21 +++ .../Abp/Rules/NRules/NRulesContributor.cs | 39 +++++ .../LINGYUN.Abp.Rules.RulesEngine.csproj | 19 ++ .../Rules/RulesEngine/AbpRulesEngineModule.cs | 30 ++++ .../RulesEngine/AbpRulesEngineOptions.cs | 25 +++ .../FileProviderWorkflowRulesContributor.cs | 99 +++++++++++ .../PhysicalFileWorkflowRulesContributor.cs | 42 +++++ .../RulesEngine/IWorkflowRulesContributor.cs | 15 ++ .../NullWorkflowRulesContributor.cs | 25 +++ .../RulesEngine/RulesEngineContributor.cs | 94 ++++++++++ .../LINGYUN.Abp.Rules.RulesEngine/README.md | 44 +++++ .../ListofRuleResultTreeExtension.cs | 76 ++++++++ .../LINGYUN.Abp.Rules.csproj | 14 ++ .../LINGYUN/Abp/Rules/AbpRulesModule.cs | 26 +++ .../LINGYUN/Abp/Rules/AbpRulesOptions.cs | 14 ++ .../LINGYUN/Abp/Rules/IRuleContributor.cs | 14 ++ .../LINGYUN/Abp/Rules/IRuleProvider.cs | 10 ++ .../LINGYUN/Abp/Rules/RuleContributorBase.cs | 31 ++++ .../LINGYUN/Abp/Rules/RuleIdGenerator.cs | 45 +++++ .../LINGYUN/Abp/Rules/RuleProvider.cs | 65 +++++++ .../Abp/Rules/RulesInitializationContext.cs | 16 ++ .../modules/rules/LINGYUN.Abp.Rules/README.md | 54 ++++++ ....OssManagement.HttpApi.Client.Tests.csproj | 27 +++ .../AbpOssManagementHttpApiClientTestBase.cs | 13 ++ ...AbpOssManagementHttpApiClientTestModule.cs | 24 +++ .../OssManagement/OssObjectAppServiceTests.cs | 35 ++++ .../StaticFilesAppServiceTests.cs | 35 ++++ ...LINGYUN.Abp.Rules.RulesEngine.Tests.csproj | 36 ++++ .../RulesEngine/AbpRulesEngineTestBase.cs | 8 + .../RulesEngine/AbpRulesEngineTestModule.cs | 23 +++ .../Abp/Rules/RulesEngine/TestInput.cs | 24 +++ .../Rules/RulesEngine/TestInputRuleTests.cs | 117 +++++++++++++ .../RulesEngine/TestMultipleRuleInputTests.cs | 38 ++++ .../Rules/TestInput.json | 42 +++++ .../Rules/TestMultipleRuleInput.json | 58 +++++++ 43 files changed, 1610 insertions(+) create mode 100644 aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors.IdentityModel.Web/Properties/launchSettings.json create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN.Abp.OssManagement.HttpApi.Client.csproj create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientModule.cs create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/LINGYUNApiDescriptionFinder.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN.Abp.Rules.NRules.csproj create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/AbpNRulesModule.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultActionInterceptor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultDependencyResolver.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/NRulesContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN.Abp.Rules.RulesEngine.csproj create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineModule.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineOptions.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/FileProviderWorkflowRulesContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/PhysicalFileWorkflowRulesContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/IWorkflowRulesContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/NullWorkflowRulesContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/RulesEngineContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/README.md create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/RulesEngine/ListofRuleResultTreeExtension.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN.Abp.Rules.csproj create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesModule.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesOptions.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleContributor.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleProvider.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleContributorBase.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleIdGenerator.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleProvider.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RulesInitializationContext.cs create mode 100644 aspnet-core/modules/rules/LINGYUN.Abp.Rules/README.md create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/OssObjectAppServiceTests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/StaticFilesAppServiceTests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN.Abp.Rules.RulesEngine.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInput.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInputRuleTests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestMultipleRuleInputTests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestInput.json create mode 100644 aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestMultipleRuleInput.json diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index e940daeb2..96c4cf875 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -142,6 +142,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Actors.Asp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Client", "modules\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj", "{879791A3-BD69-42E4-A3BC-9878EFAADDD1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rules", "rules", "{C8891F1A-E6E5-448A-B527-EBFA44D20808}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Rules", "modules\rules\LINGYUN.Abp.Rules\LINGYUN.Abp.Rules.csproj", "{5133D83C-9B23-491C-8780-3F9BBDBD0351}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Rules.NRules", "modules\rules\LINGYUN.Abp.Rules.NRules\LINGYUN.Abp.Rules.NRules.csproj", "{B8E5026B-188F-422F-A1EA-502C4A394585}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Rules.RulesEngine", "modules\rules\LINGYUN.Abp.Rules.RulesEngine\LINGYUN.Abp.Rules.RulesEngine.csproj", "{E861BE01-689D-4637-A5DC-E78E234F83FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Rules.RulesEngine.Tests", "tests\LINGYUN.Abp.Rules.RulesEngine.Tests\LINGYUN.Abp.Rules.RulesEngine.Tests.csproj", "{CF253F0A-3A45-40EE-875F-0E57C8968C48}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -368,6 +378,22 @@ Global {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Debug|Any CPU.Build.0 = Debug|Any CPU {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Release|Any CPU.Build.0 = Release|Any CPU + {5133D83C-9B23-491C-8780-3F9BBDBD0351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5133D83C-9B23-491C-8780-3F9BBDBD0351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5133D83C-9B23-491C-8780-3F9BBDBD0351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5133D83C-9B23-491C-8780-3F9BBDBD0351}.Release|Any CPU.Build.0 = Release|Any CPU + {B8E5026B-188F-422F-A1EA-502C4A394585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8E5026B-188F-422F-A1EA-502C4A394585}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8E5026B-188F-422F-A1EA-502C4A394585}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8E5026B-188F-422F-A1EA-502C4A394585}.Release|Any CPU.Build.0 = Release|Any CPU + {E861BE01-689D-4637-A5DC-E78E234F83FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E861BE01-689D-4637-A5DC-E78E234F83FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E861BE01-689D-4637-A5DC-E78E234F83FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E861BE01-689D-4637-A5DC-E78E234F83FB}.Release|Any CPU.Build.0 = Release|Any CPU + {CF253F0A-3A45-40EE-875F-0E57C8968C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF253F0A-3A45-40EE-875F-0E57C8968C48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF253F0A-3A45-40EE-875F-0E57C8968C48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF253F0A-3A45-40EE-875F-0E57C8968C48}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -439,6 +465,11 @@ Global {E263A9ED-D5DB-4495-A0C7-6268ED92EB92} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} {E74FF671-6E5E-430C-9211-ED910634DDBE} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} {879791A3-BD69-42E4-A3BC-9878EFAADDD1} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} + {C8891F1A-E6E5-448A-B527-EBFA44D20808} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {5133D83C-9B23-491C-8780-3F9BBDBD0351} = {C8891F1A-E6E5-448A-B527-EBFA44D20808} + {B8E5026B-188F-422F-A1EA-502C4A394585} = {C8891F1A-E6E5-448A-B527-EBFA44D20808} + {E861BE01-689D-4637-A5DC-E78E234F83FB} = {C8891F1A-E6E5-448A-B527-EBFA44D20808} + {CF253F0A-3A45-40EE-875F-0E57C8968C48} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors.IdentityModel.Web/Properties/launchSettings.json b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors.IdentityModel.Web/Properties/launchSettings.json new file mode 100644 index 000000000..ea7755ed2 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors.IdentityModel.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:25348/", + "sslPort": 44377 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "LINGYUN.Abp.Dapr.Actors.IdentityModel.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN.Abp.OssManagement.HttpApi.Client.csproj b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN.Abp.OssManagement.HttpApi.Client.csproj new file mode 100644 index 000000000..baddb08b9 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN.Abp.OssManagement.HttpApi.Client.csproj @@ -0,0 +1,18 @@ + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientModule.cs new file mode 100644 index 000000000..a774a27b5 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientModule.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.OssManagement +{ + [DependsOn( + typeof(AbpOssManagementApplicationContractsModule), + typeof(AbpHttpClientModule))] + public class AbpOssManagementHttpApiClientModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClientProxies( + typeof(AbpOssManagementApplicationContractsModule).Assembly, + OssManagementRemoteServiceConsts.RemoteServiceName + ); + } + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/LINGYUNApiDescriptionFinder.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/LINGYUNApiDescriptionFinder.cs new file mode 100644 index 000000000..2fe15b2c0 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.HttpApi.Client/LINGYUN/Abp/OssManagement/LINGYUNApiDescriptionFinder.cs @@ -0,0 +1,162 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.Http.Modeling; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; +using Volo.Abp.Tracing; + +namespace LINGYUN.Abp.OssManagement +{ + public class LINGYUNApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency + { + public ICancellationTokenProvider CancellationTokenProvider { get; set; } + protected IApiDescriptionCache Cache { get; } + protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + + public LINGYUNApiDescriptionFinder( + IApiDescriptionCache cache, + IOptions abpCorrelationIdOptions, + ICorrelationIdProvider correlationIdProvider, + ICurrentTenant currentTenant) + { + Cache = cache; + AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; + CorrelationIdProvider = correlationIdProvider; + CurrentTenant = currentTenant; + CancellationTokenProvider = NullCancellationTokenProvider.Instance; + } + + public async Task FindActionAsync(HttpClient client, string baseUrl, Type serviceType, MethodInfo method) + { + var apiDescription = await GetApiDescriptionAsync(client, baseUrl); + + //TODO: Cache finding? + + var methodParameters = method.GetParameters().ToArray(); + + foreach (var module in apiDescription.Modules.Values) + { + foreach (var controller in module.Controllers.Values) + { + if (!controller.Implements(serviceType)) + { + continue; + } + + foreach (var action in controller.Actions.Values) + { + if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) + { + var found = true; + + for (int i = 0; i < methodParameters.Length; i++) + { + if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i])) + { + found = false; + break; + } + } + + if (found) + { + return action; + } + } + } + } + } + + throw new AbpException($"Could not found remote action for method: {method} on the URL: {baseUrl}"); + } + + public virtual async Task GetApiDescriptionAsync(HttpClient client, string baseUrl) + { + return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl)); + } + + protected virtual async Task GetApiDescriptionFromServerAsync( + HttpClient client, + string baseUrl) + { + var requestMessage = new HttpRequestMessage( + HttpMethod.Get, + baseUrl.EnsureEndsWith('/') + "api/abp/api-definition" + ); + + AddHeaders(requestMessage); + + var response = await client.SendAsync( + requestMessage, + CancellationTokenProvider.Token + ); + + if (!response.IsSuccessStatusCode) + { + throw new AbpException("Remote service returns error! StatusCode = " + response.StatusCode); + } + + var content = await response.Content.ReadAsStringAsync(); + + var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + return (ApplicationApiDescriptionModel)result; + } + + protected virtual void AddHeaders(HttpRequestMessage requestMessage) + { + //CorrelationId + requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); + + //TenantId + if (CurrentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + + protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter) + { + return NormalizeTypeName(actionParameter.TypeAsString) == + NormalizeTypeName(methodParameter.ParameterType.GetFullNameWithAssemblyName()); + } + + protected virtual string NormalizeTypeName(string typeName) + { + const string placeholder = "%COREFX%"; + const string netCoreLib = "System.Private.CoreLib"; + const string netFxLib = "mscorlib"; + + return typeName.Replace(netCoreLib, placeholder).Replace(netFxLib, placeholder); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN.Abp.Rules.NRules.csproj b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN.Abp.Rules.NRules.csproj new file mode 100644 index 000000000..0712d61cf --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN.Abp.Rules.NRules.csproj @@ -0,0 +1,18 @@ + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/AbpNRulesModule.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/AbpNRulesModule.cs new file mode 100644 index 000000000..482c1ae72 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/AbpNRulesModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using NRules.Fluent; +using NRules.RuleModel; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Rules.NRules +{ + [DependsOn( + typeof(AbpRulesModule))] + public class AbpNRulesModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(); + + Configure(options => + { + options.Contributors.Add(); + }); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultActionInterceptor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultActionInterceptor.cs new file mode 100644 index 000000000..de9caea68 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultActionInterceptor.cs @@ -0,0 +1,14 @@ +using NRules.Extensibility; +using NRules.RuleModel; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Rules.NRules +{ + public class DefaultActionInterceptor : IActionInterceptor + { + public void Intercept(IContext context, IEnumerable actions) + { + + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultDependencyResolver.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultDependencyResolver.cs new file mode 100644 index 000000000..0faf0be2f --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/DefaultDependencyResolver.cs @@ -0,0 +1,21 @@ +using NRules.Extensibility; +using System; + +namespace LINGYUN.Abp.Rules.NRules +{ + public class DefaultDependencyResolver : IDependencyResolver + { + private readonly IServiceProvider _serviceProvider; + + public DefaultDependencyResolver( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public object Resolve(IResolutionContext context, Type serviceType) + { + return _serviceProvider.GetService(serviceType); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/NRulesContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/NRulesContributor.cs new file mode 100644 index 000000000..5e509d057 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.NRules/LINGYUN/Abp/Rules/NRules/NRulesContributor.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.DependencyInjection; +using NRules; +using NRules.RuleModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Rules.NRules +{ + public class NRulesContributor : RuleContributorBase, ISingletonDependency + { + private ISessionFactory _sessionFactory; + + public override void Initialize(RulesInitializationContext context) + { + var repository = context.GetRequiredService(); + _sessionFactory = repository.Compile(); + _sessionFactory.DependencyResolver = new DefaultDependencyResolver(context.ServiceProvider); + _sessionFactory.ActionInterceptor = new DefaultActionInterceptor(); + } + + public override Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default) + { + var session = _sessionFactory.CreateSession(); + + session.Insert(input); + if (@params != null && @params.Any()) + { + session.InsertAll(@params); + } + + // TODO: 需要研究源码 + session.Fire(cancellationToken); + + return Task.CompletedTask; + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN.Abp.Rules.RulesEngine.csproj b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN.Abp.Rules.RulesEngine.csproj new file mode 100644 index 000000000..b440bbab5 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN.Abp.Rules.RulesEngine.csproj @@ -0,0 +1,19 @@ + + + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineModule.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineModule.cs new file mode 100644 index 000000000..0487d113d --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineModule.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Json; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + [DependsOn( + typeof(AbpRulesModule), + typeof(AbpJsonModule))] + public class AbpRulesEngineModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMemoryCache(); + + Configure(options => + { + options.Contributors.Add(); + }); + + Configure(options => + { + // 加入防止空引用 + options.Contributors.Add(); + options.Contributors.Add(); + }); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineOptions.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineOptions.cs new file mode 100644 index 000000000..176358fee --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineOptions.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class AbpRulesEngineOptions + { + /// + /// 本地文件路径 + /// + public string PhysicalPath { get; set; } + /// + /// 是否忽略租户 + /// + public bool IgnoreMultiTenancy { get; set; } + /// + /// 规则提供者类型 + /// + public ITypeList Contributors { get; } + + public AbpRulesEngineOptions() + { + Contributors = new TypeList(); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/FileProviderWorkflowRulesContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/FileProviderWorkflowRulesContributor.cs new file mode 100644 index 000000000..b578c31dd --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/FileProviderWorkflowRulesContributor.cs @@ -0,0 +1,99 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; +using RulesEngine.Models; +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders +{ + public abstract class FileProviderWorkflowRulesContributor : IWorkflowRulesContributor + { + protected IMemoryCache RulesCache { get; } + protected IJsonSerializer JsonSerializer { get; } + + protected IFileProvider FileProvider { get; private set; } + + protected FileProviderWorkflowRulesContributor( + IMemoryCache ruleCache, + IJsonSerializer jsonSerializer) + { + RulesCache = ruleCache; + JsonSerializer = jsonSerializer; + } + + public void Initialize() + { + FileProvider = BuildFileProvider(); + } + + protected abstract IFileProvider BuildFileProvider(); + + public async Task LoadAsync(CancellationToken cancellationToken = default) + { + if (FileProvider != null) + { + return await GetCachedRulesAsync(cancellationToken); + } + return new WorkflowRules[0]; + } + + public void Shutdown() + { + if (FileProvider != null && FileProvider is IDisposable resource) + { + resource.Dispose(); + } + } + + private async Task GetCachedRulesAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var ruleId = GetRuleId(); + + return await RulesCache.GetOrCreateAsync(ruleId, + async (entry) => + { + entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); + + return await GetFileSystemRulesAsync(cancellationToken); + }); + } + protected abstract int GetRuleId(); + + protected abstract string GetRuleName(); + + protected virtual async Task GetFileSystemRulesAsync(CancellationToken cancellationToken = default) + { + var ruleId = GetRuleId(); + var ruleFile = GetRuleName(); + var fileInfo = FileProvider.GetFileInfo(ruleFile); + if (fileInfo != null && fileInfo.Exists) + { + // 规则文件监控 + // TODO: 删除模块的规则缓存还需要删除RulesEngine中rulesCache已编译的规则缓存 + ChangeToken.OnChange( + () => FileProvider.Watch(ruleFile), + (int ruleId) => + { + RulesCache.Remove(ruleId); + }, ruleId); + + // 打开文本流 + using (var stream = fileInfo.CreateReadStream()) + { + var result = new byte[stream.Length]; + await stream.ReadAsync(result, 0, (int)stream.Length); + var ruleDsl = Encoding.UTF8.GetString(result); + // 解析 + return JsonSerializer.Deserialize(ruleDsl); + } + } + return new WorkflowRules[0]; + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/PhysicalFileWorkflowRulesContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/PhysicalFileWorkflowRulesContributor.cs new file mode 100644 index 000000000..1060e2631 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/PhysicalFileWorkflowRulesContributor.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using System; +using System.IO; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical +{ + public class PhysicalFileWorkflowRulesContributor : FileProviderWorkflowRulesContributor, ISingletonDependency + { + private readonly RuleIdGenerator _ruleIdGenerator; + private readonly AbpRulesEngineOptions _options; + public PhysicalFileWorkflowRulesContributor( + IMemoryCache ruleCache, + RuleIdGenerator ruleIdGenerator, + IJsonSerializer jsonSerializer, + IOptions options) + : base(ruleCache, jsonSerializer) + { + _ruleIdGenerator = ruleIdGenerator; + + _options = options.Value; + } + + protected override IFileProvider BuildFileProvider() + { + // 未指定路径不启用 + if (!_options.PhysicalPath.IsNullOrWhiteSpace() && + Directory.Exists(_options.PhysicalPath)) + { + return new PhysicalFileProvider(_options.PhysicalPath); + } + return null; + } + + protected override int GetRuleId() => _ruleIdGenerator.CreateRuleId(typeof(T), _options.IgnoreMultiTenancy); + + protected override string GetRuleName() => $"{_ruleIdGenerator.CreateRuleName(typeof(T), _options.IgnoreMultiTenancy)}.json"; + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/IWorkflowRulesContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/IWorkflowRulesContributor.cs new file mode 100644 index 000000000..b13da479d --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/IWorkflowRulesContributor.cs @@ -0,0 +1,15 @@ +using RulesEngine.Models; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public interface IWorkflowRulesContributor + { + void Initialize(); + + Task LoadAsync(CancellationToken cancellationToken = default); + + void Shutdown(); + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/NullWorkflowRulesContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/NullWorkflowRulesContributor.cs new file mode 100644 index 000000000..c80dca944 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/NullWorkflowRulesContributor.cs @@ -0,0 +1,25 @@ +using RulesEngine.Models; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class NullWorkflowRulesContributor : IWorkflowRulesContributor, ISingletonDependency + { + public void Initialize() + { + + } + + public Task LoadAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(new WorkflowRules[0]); + } + + public void Shutdown() + { + + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/RulesEngineContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/RulesEngineContributor.cs new file mode 100644 index 000000000..8ca8c28f4 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/RulesEngineContributor.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using RulesEngine; +using RulesEngine.Interfaces; +using RulesEngine.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Engine = RulesEngine.RulesEngine; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class RulesEngineContributor : RuleContributorBase, ISingletonDependency + { + private IRulesEngine _ruleEngine; + private readonly IEnumerable _workflowRulesContributors; + + public RulesEngineContributor( + IServiceProvider serviceProvider, + IOptions options) + { + _workflowRulesContributors = options.Value + .Contributors + .Select(serviceProvider.GetRequiredService) + .Cast() + .ToArray(); + } + + public override void Initialize(RulesInitializationContext context) + { + var reSetting = new ReSettings + { + NestedRuleExecutionMode = NestedRuleExecutionMode.Performance + }; + + _ruleEngine = new Engine(Logger, reSetting); + + foreach (var contributor in _workflowRulesContributors) + { + contributor.Initialize(); + } + } + + public override async Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default) + { + List workflowRules = new(); + + foreach (var contributor in _workflowRulesContributors) + { + workflowRules.AddRange(await contributor.LoadAsync(cancellationToken)); + } + + if (workflowRules.Any()) + { + await ExecuteRulesAsync(input, workflowRules.ToArray(), @params); + } + } + + public override void Shutdown() + { + foreach (var contributor in _workflowRulesContributors) + { + contributor.Shutdown(); + } + } + + protected virtual async Task ExecuteRulesAsync(T input, WorkflowRules[] workflowRules, object[] @params = null) + { + _ruleEngine.AddWorkflow(workflowRules); + + // 传入参与验证的实体参数 + var inputs = new List() + { + input + }; + if (@params != null && @params.Any()) + { + inputs.AddRange(@params); + } + // 其他参数以此类推 + + foreach (var workflowRule in workflowRules) + { + // 执行当前的规则 + var ruleResult = await _ruleEngine.ExecuteAllRulesAsync(workflowRule.WorkflowName, inputs.ToArray()); + // 用户自定义扩展方法,规则校验错误抛出异常 + ruleResult.ThrowOfFaildExecute(); + } + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/README.md b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/README.md new file mode 100644 index 000000000..603c4787b --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/README.md @@ -0,0 +1,44 @@ +# LINGYUN.Abp.Rules.RulesEngine + +## 模块说明 + +集成微软规则引擎的实现 + +默认实现一个本地文件系统规则提供者,根据用户配置的 **PhysicalPath** 路径检索规则文件 + +文件名如下: + +PhysicalPath/CurrentTenant.Id[如果存在]/验证规则实体类型名称[typeof(Input).Name].json + +### 基础模块 + +### 高阶模块 + +### 权限定义 + +### 功能定义 + +### 配置定义 + +### 如何使用 + + +```csharp + + [DependsOn( + typeof(AbpRulesEngineModule))] + public class YouProjectModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + // 指定真实存在的本地文件路径, 否则将不会检索本地规则文件 + options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); + }); + } + } + +``` + +### 更新日志 diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/RulesEngine/ListofRuleResultTreeExtension.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/RulesEngine/ListofRuleResultTreeExtension.cs new file mode 100644 index 000000000..fc708c330 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/RulesEngine/ListofRuleResultTreeExtension.cs @@ -0,0 +1,76 @@ +using RulesEngine.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using Volo.Abp.Validation; + +namespace RulesEngine +{ + public static class ListofRuleResultTreeExtension + { + public static void ThrowOfFaildExecute(this IEnumerable ruleResultTrees) + { + List validationResults = new List(); + + ruleResultTrees.WrapValidationResult(validationResults); + + if (validationResults.Any()) + { + throw new AbpValidationException("一个或多个规则未通过", validationResults); + } + } + + private static void WrapValidationResult(this IEnumerable ruleResultTrees, List validationResults) + { + var failedResults = ruleResultTrees.Where(rule => !rule.IsSuccess).ToArray(); + + foreach (var failedResult in failedResults) + { + string member = null; + var errorBuilder = new StringBuilder(36); + if (!failedResult.ExceptionMessage.IsNullOrWhiteSpace()) + { + errorBuilder.AppendLine(failedResult.ExceptionMessage); + } + + // TODO: 需要修改源代码, ChildResults的验证错误个人认为有必要展示出来 + if (failedResult.ChildResults != null && failedResult.ChildResults.Any()) + { + errorBuilder.Append(failedResult.ChildResults.GetErrorMessage(out member)); + } + + validationResults.Add(new ValidationResult( + errorBuilder.ToString().TrimEnd(), + new string[] { member ?? failedResult.Rule?.Properties?.GetOrDefault("Property")?.ToString() ?? "input" })); + } + } + + + + private static string GetErrorMessage(this IEnumerable ruleResultTrees, out string member) + { + member = null; + var errorBuilder = new StringBuilder(36); + var failedResults = ruleResultTrees.Where(rule => !rule.IsSuccess).ToArray(); + + for (int index = 0; index < failedResults.Length; index++) + { + member = failedResults[index].Rule?.Properties?.GetOrDefault("Property")?.ToString(); + + if (!failedResults[index].ExceptionMessage.IsNullOrWhiteSpace()) + { + errorBuilder.AppendLine(failedResults[index].ExceptionMessage); + } + + if (failedResults[index].ChildResults != null && failedResults[index].ChildResults.Any()) + { + errorBuilder.Append(failedResults[index].ChildResults.GetErrorMessage(out member)); + } + } + + return errorBuilder.ToString(); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN.Abp.Rules.csproj b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN.Abp.Rules.csproj new file mode 100644 index 000000000..ab990bade --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN.Abp.Rules.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard2.0 + + + + + + + + diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesModule.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesModule.cs new file mode 100644 index 000000000..7910a7ef6 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.Rules +{ + [DependsOn( + typeof(AbpMultiTenancyModule))] + public class AbpRulesModule : AbpModule + { + public override void OnPostApplicationInitialization(ApplicationInitializationContext context) + { + context.ServiceProvider + .GetRequiredService() + .Initialize(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + context.ServiceProvider + .GetRequiredService() + .Shutdown(); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesOptions.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesOptions.cs new file mode 100644 index 000000000..52c801958 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/AbpRulesOptions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.Rules +{ + public class AbpRulesOptions + { + public ITypeList Contributors { get; } + + public AbpRulesOptions() + { + Contributors = new TypeList(); + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleContributor.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleContributor.cs new file mode 100644 index 000000000..62623fabd --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleContributor.cs @@ -0,0 +1,14 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Rules +{ + public interface IRuleContributor + { + void Initialize(RulesInitializationContext context); + + Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default); + + void Shutdown(); + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleProvider.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleProvider.cs new file mode 100644 index 000000000..87889a638 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/IRuleProvider.cs @@ -0,0 +1,10 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Rules +{ + public interface IRuleProvider + { + Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default); + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleContributorBase.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleContributorBase.cs new file mode 100644 index 000000000..5a3639f9b --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleContributorBase.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Rules +{ + public abstract class RuleContributorBase : IRuleContributor + { + public ILogger Logger { get; protected set; } + + protected RuleContributorBase() + { + Logger = NullLogger.Instance; + } + + public virtual void Initialize(RulesInitializationContext context) + { + } + + public virtual Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public virtual void Shutdown() + { + + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleIdGenerator.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleIdGenerator.cs new file mode 100644 index 000000000..e09df6344 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleIdGenerator.cs @@ -0,0 +1,45 @@ +using System; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.Rules +{ + public class RuleIdGenerator : ISingletonDependency + { + private readonly ICurrentTenant _currentTenant; + + public RuleIdGenerator( + ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public virtual int CreateRuleId(Type type, bool ignoreMultiTenancy = false) + { + var typeId = type.GetHashCode(); + + if (!ignoreMultiTenancy) + { + if (_currentTenant.Id.HasValue) + { + return _currentTenant.Id.GetHashCode() & typeId; + } + } + + return typeId; + } + + public virtual string CreateRuleName(Type type, bool ignoreMultiTenancy = false) + { + var ruleName = type.Name; + if (!ignoreMultiTenancy) + { + if (_currentTenant.Id.HasValue) + { + return $"{_currentTenant.Id.Value:D}/{ruleName}"; + } + } + return ruleName; + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleProvider.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleProvider.cs new file mode 100644 index 000000000..d9d1afec6 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RuleProvider.cs @@ -0,0 +1,65 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Rules +{ + public class RuleProvider : IRuleProvider, ISingletonDependency + { + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + private readonly IEnumerable _rulesEngineContributors; + + public RuleProvider( + IServiceProvider serviceProvider, + IOptions options, + ILogger logger) + { + _rulesEngineContributors = options.Value + .Contributors + .Select(serviceProvider.GetRequiredService) + .Cast() + .ToArray(); + + _logger = logger; + _serviceProvider = serviceProvider; + } + + public virtual async Task ExecuteAsync(T input, object[] @params = null, CancellationToken cancellationToken = default) + { + _logger.LogDebug("Starting all typed rules engine."); + + foreach (var contributor in _rulesEngineContributors) + { + await contributor.ExecuteAsync(input, @params, cancellationToken); + } + + _logger.LogDebug("Executed all typed rules engine."); + } + + internal void Initialize() + { + var context = new RulesInitializationContext(_serviceProvider); + + foreach (var contributor in _rulesEngineContributors) + { + contributor.Initialize(context); + } + } + + internal void Shutdown() + { + foreach (var contributor in _rulesEngineContributors) + { + contributor.Shutdown(); + } + } + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RulesInitializationContext.cs b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RulesInitializationContext.cs new file mode 100644 index 000000000..bc2dd08b6 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/LINGYUN/Abp/Rules/RulesInitializationContext.cs @@ -0,0 +1,16 @@ +using System; + +namespace LINGYUN.Abp.Rules +{ + public class RulesInitializationContext : IServiceProvider + { + public IServiceProvider ServiceProvider { get; } + + internal RulesInitializationContext(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public object GetService(Type serviceType) => ServiceProvider.GetService(serviceType); + } +} diff --git a/aspnet-core/modules/rules/LINGYUN.Abp.Rules/README.md b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/README.md new file mode 100644 index 000000000..90c4b1651 --- /dev/null +++ b/aspnet-core/modules/rules/LINGYUN.Abp.Rules/README.md @@ -0,0 +1,54 @@ +# LINGYUN.Abp.Rules + +## 模块说明 + +规则引擎基础模块 + +### 基础模块 + +### 高阶模块 + +### 权限定义 + +### 功能定义 + +### 配置定义 + +### 如何使用 + + +```csharp + + [DependsOn( + typeof(AbpRulesModule))] + public class YouProjectModule : AbpModule + { + } + + public class YouService + { + private readonly IRuleProvider _ruleProvider; + + public YouService(IRuleProvider ruleProvider) + { + _ruleProvider = ruleProvider; + } + + public async Task DoAsync() + { + var input = new YouInput(); + // 规则校验 + await _ruleProvider.ExecuteAsync(input); + + var inputParams = new object[1] + { + new InputParam() + }; + // 带参数规则校验 + await _ruleProvider.ExecuteAsync(input, inputParams); + } + } + +``` + +### 更新日志 diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests.csproj new file mode 100644 index 000000000..12063eeac --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestBase.cs new file mode 100644 index 000000000..9673da59a --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestBase.cs @@ -0,0 +1,13 @@ +using LINGYUN.Abp.Tests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.OssManagement +{ + public class AbpOssManagementHttpApiClientTestBase : AbpTestsBase + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestModule.cs new file mode 100644 index 000000000..dd63d30f9 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementHttpApiClientTestModule.cs @@ -0,0 +1,24 @@ +using LINGYUN.Abp.Tests; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.OssManagement +{ + [DependsOn( + typeof(AbpTestsBaseModule), + typeof(AbpOssManagementHttpApiClientModule))] + public class AbpOssManagementHttpApiClientTestModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration( + ConfigurationHelper.BuildConfiguration( + new AbpConfigurationBuilderOptions + { + BasePath = @"D:\Projects\Development\Abp\OssManagement", + EnvironmentName = "Development" + })); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/OssObjectAppServiceTests.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/OssObjectAppServiceTests.cs new file mode 100644 index 000000000..668d49da6 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/OssObjectAppServiceTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Http.Client.DynamicProxying; +using Xunit; + +namespace LINGYUN.Abp.OssManagement +{ + public class OssObjectAppServiceTests : AbpOssManagementHttpApiClientTestBase + { + private readonly IOssObjectAppService _service; + + public OssObjectAppServiceTests() + { + _service = GetRequiredService(); + } + + [Fact] + public async Task Get_By_Bucket_And_Object_Shouldly_Not_Null() + { + var ossObject = await _service + .GetAsync( + new GetOssObjectInput + { + Bucket = "abp-file-management", + Object = "123.png" + }); + + ossObject.ShouldNotBeNull(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/StaticFilesAppServiceTests.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/StaticFilesAppServiceTests.cs new file mode 100644 index 000000000..668d49da6 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.HttpApi.Client.Tests/LINGYUN/Abp/OssManagement/StaticFilesAppServiceTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Http.Client.DynamicProxying; +using Xunit; + +namespace LINGYUN.Abp.OssManagement +{ + public class OssObjectAppServiceTests : AbpOssManagementHttpApiClientTestBase + { + private readonly IOssObjectAppService _service; + + public OssObjectAppServiceTests() + { + _service = GetRequiredService(); + } + + [Fact] + public async Task Get_By_Bucket_And_Object_Shouldly_Not_Null() + { + var ossObject = await _service + .GetAsync( + new GetOssObjectInput + { + Bucket = "abp-file-management", + Object = "123.png" + }); + + ossObject.ShouldNotBeNull(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN.Abp.Rules.RulesEngine.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN.Abp.Rules.RulesEngine.Tests.csproj new file mode 100644 index 000000000..031c72440 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN.Abp.Rules.RulesEngine.Tests.csproj @@ -0,0 +1,36 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestBase.cs new file mode 100644 index 000000000..21b4563d9 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestBase.cs @@ -0,0 +1,8 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class AbpRulesEngineTestBase : AbpTestsBase + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestModule.cs new file mode 100644 index 000000000..63d64b561 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/AbpRulesEngineTestModule.cs @@ -0,0 +1,23 @@ +using LINGYUN.Abp.Tests; +using System.IO; +using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + [DependsOn( + typeof(AbpRulesEngineModule), + typeof(AbpTestsBaseModule))] + public class AbpRulesEngineTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMemoryCache(); + + Configure(options => + { + options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInput.cs b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInput.cs new file mode 100644 index 000000000..3dea6b076 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInput.cs @@ -0,0 +1,24 @@ +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class TestInput + { + public string Required { get; set; } + + public int Integer1 { get; set; } + + public int Integer2 { get; set; } + + public string Length { get; set; } + } + + public class TestMultipleRuleInput + { + public string Required { get; set; } + + public int Integer1 { get; set; } + + public int Integer2 { get; set; } + + public string Length { get; set; } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInputRuleTests.cs b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInputRuleTests.cs new file mode 100644 index 000000000..46d60ed2c --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestInputRuleTests.cs @@ -0,0 +1,117 @@ +using Shouldly; +using System.Threading.Tasks; +using Volo.Abp.Validation; +using Xunit; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class TestInputRuleTests : AbpRulesEngineTestBase + { + private readonly IRuleProvider _ruleProvider; + + public TestInputRuleTests() + { + _ruleProvider = GetRequiredService(); + } + + [Fact] + public async Task Input_Required_Should_Required() + { + var input = new TestInput + { + Integer1 = 101, + Integer2 = 99, + Length = "123456" + } + ; + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(1); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Required 必须输入!"); + } + + [Fact] + public async Task Input_Integer1_Should_MustBeGreaterThan100() + { + var input = new TestInput + { + Required = "123456", + Integer1 = 99, + Integer2 = 99, + Length = "123456" + }; + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(1); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer1 必须大于100!"); + } + + [Fact] + public async Task Input_Integer2_Should_MustBeLessThan100() + { + var input = new TestInput + { + Required = "123456", + Integer1 = 101, + Integer2 = 100, + Length = "123456" + }; + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(1); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer2 必须小于100!"); + } + + [Fact] + public async Task Input_Sum_Integer1_And_Integer2_Should_MustBeGreaterThan150() + { + var input = new TestInput + { + Required = "1", + Integer1 = 101, + Integer2 = 48, + Length = "123456" + }; + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(1); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer1 与 Integer2 之和 必须大于150!"); + } + + [Fact] + public async Task Input_Required_Length_Should_MustBeGreaterThan5() + { + var input = new TestInput + { + Required = "1", + Integer1 = 101, + Integer2 = 50, + Length = "12345" + }; + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(1); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Length 长度必须大于5!"); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestMultipleRuleInputTests.cs b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestMultipleRuleInputTests.cs new file mode 100644 index 000000000..4bf2f32a9 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/LINGYUN/Abp/Rules/RulesEngine/TestMultipleRuleInputTests.cs @@ -0,0 +1,38 @@ +using Shouldly; +using System.Threading.Tasks; +using Volo.Abp.Validation; +using Xunit; + +namespace LINGYUN.Abp.Rules.RulesEngine +{ + public class TestMultipleRuleInputTests : AbpRulesEngineTestBase + { + private readonly IRuleProvider _ruleProvider; + + public TestMultipleRuleInputTests() + { + _ruleProvider = GetRequiredService(); + } + + [Fact] + public async Task Multiple_Rule_Input_Should_Failed() + { + var input = new TestMultipleRuleInput + { + Length = "12345", + Integer1 = 100, + Integer2 = 100 + }; + + var exception = await Assert.ThrowsAsync(async () => + { + await _ruleProvider.ExecuteAsync(input); + }); + + exception.Message.ShouldBe("一个或多个规则未通过"); + exception.ValidationErrors.Count.ShouldBe(2); + exception.ValidationErrors[0].ErrorMessage.ShouldBe("输入字段验证无效!"); + exception.ValidationErrors[1].ErrorMessage.ShouldBe("长度与求和验证无效!"); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestInput.json b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestInput.json new file mode 100644 index 000000000..bd8a513a9 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestInput.json @@ -0,0 +1,42 @@ +[ + { + "WorkflowName": "TestInputValidation", + "Rules": [ + { + "RuleName": "InputRequired", + "ErrorMessage": "字段 Required 必须输入!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "!string.IsNullOrWhiteSpace(input1.Required)" + }, + { + "RuleName": "MustBeGreaterThan100", + "ErrorMessage": "字段 Integer1 必须大于100!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer1 > 100" + }, + { + "RuleName": "MustBeLessThan100", + "ErrorMessage": "字段 Integer2 必须小于100!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer2 < 100" + }, + { + "RuleName": "SumMustBeGreaterThan150", + "ErrorMessage": "字段 Integer1 与 Integer2 之和 必须大于150!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer1 + input1.Integer2 > 150" + }, + { + "RuleName": "RequiredLengthMustBeGreaterThan5", + "ErrorMessage": "字段 Length 长度必须大于5!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Length.Length > 5" + } + ] + } +] \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestMultipleRuleInput.json b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestMultipleRuleInput.json new file mode 100644 index 000000000..f4d391383 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Rules.RulesEngine.Tests/Rules/TestMultipleRuleInput.json @@ -0,0 +1,58 @@ +[ + { + "WorkflowName": "TestMultipleRuleInputValidation", + "Rules": [ + { + "RuleName": "ValidationInputField", + "ErrorMessage": "输入字段验证无效!", + "ErrorType": "Error", + "Operator": "AndAlso", + "Rules": [ + { + "RuleName": "InputRequired", + "ErrorMessage": "字段 Required 必须输入!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "!string.IsNullOrWhiteSpace(input1.Required)" + }, + { + "RuleName": "MustBeGreaterThan100", + "ErrorMessage": "字段 Integer1 必须大于100!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer1 > 100" + }, + { + "RuleName": "MustBeLessThan100", + "ErrorMessage": "字段 Integer2 必须小于100!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer2 < 100" + } + ] + }, + { + "RuleName": "ValidationLengthAndSum", + "ErrorMessage": "长度与求和验证无效!", + "ErrorType": "Error", + "Operator": "AndAlso", + "Rules": [ + { + "RuleName": "SumMustBeGreaterThan150", + "ErrorMessage": "字段 Integer1 与 Integer2 之和 必须大于200!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Integer1 + input1.Integer2 > 200" + }, + { + "RuleName": "RequiredLengthMustBeGreaterThan5", + "ErrorMessage": "字段 Length 长度必须大于5!", + "ErrorType": "Error", + "RuleExpressionType": "LambdaExpression", + "Expression": "input1.Length.Length > 5" + } + ] + } + ] + } +] \ No newline at end of file