Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extensions to register conditions in the Rules module #15645

Merged
merged 12 commits into from
Apr 7, 2024
7 changes: 7 additions & 0 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Email.Azure", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Email.Smtp", "src\OrchardCore.Modules\OrchardCore.Email.Smtp\OrchardCore.Email.Smtp.csproj", "{E8A1097D-A65A-4B17-A3A2-F50D79552732}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Rules.Core", "src\OrchardCore\OrchardCore.Rules.Core\OrchardCore.Rules.Core.csproj", "{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1371,6 +1373,10 @@ Global
{E8A1097D-A65A-4B17-A3A2-F50D79552732}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8A1097D-A65A-4B17-A3A2-F50D79552732}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8A1097D-A65A-4B17-A3A2-F50D79552732}.Release|Any CPU.Build.0 = Release|Any CPU
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1608,6 +1614,7 @@ Global
{47777735-7432-4CCA-A8C5-672E9EE65121} = {90030E85-0C4F-456F-B879-443E8A3F220D}
{C35AB37B-5A09-4896-BEEE-B126B7E7018A} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
{E8A1097D-A65A-4B17-A3A2-F50D79552732} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7} = {F23AC6C2-DE44-4699-999D-3C478EF3D691}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Module.Targets\OrchardCore.Module.Targets.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ResourceManagement\OrchardCore.ResourceManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Rules.Abstractions\OrchardCore.Rules.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Rules.Core\OrchardCore.Rules.Core.csproj" />
</ItemGroup>

</Project>
77 changes: 21 additions & 56 deletions src/OrchardCore.Modules/OrchardCore.Rules/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.ContentManagement.Display.ContentDisplay;
Expand Down Expand Up @@ -27,83 +26,49 @@ public override void ConfigureServices(IServiceCollection services)
.AddScoped<IRuleMigrator, RuleMigrator>();

// All condition.
services
.AddScoped<IDisplayDriver<Condition>, AllConditionDisplayDriver>()
.AddCondition<AllConditionGroup, AllConditionEvaluator, ConditionFactory<AllConditionGroup>>();
services.AddRule<AllConditionGroup, AllConditionEvaluator, AllConditionDisplayDriver>();

// Any condition.
services
.AddScoped<IDisplayDriver<Condition>, AnyConditionDisplayDriver>()
.AddCondition<AnyConditionGroup, AnyConditionEvaluator, ConditionFactory<AnyConditionGroup>>();
services.AddRule<AnyConditionGroup, AnyConditionEvaluator, AnyConditionDisplayDriver>();

// Boolean condition.
services
.AddScoped<IDisplayDriver<Condition>, BooleanConditionDisplayDriver>()
.AddCondition<BooleanCondition, BooleanConditionEvaluator, ConditionFactory<BooleanCondition>>();
services.AddRule<BooleanCondition, BooleanConditionEvaluator, BooleanConditionDisplayDriver>();

// Homepage condition.
services
.AddScoped<IDisplayDriver<Condition>, HomepageConditionDisplayDriver>()
.AddCondition<HomepageCondition, HomepageConditionEvaluator, ConditionFactory<HomepageCondition>>();
services.AddRule<HomepageCondition, HomepageConditionEvaluator, HomepageConditionDisplayDriver>();

// Url condition.
services
.AddScoped<IDisplayDriver<Condition>, UrlConditionDisplayDriver>()
.AddCondition<UrlCondition, UrlConditionEvaluator, ConditionFactory<UrlCondition>>();
services.AddRule<UrlCondition, UrlConditionEvaluator, UrlConditionDisplayDriver>();

// Culture condition.
services
.AddScoped<IDisplayDriver<Condition>, CultureConditionDisplayDriver>()
.AddCondition<CultureCondition, CultureConditionEvaluator, ConditionFactory<CultureCondition>>();
services.AddRule<CultureCondition, CultureConditionEvaluator, CultureConditionDisplayDriver>();

// Role condition.
services
.AddScoped<IDisplayDriver<Condition>, RoleConditionDisplayDriver>()
.AddCondition<RoleCondition, RoleConditionEvaluator, ConditionFactory<RoleCondition>>();
services.AddRule<RoleCondition, RoleConditionEvaluator, RoleConditionDisplayDriver>();

