- RuleEngine implementation for widget layer rules.

- The ResultFilter uses the rule manager to filter out layers and widgets.
- Added rules to default layers.
- RuleManager with callback injection into the dlr scripting sandbox.
- RuleProvider implementations for Url and Authenticated. Anonymous is just "not Authenticated".
- RuleContext.
- Renaming Layer.Rule to Layer.LayerRule. NHibernate bug for SqlCe most likely, couldn't escape "Rule" as expected.
- Unit tests.

--HG--
branch : dev
This commit is contained in:
Suha Can
2010-10-04 12:27:59 -07:00
parent 6e8880705f
commit ce208c44f2
13 changed files with 231 additions and 11 deletions

View File

@@ -63,6 +63,21 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\fluentnhibernate\FluentNHibernate.dll</HintPath>
</Reference>
<Reference Include="IronRuby, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\dlr\IronRuby.dll</HintPath>
</Reference>
<Reference Include="IronRuby.Libraries">
<HintPath>..\..\lib\dlr\IronRuby.Libraries.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Dynamic, Version=1.1.0.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\dlr\Microsoft.Dynamic.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Scripting, Version=1.1.0.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\dlr\Microsoft.Scripting.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.0.812.4, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\moq\Moq.dll</HintPath>
@@ -115,6 +130,7 @@
<Compile Include="Values.cs" />
<Compile Include="Users\Controllers\AdminControllerTests.cs" />
<Compile Include="Users\Services\MembershipServiceTests.cs" />
<Compile Include="Widgets\WidgetsTests.cs" />
<Compile Include="XmlRpc\Controllers\HomeControllerTests.cs" />
<Compile Include="XmlRpc\Services\XmlRpcReaderTests.cs" />
<Compile Include="XmlRpc\Services\XmlRpcWriterTests.cs" />
@@ -144,6 +160,10 @@
<Project>{79AED36E-ABD0-4747-93D3-8722B042454B}</Project>
<Name>Orchard.Users</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Widgets\Orchard.Widgets.csproj">
<Project>{194D3CCC-1153-474D-8176-FDE8D7D0D0BD}</Project>
<Name>Orchard.Widgets</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using Autofac;
using NUnit.Framework;
using Orchard.Scripting;
using Orchard.UI.Widgets;
using Orchard.Widgets.RuleEngine;
namespace Orchard.Tests.Modules.Widgets {
[TestFixture]
public class WidgetsTests {
private IContainer _container;
private IRuleManager _ruleManager;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<ScriptingRuntime>().As<IScriptingRuntime>();
builder.RegisterType<ScriptingManager>().As<IScriptingManager>();
builder.RegisterType<AlwaysTrueRuleProvider>().As<IRuleProvider>();
builder.RegisterType<RuleManager>().As<IRuleManager>();
_container = builder.Build();
_ruleManager = _container.Resolve<IRuleManager>();
}
[Test]
public void ProviderGetsCalledForExpression() {
bool result = _ruleManager.Matches("hello");
Assert.IsTrue(result);
}
[Test]
public void RubyExpressionIsEvaluated() {
bool result = _ruleManager.Matches("not hello");
Assert.IsFalse(result);
}
[Test]
public void ArgumentsArePassedCorrectly() {
bool result = _ruleManager.Matches("add(2, 3) == 5");
Assert.IsTrue(result);
}
}
public class AlwaysTrueRuleProvider : IRuleProvider {
public void Process(RuleContext ruleContext) {
if (ruleContext.FunctionName == "add") {
ruleContext.Result = Convert.ToInt32(ruleContext.Arguments[0]) + Convert.ToInt32(ruleContext.Arguments[1]);
return;
}
ruleContext.Result = true;
}
}
}

View File

