Skip to content

Commit

Permalink
Add extensions to register conditions in the Rules module (#15645)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored Apr 7, 2024
1 parent 2864582 commit c3e5393
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 76 deletions.
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

0 comments on commit c3e5393

Please sign in to comment.