// Javascript condition.
services
.AddScoped<IDisplayDriver<Condition>, JavascriptConditionDisplayDriver>()
.AddCondition<JavascriptCondition, JavascriptConditionEvaluator, ConditionFactory<JavascriptCondition>>();
// JavaScript condition.
services.AddRule<JavascriptCondition, JavascriptConditionEvaluator, JavascriptConditionDisplayDriver>();

// Is authenticated condition.
services
.AddScoped<IDisplayDriver<Condition>, IsAuthenticatedConditionDisplayDriver>()
.AddCondition<IsAuthenticatedCondition, IsAuthenticatedConditionEvaluator, ConditionFactory<IsAuthenticatedCondition>>();
services.AddRule<IsAuthenticatedCondition, IsAuthenticatedConditionEvaluator, IsAuthenticatedConditionDisplayDriver>();

// Is anonymous condition.
services
.AddScoped<IDisplayDriver<Condition>, IsAnonymousConditionDisplayDriver>()
.AddCondition<IsAnonymousCondition, IsAnonymousConditionEvaluator, ConditionFactory<IsAnonymousCondition>>();
services.AddRule<IsAnonymousCondition, IsAnonymousConditionEvaluator, IsAnonymousConditionDisplayDriver>();

// Content type condition.
services
.AddScoped<IDisplayDriver<Condition>, ContentTypeConditionDisplayDriver>()
.AddCondition<ContentTypeCondition, ContentTypeConditionEvaluatorDriver, ConditionFactory<ContentTypeCondition>>()
services.AddScoped<IDisplayDriver<Condition>, ContentTypeConditionDisplayDriver>()
.AddRuleCondition<ContentTypeCondition, ContentTypeConditionEvaluatorDriver>()
.AddScoped<IContentDisplayDriver>(sp => sp.GetRequiredService<ContentTypeConditionEvaluatorDriver>());

// Allows to serialize 'Condition' derived types for List<Condition> Layer.LayerRule
services.AddJsonDerivedTypeInfo<AllConditionGroup, Condition>();
services.AddJsonDerivedTypeInfo<AnyConditionGroup, Condition>();
services.AddJsonDerivedTypeInfo<BooleanCondition, Condition>();
services.AddJsonDerivedTypeInfo<ContentTypeCondition, Condition>();
services.AddJsonDerivedTypeInfo<CultureCondition, Condition>();
services.AddJsonDerivedTypeInfo<HomepageCondition, Condition>();
services.AddJsonDerivedTypeInfo<IsAnonymousCondition, Condition>();
services.AddJsonDerivedTypeInfo<IsAuthenticatedCondition, Condition>();
services.AddJsonDerivedTypeInfo<JavascriptCondition, Condition>();
services.AddJsonDerivedTypeInfo<RoleCondition, Condition>();
services.AddJsonDerivedTypeInfo<UrlCondition, Condition>();

// Allows to serialize 'ConditionOperator' derived types
services.AddJsonDerivedTypeInfo<StringEqualsOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringNotEqualsOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringStartsWithOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringNotStartsWithOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringEndsWithOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringNotEndsWithOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringContainsOperator, ConditionOperator>();
services.AddJsonDerivedTypeInfo<StringNotContainsOperator, ConditionOperator>();
services.AddRuleConditionOperator<StringEqualsOperator>()
.AddRuleConditionOperator<StringNotEqualsOperator>()
.AddRuleConditionOperator<StringStartsWithOperator>()
.AddRuleConditionOperator<StringNotStartsWithOperator>()
.AddRuleConditionOperator<StringEndsWithOperator>()
.AddRuleConditionOperator<StringNotEndsWithOperator>()
.AddRuleConditionOperator<StringContainsOperator>()
.AddRuleConditionOperator<StringNotContainsOperator>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ public static class ServiceCollectionExtensions
/// Registers a JSON type resolver allowing to serialize a given type from its base type.
/// </summary>
public static IServiceCollection AddJsonDerivedTypeInfo<TDerived, TBase>(this IServiceCollection services)
where TDerived : class where TBase : class =>