@@ -1,20 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Mvc.Filters;
using Orchard.UI.Admin;
using Orchard.UI.Widgets;
using Orchard.Widgets.Models;
namespace Orchard.Widgets.Filters {
public class WidgetFilter : FilterProvider, IResultFilter {
private readonly IContentManager _contentManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IRuleManager _ruleManager;
public WidgetFilter(IContentManager contentManager, IWorkContextAccessor workContextAccessor) {
public WidgetFilter(IContentManager contentManager, IWorkContextAccessor workContextAccessor, IRuleManager ruleManager) {
_contentManager = contentManager;
_workContextAccessor = workContextAccessor;
_ruleManager = ruleManager;
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
@@ -30,13 +33,22 @@ namespace Orchard.Widgets.Filters {
// Once the Rule Engine is done:
// Get Layers and filter by zone and rule
IEnumerable<WidgetPart> widgetParts = _contentManager.Query<WidgetPart, WidgetPartRecord>().List();
IEnumerable<LayerPart> activeLayers = _contentManager.Query<LayerPart, LayerPartRecord>().List();
List<int> activeLayerIds = new List<int>();
foreach (var activeLayer in activeLayers) {
if (_ruleManager.Matches(activeLayer.Record.LayerRule)) {
activeLayerIds.Add(activeLayer.ContentItem.Id);
}
}
// Build and add shape to zone.
var zones = workContext.Page.Zones;
foreach (var widgetPart in widgetParts) {
var widgetShape = _contentManager.BuildDisplayModel(widgetPart);
zones[widgetPart.Record.Zone].Add(widgetShape, widgetPart.Record.Position);
if (activeLayerIds.Contains(widgetPart.As<ICommonPart>().Container.ContentItem.Id)) {
var widgetShape = _contentManager.BuildDisplayModel(widgetPart);
zones[widgetPart.Record.Zone].Add(widgetShape, widgetPart.Record.Position);
}
}
}

View File

@@ -21,9 +21,12 @@ namespace Orchard.Widgets {
}
public void CreateDefaultLayers() {
_contentManager.Create<LayerPart>("Layer", t => t.Record.Name = "Default");
_contentManager.Create<LayerPart>("Layer", t => t.Record.Name = "Authenticated");
_contentManager.Create<LayerPart>("Layer", t => t.Record.Name = "Anonymous");
IContent defaultLayer = _contentManager.Create<LayerPart>("Layer", t => { t.Record.Name = "Default"; t.Record.LayerRule = "true"; });
_contentManager.Publish(defaultLayer.ContentItem);
IContent authenticatedLayer = _contentManager.Create<LayerPart>("Layer", t => { t.Record.Name = "Authenticated"; t.Record.LayerRule = "Authenticated"; });
_contentManager.Publish(authenticatedLayer.ContentItem);
IContent anonymousLayer = _contentManager.Create<LayerPart>("Layer", t => { t.Record.Name = "Anonymous"; t.Record.LayerRule = "not Authenticated"; });
_contentManager.Publish(anonymousLayer.ContentItem);
}
}
@@ -43,7 +46,7 @@ namespace Orchard.Widgets {
.ContentPartRecord()
.Column<string>("Name")
.Column<string>("Description")
.Column<string>("Rule")
.Column<string>("LayerRule")
);
SchemaBuilder.CreateTable("WidgetPartRecord", table => table

View File

@@ -4,6 +4,6 @@ namespace Orchard.Widgets.Models {
public class LayerPartRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Rule { get; set; }
public virtual string LayerRule { get; set; }
}
}

View File

@@ -69,6 +69,9 @@
<Compile Include="Permissions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Filters\WidgetFilter.cs" />
<Compile Include="RuleEngine\AuthenticatedRuleProvider.cs" />
<Compile Include="RuleEngine\RuleManager.cs" />
<Compile Include="RuleEngine\UrlRuleProvider.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />

View File

@@ -0,0 +1,26 @@
using System;
using Orchard.Security;
using Orchard.UI.Widgets;
namespace Orchard.Widgets.RuleEngine {
public class AuthenticatedRuleProvider : IRuleProvider {
private readonly IAuthenticationService _authenticationService;
public AuthenticatedRuleProvider(IAuthenticationService authenticationService) {
_authenticationService = authenticationService;
}
public void Process(RuleContext ruleContext) {
if (!String.Equals(ruleContext.FunctionName, "Authenticated", StringComparison.OrdinalIgnoreCase)) {
return;
}
if (_authenticationService.GetAuthenticatedUser() != null) {
ruleContext.Result = true;
return;
}
ruleContext.Result = false;
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Scripting;
using Orchard.UI.Widgets;
namespace Orchard.Widgets.RuleEngine {
public class RuleManager : IRuleManager {
private readonly IEnumerable<IRuleProvider> _ruleProviders;
private readonly IScriptingManager _scriptingManager;
public RuleManager(IEnumerable<IRuleProvider> ruleProviders, IScriptingManager scriptingManager) {
_ruleProviders = ruleProviders;
_scriptingManager = scriptingManager;
}
public bool Matches(string expression) {
_scriptingManager.SetVariable("callbacks", new CallbackApi(this));
dynamic execContext = _scriptingManager.Eval(@"
class ExecContext
def initialize(callbacks)
@callbacks = callbacks;
end
def execute(text)
instance_eval(text.to_s);
end
def method_missing(name, *args, &block)
@callbacks.send(name, args, &block);
end
end
ExecContext.new(callbacks)");
return execContext.execute(expression);
}
public class CallbackApi {
private readonly RuleManager _ruleManager;
public CallbackApi(RuleManager ruleManager) {
_ruleManager = ruleManager;
}
public object send(string name, IList<object> args) {
return _ruleManager.Evaluate(name, args);
}
}
private object Evaluate(string name, IList<object> args) {
RuleContext ruleContext = new RuleContext {FunctionName = name, Arguments = args.ToArray()};
foreach (var ruleProvider in _ruleProviders) {
ruleProvider.Process(ruleContext);
}
return ruleContext.Result;
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using Orchard.UI.Widgets;
namespace Orchard.Widgets.RuleEngine {
public class UrlRuleProvider : IRuleProvider {
private readonly IWorkContextAccessor _workContextAccessor;
public UrlRuleProvider(IWorkContextAccessor workContextAccessor) {
_workContextAccessor = workContextAccessor;
}
public void Process(RuleContext ruleContext) {
if (!String.Equals(ruleContext.FunctionName, "Url", StringComparison.OrdinalIgnoreCase)) {
return;
}
var context = _workContextAccessor.GetContext();
ruleContext.Result = context.HttpContext.Request.RawUrl.StartsWith(Convert.ToString(ruleContext.Arguments[0]));
}
}
}

View File

@@ -179,6 +179,9 @@
<Compile Include="Settings\ResourceDebugMode.cs" />
<Compile Include="UI\Resources\IResourceManifestProvider.cs" />
<Compile Include="UI\Resources\ResourceManifestBuilder.cs" />
<Compile Include="UI\Widgets\IRuleManager.cs" />
<Compile Include="UI\Widgets\IRuleProvider.cs" />
<Compile Include="UI\Widgets\RuleContext.cs" />
<Compile Include="UI\Zones\PageWorkContext.cs" />
<Compile Include="UI\Zones\ZoneHoldingBehavior.cs" />
<Compile Include="Mvc\ViewEngines\ThemeAwareness\ConfiguredEnginesCache.cs" />

View File

@@ -0,0 +1,5 @@
namespace Orchard.UI.Widgets {
public interface IRuleManager : IDependency {
bool Matches(string expression);
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.UI.Widgets {
public interface IRuleProvider : IDependency {
void Process(RuleContext ruleContext);
}
}

View File

@@ -0,0 +1,7 @@
namespace Orchard.UI.Widgets {
public class RuleContext {
public string FunctionName { get; set; }
public object[] Arguments { get; set; }
public object Result { get; set; }
}
}