services.Configure<JsonDerivedTypesOptions>(options =>
where TDerived : class
where TBase : class
=> services.Configure<JsonDerivedTypesOptions>(options =>
{

if (!options.DerivedTypes.TryGetValue(typeof(TBase), out var derivedTypes))
{
derivedTypes = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static IServiceCollection AddCondition(this IServiceCollection services,
return services;
}

[Obsolete("This method is deprecated and will be removed in future releases. Please use the .AddRule<> or .AddRuleCondition<> extensions found in OrchardCore.Rule.Core instead.")]
public static IServiceCollection AddCondition<TCondition, TConditionEvaluator, TConditionFactory>(this IServiceCollection services)
where TCondition : Condition
where TConditionEvaluator : IConditionEvaluator
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>OrchardCore.Rules</RootNamespace>
<Title>OrchardCore Rules Core</Title>
<Description>
$(OCCMSDescription)

Core implementation for Rules module.
</Description>
<PackageTags>$(PackageTags) OrchardCoreCMS</PackageTags>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OrchardCore.Abstractions\OrchardCore.Abstractions.csproj" />
<ProjectReference Include="..\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
<ProjectReference Include="..\OrchardCore.Rules.Abstractions\OrchardCore.Rules.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.DisplayManagement.Handlers;

namespace OrchardCore.Rules;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRule<TCondition, TEvaluator, TDisplayDriver>(this IServiceCollection services)
where TCondition : Condition, new()
where TEvaluator : IConditionEvaluator
where TDisplayDriver : class, IDisplayDriver<Condition>
=> services.AddRuleCondition<TCondition, TEvaluator>()
.AddScoped<IDisplayDriver<Condition>, TDisplayDriver>();

public static IServiceCollection AddRuleConditionOperator<TOperator>(this IServiceCollection services)
where TOperator : ConditionOperator
=> services.AddJsonDerivedTypeInfo<TOperator, ConditionOperator>();

public static IServiceCollection AddRuleCondition<TCondition, TConditionEvaluator>(this IServiceCollection services)
where TCondition : Condition, new()
=> services.AddCondition(typeof(TCondition), typeof(TConditionEvaluator), typeof(ConditionFactory<TCondition>))
.AddJsonDerivedTypeInfo<TCondition, Condition>();

public static IServiceCollection AddRuleCondition<TCondition, TConditionEvaluator, TConditionFactory>(this IServiceCollection services)
where TCondition : Condition
where TConditionEvaluator : IConditionEvaluator
where TConditionFactory : IConditionFactory
=> services.AddCondition(typeof(TCondition), typeof(TConditionEvaluator), typeof(TConditionFactory))
.AddJsonDerivedTypeInfo<TCondition, Condition>();
}
11 changes: 4 additions & 7 deletions src/docs/reference/modules/Rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ Enabling the `OrchardCore.Rules` module allows you to implement condition based

### Custom Conditions

You may create your own conditions for more complex scenarios.

You will need to implement the abstractions found in the `OrchardCore.Rules.Abstractions` package.
For more intricate scenarios, you have the option to craft your own conditions. To achieve this, you'll be required to implement the following abstractions from the `OrchardCore.Rules.Abstractions` package:

- `Condition`
- `ConditionEvaluator`
- `ConditionDisplayDriver`
- Appropriate views for your condition display driver.

Afterward, proceed with registering the services as follows:

``` csharp
services
.AddScoped<IDisplayDriver<Condition>, BooleanConditionDisplayDriver>()
.AddCondition<BooleanCondition, BooleanConditionEvaluator, ConditionFactory<BooleanCondition>>();
services.AddRule<BooleanCondition, BooleanConditionEvaluator, BooleanConditionDisplayDriver>();
```

Refer [Layers](../Layers/README.md) for more information about rules and conditions.
Expand Down
12 changes: 11 additions & 1 deletion src/docs/releases/1.9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,17 @@ services.AddAdminNode<PlaceholderAdminNode, PlaceholderAdminNodeNavigationBuilde
services.AddJsonDerivedTypeInfo<UrlCondition, Condition>();
```

In particular, any type introduced in custom modules inheriting from `MenuItem`, `AdminNode`, `Condition`, `ConditionOperator`, `Query`, `SitemapType` will have to use this method.
Alternatively, you can simplify your code by using the newly added extensions to register custom conditions. For example,

```csharp
services.AddRule<HomepageCondition, HomepageConditionEvaluator, HomepageConditionDisplayDriver>();
```

- Any type introduced in custom modules inheriting from `MenuItem`, `AdminNode`, `Condition`, `ConditionOperator`, `Query`, `SitemapType` will have to register the class using the `services.AddJsonDerivedTypeInfo<>` method. For example,

```csharp
services.AddJsonDerivedTypeInfo<SqlQuery, Query>();
```

- The extension `PopulateSettings<T>(model)` was removed from `PartFieldDefinition`. If you are using this method in your code, you'll have to get the settings using the `Settings` object directly. For instance, if you have this code,

Expand Down
2 changes: 1 addition & 1 deletion test/OrchardCore.Benchmarks/RuleBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class RuleBenchmark
static RuleBenchmark()
{
var services = RuleTests.CreateRuleServiceCollection()
.AddCondition<HomepageCondition, HomepageConditionEvaluator, ConditionFactory<HomepageCondition>>()
.AddRuleCondition<HomepageCondition, HomepageConditionEvaluator>()
.AddSingleton<IGlobalMethodProvider, DefaultLayersMethodProvider>()
.AddMemoryCache()
.AddScripting()
Expand Down
12 changes: 6 additions & 6 deletions test/OrchardCore.Tests/Modules/OrchardCore.Rules/RuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task ShouldEvaluateHomepage(string path, bool isHomepage, bool expe
};

var services = CreateRuleServiceCollection()
.AddCondition<HomepageCondition, HomepageConditionEvaluator, ConditionFactory<HomepageCondition>>();
.AddRuleCondition<HomepageCondition, HomepageConditionEvaluator>();

var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();
Expand Down Expand Up @@ -73,7 +73,7 @@ public async Task ShouldEvaluateBoolean(bool boolean, bool expected)
};

var services = CreateRuleServiceCollection()
.AddCondition<BooleanCondition, BooleanConditionEvaluator, ConditionFactory<BooleanCondition>>();
.AddRuleCondition<BooleanCondition, BooleanConditionEvaluator>();

var serviceProvider = services.BuildServiceProvider();

Expand Down Expand Up @@ -104,8 +104,8 @@ public async Task ShouldEvaluateAny(bool first, bool second, bool expected)
};

var services = CreateRuleServiceCollection()
.AddCondition<AnyConditionGroup, AnyConditionEvaluator, ConditionFactory<AnyConditionGroup>>()
.AddCondition<BooleanCondition, BooleanConditionEvaluator, ConditionFactory<BooleanCondition>>();
.AddRuleCondition<AnyConditionGroup, AnyConditionEvaluator>()
.AddRuleCondition<BooleanCondition, BooleanConditionEvaluator>();

var serviceProvider = services.BuildServiceProvider();

Expand All @@ -132,7 +132,7 @@ public async Task ShouldEvaluateUrlEquals(string path, string requestPath, bool
};

var services = CreateRuleServiceCollection()
.AddCondition<UrlCondition, UrlConditionEvaluator, ConditionFactory<UrlCondition>>();
.AddRuleCondition<UrlCondition, UrlConditionEvaluator>();

var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();
Expand Down Expand Up @@ -165,7 +165,7 @@ public async Task ShouldEvaluateJavascriptCondition(string script, string reques
};

var services = CreateRuleServiceCollection()
.AddCondition<JavascriptCondition, JavascriptConditionEvaluator, ConditionFactory<JavascriptCondition>>()
.AddRuleCondition<JavascriptCondition, JavascriptConditionEvaluator>()
.AddSingleton<IGlobalMethodProvider, DefaultLayersMethodProvider>()
.AddMemoryCache()
.AddScripting()
Expand Down
Loading