From 7e10718e12e72303686ecd2d6f07daf6b0f97c3a Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Fri, 27 Aug 2010 17:47:27 -0700 Subject: [PATCH] Implementing shape binding abstractions and built-in strategies --HG-- branch : mvc3p1 --- .../DefaultShapeTableFactoryTests.cs | 39 ------ .../Descriptors/ContainerTestBase.cs | 22 ++++ .../DefaultShapeTableFactoryTests.cs | 24 ++++ .../DefaultShapeTableManagerTests.cs | 8 ++ .../ShapeAttributeBindingStrategyTests.cs | 111 +++++++++++++++++ .../DisplayManagement/DisplayHelperTests.cs | 2 +- .../DisplayManagement/ShapeFactoryTests.cs | 4 +- .../DisplayManagement/ShapeHelperTests.cs | 6 +- .../DisplayManagement/SubsystemTests.cs | 19 ++- .../Orchard.Framework.Tests.csproj | 5 +- .../Modules/Orchard.DevTools/Shapes.cs | 11 +- .../Modules/Orchard.Setup/SetupMode.cs | 3 +- .../ContentFieldDriverCoordinator.cs | 10 +- .../Descriptors/DefaultShapeTableFactory.cs | 10 ++ .../Descriptors/DefaultShapeTableManager.cs | 37 ++++++ .../Descriptors/Interfaces.cs | 112 ++++++++++++++++++ .../ShapeAttributeBindingModule.cs | 34 ++++++ .../ShapeAttributeBindingStrategy.cs | 84 +++++++++++++ .../ShapeAttributeOccurrence.cs | 22 ++++ src/Orchard/DisplayManagement/IShape.cs | 2 +- .../DisplayManagement/IShapeMetadata.cs | 2 +- .../{IShapeDriver.cs => IShapeProvider.cs} | 2 +- .../Implementation/DefaultDisplayManager.cs | 21 ++-- .../Implementation/DefaultShapeFactory.cs | 2 +- .../Implementation/DisplayHelperFactory.cs | 2 + .../DisplayManagement/ShapeAttribute.cs | 10 ++ src/Orchard/DisplayManagement/Shapes/Shape.cs | 2 +- .../DisplayManagement/Shapes/ShapeMetadata.cs | 2 +- .../Speculation/DefaultShapeTableFactory.cs | 86 -------------- .../Speculation/IShapeTableFactory.cs | 10 -- .../Speculation/ShapeTable.cs | 15 --- src/Orchard/Orchard.Framework.csproj | 16 ++- 32 files changed, 545 insertions(+), 190 deletions(-) delete mode 100644 src/Orchard.Tests/DisplayManagement/DefaultShapeTableFactoryTests.cs create mode 100644 src/Orchard.Tests/DisplayManagement/Descriptors/ContainerTestBase.cs create mode 100644 src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableFactoryTests.cs create mode 100644 src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs create mode 100644 src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableFactory.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/Interfaces.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingModule.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs create mode 100644 src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeOccurrence.cs rename src/Orchard/DisplayManagement/{IShapeDriver.cs => IShapeProvider.cs} (78%) create mode 100644 src/Orchard/DisplayManagement/ShapeAttribute.cs delete mode 100644 src/Orchard/DisplayManagement/Speculation/DefaultShapeTableFactory.cs delete mode 100644 src/Orchard/DisplayManagement/Speculation/IShapeTableFactory.cs delete mode 100644 src/Orchard/DisplayManagement/Speculation/ShapeTable.cs diff --git a/src/Orchard.Tests/DisplayManagement/DefaultShapeTableFactoryTests.cs b/src/Orchard.Tests/DisplayManagement/DefaultShapeTableFactoryTests.cs deleted file mode 100644 index dba922a7a..000000000 --- a/src/Orchard.Tests/DisplayManagement/DefaultShapeTableFactoryTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autofac; -using NUnit.Framework; -using Orchard.DisplayManagement; - -namespace Orchard.Tests.DisplayManagement { - [TestFixture] - public class DefaultShapeTableFactoryTests { - static IShapeTableFactory CreateShapeTableFactory(Action config) { - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - config(builder); - var container = builder.Build(); - return container.Resolve(); - } - - [Test] - public void ShapeTableRecognizesMethodNames() { - var stf = CreateShapeTableFactory(cfg => cfg.RegisterType().As()); - var shapeTable = stf.CreateShapeTable(); - Assert.That(shapeTable.Entries.Count(), Is.EqualTo(2)); - Assert.That(shapeTable.Entries.ContainsKey("Pager")); - Assert.That(shapeTable.Entries.ContainsKey("Email")); - } - - public class Test : IShapeDriver { - public void Pager() { - } - - public void Email(string text, string address) { - } - } - } - - -} diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ContainerTestBase.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ContainerTestBase.cs new file mode 100644 index 000000000..d2bf35180 --- /dev/null +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ContainerTestBase.cs @@ -0,0 +1,22 @@ +using Autofac; +using NUnit.Framework; +using Orchard.Tests.Utility; + +namespace Orchard.Tests.DisplayManagement.Descriptors { + public class ContainerTestBase { + + protected IContainer _container; + + [SetUp] + public virtual void Init() { + var builder = new ContainerBuilder(); + builder.RegisterAutoMocking(); + Register(builder); + _container = builder.Build(); + Resolve(_container); + } + + protected virtual void Register(ContainerBuilder builder) { } + protected virtual void Resolve(IContainer container) { } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableFactoryTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableFactoryTests.cs new file mode 100644 index 000000000..fac9c5874 --- /dev/null +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableFactoryTests.cs @@ -0,0 +1,24 @@ +using System; +using Autofac; +using NUnit.Framework; +using Orchard.DisplayManagement.Descriptors; + +namespace Orchard.Tests.DisplayManagement.Descriptors { + [TestFixture] + public class DefaultShapeTableFactoryTests : ContainerTestBase { + private IShapeTableFactory _factory; + + protected override void Register(ContainerBuilder builder) { + builder.RegisterType().As(); + } + + protected override void Resolve(IContainer container) { + _factory = container.Resolve(); + } + + [Test] + public void FactoryIsResolved() { + Assert.That(_factory, Is.Not.Null); + } + } +} diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs new file mode 100644 index 000000000..8fb58e4e3 --- /dev/null +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs @@ -0,0 +1,8 @@ +using NUnit.Framework; + +namespace Orchard.Tests.DisplayManagement.Descriptors { + [TestFixture] + public class DefaultShapeTableManagerTests { + + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs new file mode 100644 index 000000000..36685bf7e --- /dev/null +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.Linq; +using Autofac; +using Moq; +using NUnit.Framework; +using Orchard.DisplayManagement; +using Orchard.DisplayManagement.Descriptors; +using Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy; +using Orchard.DisplayManagement.Implementation; +using Orchard.Environment; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Tests.DisplayManagement.Descriptors { + [TestFixture] + public class ShapeAttributeBindingStrategyTests : ContainerTestBase { + private FeatureDescriptor _testFeature; + + protected override void Register(Autofac.ContainerBuilder builder) { + _testFeature = new FeatureDescriptor { Name = "Testing", Extension = new ExtensionDescriptor { Name = "Testing" } }; + builder.RegisterType().As(); + builder.RegisterInstance(new TestProvider()).WithMetadata("Feature", _testFeature); + builder.RegisterModule(new ShapeAttributeBindingModule()); + } + + protected override void Resolve(IContainer container) { + // implementation resorts to orchard host to resolve "current scope" services + container.Resolve>() + .Setup(x => x.Resolve()) + .Returns(container); + } + + class TestProvider { + [Shape] + public string Simple() { + return "Simple"; + } + + [Shape("Renamed")] + public string RenamedMethod() { + return "Renamed"; + } + } + + private IEnumerable GetInitializers() { + var strategy = _container.Resolve(); + var builder = new ShapeTableBuilder(); + strategy.Discover(builder); + return builder.Build(); + } + + [Test] + public void ShapeAttributeOccurrencesAreDetected() { + var occurrences = _container.Resolve>(); + Assert.That(occurrences.Any(o => o.MethodInfo == typeof(TestProvider).GetMethod("Simple"))); + } + + [Test] + public void InitializersHaveExpectedShapeTypeNames() { + var strategy = _container.Resolve(); + var builder = new ShapeTableBuilder(); + strategy.Discover(builder); + var initializers = builder.Build(); + Assert.That(initializers.Any(i => i.ShapeType == "Simple")); + Assert.That(initializers.Any(i => i.ShapeType == "Renamed")); + Assert.That(initializers.Any(i => i.ShapeType == "RenamedMethod"), Is.False); + } + + [Test] + public void FeatureMetadataIsDetected() { + var strategy = _container.Resolve(); + var builder = new ShapeTableBuilder(); + strategy.Discover(builder); + var initializers = builder.Build(); + Assert.That(initializers.All(i => i.Feature == _testFeature)); + } + + [Test] + public void LifetimeScopeContainersHaveMetadata() { + var strategy = _container.Resolve(); + var builder = new ShapeTableBuilder(); + strategy.Discover(builder); + var initializers = builder.Build(); + Assert.That(initializers.Any(i => i.ShapeType == "Simple")); + + var childContainer = _container.BeginLifetimeScope(); + + var strategy2 = childContainer.Resolve(); + var builder2 = new ShapeTableBuilder(); + strategy2.Discover(builder2); + var initializers2 = builder2.Build(); + Assert.That(initializers2.Any(i => i.ShapeType == "Simple")); + + Assert.That(strategy, Is.Not.SameAs(strategy2)); + } + + [Test] + public void BindingProvidedByStrategyInvokesMethod() { + var initializers = GetInitializers(); + + var shapeDescriptor = initializers.Where(i => i.ShapeType == "Simple") + .Aggregate(new ShapeDescriptor { ShapeType = "Simple" }, (d, i) => { i.Alter(d); return d; }); + + var displayContext = new DisplayContext(); + var result = shapeDescriptor.Binding(displayContext); + var result2 = shapeDescriptor.Binding.Invoke(displayContext); + Assert.That(result.ToString(), Is.StringContaining("Simple")); + Assert.That(result2.ToString(), Is.StringContaining("Simple")); + } + + } +} diff --git a/src/Orchard.Tests/DisplayManagement/DisplayHelperTests.cs b/src/Orchard.Tests/DisplayManagement/DisplayHelperTests.cs index 4187bb2a1..52569fb86 100644 --- a/src/Orchard.Tests/DisplayManagement/DisplayHelperTests.cs +++ b/src/Orchard.Tests/DisplayManagement/DisplayHelperTests.cs @@ -56,7 +56,7 @@ namespace Orchard.Tests.DisplayManagement { var displayHelperFactory = new DisplayHelperFactory(displayManager.Object, shapeFactory.Object); var display = (dynamic)displayHelperFactory.CreateHelper(viewContext, null); - var outline = new Shape { Attributes = new ShapeAttributes { Type = "Outline" } }; + var outline = new Shape { Metadata = new ShapeMetadata { Type = "Outline" } }; display(outline); //displayManager.Verify(dm => dm.Execute(outline, viewContext, null)); diff --git a/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs b/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs index 46051cba2..13d2d3bca 100644 --- a/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs +++ b/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs @@ -25,8 +25,8 @@ namespace Orchard.Tests.DisplayManagement { public void ShapeHasAttributesType() { var factory = _container.Resolve(); dynamic foo = factory.Create("Foo", ArgsUtility.Empty()); - ShapeAttributes attributes = foo.Attributes; - Assert.That(attributes.Type, Is.EqualTo("Foo")); + ShapeMetadata metadata = foo.Metadata; + Assert.That(metadata.Type, Is.EqualTo("Foo")); } [Test] diff --git a/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs b/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs index d3f7e5bf0..33d58a0bd 100644 --- a/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs +++ b/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs @@ -28,7 +28,7 @@ namespace Orchard.Tests.DisplayManagement { var alpha = shape.Alpha(); - Assert.That(alpha.Attributes.Type, Is.EqualTo("Alpha")); + Assert.That(alpha.Metadata.Type, Is.EqualTo("Alpha")); } [Test] @@ -37,7 +37,7 @@ namespace Orchard.Tests.DisplayManagement { var alpha = shape.Alpha(one: 1, two: "dos"); - Assert.That(alpha.Attributes.Type, Is.EqualTo("Alpha")); + Assert.That(alpha.Metadata.Type, Is.EqualTo("Alpha")); Assert.That(alpha.one, Is.EqualTo(1)); Assert.That(alpha.two, Is.EqualTo("dos")); } @@ -48,7 +48,7 @@ namespace Orchard.Tests.DisplayManagement { var alpha = shape.Alpha(new { one = 1, two = "dos" }); - Assert.That(alpha.Attributes.Type, Is.EqualTo("Alpha")); + Assert.That(alpha.Metadata.Type, Is.EqualTo("Alpha")); Assert.That(alpha.one, Is.EqualTo(1)); Assert.That(alpha.two, Is.EqualTo("dos")); } diff --git a/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs b/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs index c898bf87d..13bd0b8a0 100644 --- a/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs +++ b/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs @@ -5,10 +5,15 @@ using System.Text; using System.Web; using System.Web.Mvc; using Autofac; +using Moq; using NUnit.Framework; using Orchard.DisplayManagement; +using Orchard.DisplayManagement.Descriptors; +using Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy; using Orchard.DisplayManagement.Implementation; using Orchard.DisplayManagement.Shapes; +using Orchard.Environment; +using Orchard.Tests.Utility; namespace Orchard.Tests.DisplayManagement { [TestFixture] @@ -18,20 +23,28 @@ namespace Orchard.Tests.DisplayManagement { [SetUp] public void Init() { var builder = new ContainerBuilder(); + builder.RegisterModule(new ShapeAttributeBindingModule()); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType(); + builder.RegisterAutoMocking(MockBehavior.Loose); _container = builder.Build(); + _container.Resolve>() + .Setup(x => x.Resolve()) + .Returns(_container); } - public class SimpleShapes : IShapeDriver { + public class SimpleShapes { + [Shape] public IHtmlString Something() { return new HtmlString("
"); } + [Shape] public IHtmlString Pager() { return new HtmlString("
hello
"); } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 4731876c2..ad68c69e7 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -194,7 +194,10 @@ - + + + + diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Shapes.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Shapes.cs index 5bc4d1c5e..fa319bfac 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Shapes.cs +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Shapes.cs @@ -6,19 +6,24 @@ using System.Web.Mvc; using Orchard.DisplayManagement; namespace Orchard.DevTools { - public class Shapes : IShapeDriver { + public class Shapes : IDependency { + [Shape] public IHtmlString Title(dynamic text) { return new HtmlString("

" + text + "

"); } + [Shape] public IHtmlString Explosion(int? Height, int? Width) { + return new HtmlString(string.Format("Boom {0}x{1}", Height, Width)); } + [Shape] public IHtmlString Page(dynamic Display, dynamic Shape) { return Display(Shape.Sidebar, Shape.Messages); } + [Shape] public IHtmlString Zone(dynamic Display, dynamic Shape) { var tag = new TagBuilder("div"); tag.GenerateId("zone-" + Shape.Name); @@ -27,12 +32,12 @@ namespace Orchard.DevTools { tag.InnerHtml = Combine(DisplayAll(Display, Shape).ToArray()).ToString(); return new HtmlString(tag.ToString()); } - + + [Shape] public IHtmlString Message(dynamic Display, object Content, string Severity) { return Display(new HtmlString("

"), Severity ?? "Neutral", ": ", Content, new HtmlString("

")); } - static IHtmlString Combine(IEnumerable contents) { return new HtmlString(contents.Aggregate("", (a, b) => a + b)); } diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 1eb5f213c..785d2af22 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -12,6 +12,7 @@ using Orchard.Data.Migration.Interpreters; using Orchard.Data.Providers; using Orchard.Data.Migration; using Orchard.DisplayManagement; +using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Implementation; using Orchard.DisplayManagement.Shapes; using Orchard.Environment.Extensions; @@ -65,7 +66,7 @@ namespace Orchard.Setup { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As(); } diff --git a/src/Orchard/ContentManagement/Drivers/Coordinators/ContentFieldDriverCoordinator.cs b/src/Orchard/ContentManagement/Drivers/Coordinators/ContentFieldDriverCoordinator.cs index 83f584542..c6f587a61 100644 --- a/src/Orchard/ContentManagement/Drivers/Coordinators/ContentFieldDriverCoordinator.cs +++ b/src/Orchard/ContentManagement/Drivers/Coordinators/ContentFieldDriverCoordinator.cs @@ -29,11 +29,11 @@ namespace Orchard.ContentManagement.Drivers.Coordinators { var fieldTypeName = partFieldDefinition.FieldDefinition.Name; var fieldInfo = fieldInfos.FirstOrDefault(x => x.FieldTypeName == fieldTypeName); if (fieldInfo != null) { - var storage = _fieldStorageProviderSelector - .GetProvider(partFieldDefinition) - .BindStorage(contentPart, partFieldDefinition); - var field = fieldInfo.Factory(partFieldDefinition, storage); - contentPart.Weld(field); +var storage = _fieldStorageProviderSelector + .GetProvider(partFieldDefinition) + .BindStorage(contentPart, partFieldDefinition); +var field = fieldInfo.Factory(partFieldDefinition, storage); +contentPart.Weld(field); } } } diff --git a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableFactory.cs b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableFactory.cs new file mode 100644 index 000000000..2acdff0eb --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableFactory.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Orchard.DisplayManagement.Descriptors { + public class DefaultShapeTableFactory : IShapeTableFactory { + public IDictionary CreateShapeTables() { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs new file mode 100644 index 000000000..74b3b74cb --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.DisplayManagement.Descriptors { + public class DefaultShapeTableManager : IShapeTableManager { + private readonly IEnumerable _bindingStrategies; + + public DefaultShapeTableManager(IEnumerable bindingStrategies) { + _bindingStrategies = bindingStrategies; + } + + private ShapeTable _shapeTable; + + public ShapeTable GetShapeTable(string themeName) { + if (_shapeTable == null) { + var builder = new ShapeTableBuilder(); + foreach (var bindingStrategy in _bindingStrategies) { + bindingStrategy.Discover(builder); + } + // placeholder - alterations will need to be selective and in a particular order + + _shapeTable = new ShapeTable { + Descriptors = builder.Build() + .GroupBy(alteration => alteration.ShapeType) + .Select(group => group.Aggregate( + new ShapeDescriptor { ShapeType = group.Key }, + (d, a) => { + a.Alter(d); + return d; + })) + .ToDictionary(sd => sd.ShapeType) + }; + } + return _shapeTable; + } + } +} diff --git a/src/Orchard/DisplayManagement/Descriptors/Interfaces.cs b/src/Orchard/DisplayManagement/Descriptors/Interfaces.cs new file mode 100644 index 000000000..919f53cc6 --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/Interfaces.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.DisplayManagement.Implementation; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.DisplayManagement.Descriptors { + + public interface IShapeTableManager : IDependency { + ShapeTable GetShapeTable(string themeName); + } + + public interface IShapeTableFactory : IDependency { + IDictionary CreateShapeTables(); + } + + public interface IShapeDescriptorBindingStrategy : IDependency { + void Discover(ShapeTableBuilder builder); + } + + public class ShapeTable { + public IDictionary Descriptors { get; set; } + } + + public class ShapeDescriptor { + public string ShapeType { get; set; } + public ShapeBinding Binding { get; set; } + } + + public delegate object ShapeBinding(DisplayContext displayContext); + + public class ShapeTableBuilder { + readonly IList _descriptorBuilders = new List(); + + public ShapeDescriptorAlterationBuilder Describe { + get { + var db = new ShapeDescriptorAlterationBuilderImpl(); + _descriptorBuilders.Add(db); + return db; + } + } + + public IEnumerable Build() { + return _descriptorBuilders.Select(b => b.Build()); + } + + class ShapeDescriptorAlterationBuilderImpl : ShapeDescriptorAlterationBuilder { + public ShapeDescriptorAlteration Build() { + return new ShapeDescriptorAlteration(_shapeType, _feature, _configurations.ToArray()); + } + } + } + + public class ShapeDescriptorAlteration { + private readonly IList> _configurations; + + public ShapeDescriptorAlteration(string shapeType, FeatureDescriptor feature, IList> configurations) { + _configurations = configurations; + ShapeType = shapeType; + Feature = feature; + } + + public string ShapeType { get; private set; } + public FeatureDescriptor Feature { get; private set; } + public void Alter(ShapeDescriptor descriptor) { + foreach (var configuration in _configurations) { + configuration(descriptor); + } + } + } + + public class ShapeDescriptorAlterationBuilder { + protected FeatureDescriptor _feature; + protected string _shapeType; + protected readonly IList> _configurations = new List>(); + + public ShapeDescriptorAlterationBuilder Named(string shapeType) { + _shapeType = shapeType; + return this; + } + + public ShapeDescriptorAlterationBuilder From(FeatureDescriptor feature) { + _feature = feature; + return this; + } + + public ShapeDescriptorAlterationBuilder Configure(Action action) { + _configurations.Add(action); + return this; + } + + public ShapeDescriptorAlterationBuilder BoundAs(Func binder) { + // schedule the configuration + return Configure(descriptor => { + + ShapeBinding target = null; + + // announce the binding, which may be reconfigured before it's used + descriptor.Binding = displayContext => { + + // when used, first realize the actual target once + if (target == null) + target = binder(descriptor); + + // and execute the re + return target(displayContext); + }; + }); + } + } + +} diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingModule.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingModule.cs new file mode 100644 index 000000000..4ece18252 --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingModule.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using Autofac; +using Autofac.Core; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy { + public class ShapeAttributeBindingModule : Module { + readonly List _occurrences = new List(); + + protected override void Load(ContainerBuilder builder) { + builder.RegisterInstance(_occurrences).As>(); + } + + protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { + + var occurrences = registration.Activator.LimitType.GetMethods() + .SelectMany(mi => mi.GetCustomAttributes(typeof(ShapeAttribute), false).OfType() + .Select(sa => new ShapeAttributeOccurrence( + sa, + mi, + registration, + () => GetFeatureDescriptor(registration)))) + .ToArray(); + + if (occurrences.Any()) + _occurrences.AddRange(occurrences); + } + + private static FeatureDescriptor GetFeatureDescriptor(IComponentRegistration registration) { + object value; return registration.Metadata.TryGetValue("Feature", out value) ? value as FeatureDescriptor : null; + } + } +} \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs new file mode 100644 index 000000000..84686dcf9 --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Autofac; +using Autofac.Core; +using Microsoft.CSharp.RuntimeBinder; +using Orchard.DisplayManagement.Implementation; +using Orchard.Environment; + +namespace Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy { + public class ShapeAttributeBindingStrategy : IShapeDescriptorBindingStrategy { + private readonly IEnumerable _shapeAttributeOccurrences; + private readonly IOrchardHostContainer _orchardHostContainer; + + public ShapeAttributeBindingStrategy( + IEnumerable shapeAttributeOccurrences, + IOrchardHostContainer orchardHostContainer) { + _shapeAttributeOccurrences = shapeAttributeOccurrences; + _orchardHostContainer = orchardHostContainer; + } + + public void Discover(ShapeTableBuilder builder) { + foreach (var iter in _shapeAttributeOccurrences) { + var occurrence = iter; + var shapeType = occurrence.ShapeAttribute.ShapeType ?? occurrence.MethodInfo.Name; + builder.Describe + .Named(shapeType) + .From(occurrence.Feature) + .BoundAs(descriptor => CreateDelegate(occurrence, descriptor)); + } + } + + private ShapeBinding CreateDelegate( + ShapeAttributeOccurrence attributeOccurrence, + ShapeDescriptor descriptor) { + return context => { + var componentContext = _orchardHostContainer.Resolve(); + var serviceInstance = componentContext.Resolve(attributeOccurrence.Registration, Enumerable.Empty()); + + // oversimplification for the sake of evolving + return PerformInvoke(context, attributeOccurrence.MethodInfo, serviceInstance); + }; + } + + + private object PerformInvoke(DisplayContext displayContext, MethodInfo methodInfo, object serviceInstance) { + var arguments = methodInfo.GetParameters() + .Select(parameter => BindParameter(displayContext, parameter)); + + return methodInfo.Invoke(serviceInstance, arguments.ToArray()); + } + + private object BindParameter(DisplayContext displayContext, ParameterInfo parameter) { + if (parameter.Name == "Shape") + return displayContext.Value; + + if (parameter.Name == "Display") + return displayContext.Display; + + var result = ((dynamic)(displayContext.Value))[parameter.Name]; + var converter = _converters.GetOrAdd(parameter.ParameterType, CompileConverter); + return converter.Invoke((object)result); + } + + static readonly ConcurrentDictionary> _converters = + new ConcurrentDictionary>(); + + static Func CompileConverter(Type targetType) { + var valueParameter = Expression.Parameter(typeof(object), "value"); + + return Expression.Lambda>( + Expression.Convert( + Expression.Dynamic( + Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.ConvertExplicit, targetType, null), + targetType, + valueParameter), + typeof(object)), + valueParameter).Compile(); + } + } +} \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeOccurrence.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeOccurrence.cs new file mode 100644 index 000000000..050a75368 --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeOccurrence.cs @@ -0,0 +1,22 @@ +using System; +using System.Reflection; +using Autofac.Core; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy { + public class ShapeAttributeOccurrence { + private readonly Func _feature; + + public ShapeAttributeOccurrence(ShapeAttribute shapeAttribute, MethodInfo methodInfo, IComponentRegistration registration, Func feature) { + ShapeAttribute = shapeAttribute; + MethodInfo = methodInfo; + Registration = registration; + _feature = feature; + } + + public ShapeAttribute ShapeAttribute { get; private set; } + public MethodInfo MethodInfo { get; private set; } + public IComponentRegistration Registration { get; private set; } + public FeatureDescriptor Feature { get { return _feature(); } } + } +} \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/IShape.cs b/src/Orchard/DisplayManagement/IShape.cs index d6ee8dcc4..cad3c2fbc 100644 --- a/src/Orchard/DisplayManagement/IShape.cs +++ b/src/Orchard/DisplayManagement/IShape.cs @@ -5,6 +5,6 @@ /// Note: Anything on this interface is a reserved word for the purpose of shape properties /// public interface IShape { - IShapeAttributes Attributes { get; set; } + IShapeMetadata Metadata { get; set; } } } \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/IShapeMetadata.cs b/src/Orchard/DisplayManagement/IShapeMetadata.cs index 6c9507552..06a4d43b2 100644 --- a/src/Orchard/DisplayManagement/IShapeMetadata.cs +++ b/src/Orchard/DisplayManagement/IShapeMetadata.cs @@ -1,5 +1,5 @@ namespace Orchard.DisplayManagement { - public interface IShapeAttributes { + public interface IShapeMetadata { string Type { get; set; } string Position { get; set; } } diff --git a/src/Orchard/DisplayManagement/IShapeDriver.cs b/src/Orchard/DisplayManagement/IShapeProvider.cs similarity index 78% rename from src/Orchard/DisplayManagement/IShapeDriver.cs rename to src/Orchard/DisplayManagement/IShapeProvider.cs index 239d9eb1f..77a771165 100644 --- a/src/Orchard/DisplayManagement/IShapeDriver.cs +++ b/src/Orchard/DisplayManagement/IShapeProvider.cs @@ -3,5 +3,5 @@ /// Base interface for module components which define new shape types and /// optionally provide default implementation method /// - public interface IShapeDriver : IDependency{} + public interface IShapeProvider : IDependency{} } \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs b/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs index 79c6f8422..de11ada5f 100644 --- a/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs +++ b/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs @@ -4,11 +4,13 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Web; using Microsoft.CSharp.RuntimeBinder; +using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Shapes; using Orchard.Localization; namespace Orchard.DisplayManagement.Implementation { public class DefaultDisplayManager : IDisplayManager { + private readonly IShapeTableManager _shapeTableManager; private readonly IShapeTableFactory _shapeTableFactory; // this need to be Shape instead of IShape - cast to interface throws error on clr types like HtmlString @@ -19,8 +21,8 @@ namespace Orchard.DisplayManagement.Implementation { typeof(Shape), null/*typeof(DefaultDisplayManager)*/))); - public DefaultDisplayManager(IShapeTableFactory shapeTableFactory) { - _shapeTableFactory = shapeTableFactory; + public DefaultDisplayManager(IShapeTableManager shapeTableManager) { + _shapeTableManager = shapeTableManager; T = NullLocalizer.Instance; } @@ -28,23 +30,24 @@ namespace Orchard.DisplayManagement.Implementation { public IHtmlString Execute(DisplayContext context) { + var shape = _convertAsShapeCallsite.Target(_convertAsShapeCallsite, context.Value); // non-shape arguements are returned as a no-op if (shape == null) return CoerceHtmlString(context.Value); - var shapeAttributes = shape.Attributes; + var shapeAttributes = shape.Metadata; // can't really cope with a shape that has no type information if (shapeAttributes == null || string.IsNullOrEmpty(shapeAttributes.Type)) return CoerceHtmlString(context.Value); - var shapeTable = _shapeTableFactory.CreateShapeTable(); + var shapeTable = _shapeTableManager.GetShapeTable(null); //preproc loop / event (alter shape, swapping type) - ShapeTable.Entry entry; - if (shapeTable.Entries.TryGetValue(shapeAttributes.Type, out entry)) { - return Process(entry, shape, context); + ShapeDescriptor shapeDescriptor; + if (shapeTable.Descriptors.TryGetValue(shapeAttributes.Type, out shapeDescriptor)) { + return Process(shapeDescriptor, shape, context); } //postproc / content html alteration/wrapping/etc throw new OrchardException(T("Shape type {0} not found", shapeAttributes.Type)); @@ -61,8 +64,8 @@ namespace Orchard.DisplayManagement.Implementation { return new HtmlString(HttpUtility.HtmlEncode(value)); } - private IHtmlString Process(ShapeTable.Entry entry, IShape shape, DisplayContext context) { - return CoerceHtmlString(entry.Target(context)); + private IHtmlString Process(ShapeDescriptor shapeDescriptor, IShape shape, DisplayContext context) { + return CoerceHtmlString(shapeDescriptor.Binding(context)); } class ForgivingConvertBinder : ConvertBinder { diff --git a/src/Orchard/DisplayManagement/Implementation/DefaultShapeFactory.cs b/src/Orchard/DisplayManagement/Implementation/DefaultShapeFactory.cs index c82f3b620..d212cd865 100644 --- a/src/Orchard/DisplayManagement/Implementation/DefaultShapeFactory.cs +++ b/src/Orchard/DisplayManagement/Implementation/DefaultShapeFactory.cs @@ -41,7 +41,7 @@ namespace Orchard.DisplayManagement.Implementation { // consideration - types without default constructors could consume positional arguments? var shape = ClayActivator.CreateInstance(baseType, behaviors); - shape.Attributes = new ShapeAttributes { Type = shapeType }; + shape.Metadata = new ShapeMetadata { Type = shapeType }; // only one non-Type, non-named argument is allowed var initializer = positional.SingleOrDefault(); diff --git a/src/Orchard/DisplayManagement/Implementation/DisplayHelperFactory.cs b/src/Orchard/DisplayManagement/Implementation/DisplayHelperFactory.cs index 274a9adea..4107114e4 100644 --- a/src/Orchard/DisplayManagement/Implementation/DisplayHelperFactory.cs +++ b/src/Orchard/DisplayManagement/Implementation/DisplayHelperFactory.cs @@ -26,6 +26,8 @@ namespace Orchard.DisplayManagement.Implementation { public override object InvokeMember(Func proceed, object target, string name, INamedEnumerable args) { return ((DisplayHelper)target).Invoke(name, args); } + + } } } \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/ShapeAttribute.cs b/src/Orchard/DisplayManagement/ShapeAttribute.cs new file mode 100644 index 000000000..5ef7b82db --- /dev/null +++ b/src/Orchard/DisplayManagement/ShapeAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Orchard.DisplayManagement { + public class ShapeAttribute : Attribute { + public ShapeAttribute() { } + public ShapeAttribute(string shapeType) { this.ShapeType = shapeType; } + + public string ShapeType { get; private set; } + } +} \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Shapes/Shape.cs b/src/Orchard/DisplayManagement/Shapes/Shape.cs index 169e30aaa..d7c95254d 100644 --- a/src/Orchard/DisplayManagement/Shapes/Shape.cs +++ b/src/Orchard/DisplayManagement/Shapes/Shape.cs @@ -1,5 +1,5 @@ namespace Orchard.DisplayManagement.Shapes { public class Shape : IShape { - public virtual IShapeAttributes Attributes { get; set; } + public virtual IShapeMetadata Metadata { get; set; } } } diff --git a/src/Orchard/DisplayManagement/Shapes/ShapeMetadata.cs b/src/Orchard/DisplayManagement/Shapes/ShapeMetadata.cs index 486820e6d..0a70c9a67 100644 --- a/src/Orchard/DisplayManagement/Shapes/ShapeMetadata.cs +++ b/src/Orchard/DisplayManagement/Shapes/ShapeMetadata.cs @@ -1,5 +1,5 @@ namespace Orchard.DisplayManagement.Shapes { - public class ShapeAttributes : IShapeAttributes { + public class ShapeMetadata : IShapeMetadata { public string Type { get; set; } public string Position { get; set; } } diff --git a/src/Orchard/DisplayManagement/Speculation/DefaultShapeTableFactory.cs b/src/Orchard/DisplayManagement/Speculation/DefaultShapeTableFactory.cs deleted file mode 100644 index 559bdfda9..000000000 --- a/src/Orchard/DisplayManagement/Speculation/DefaultShapeTableFactory.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Web; -using Microsoft.CSharp.RuntimeBinder; -using Orchard.DisplayManagement.Implementation; -using Binder = Microsoft.CSharp.RuntimeBinder.Binder; - -namespace Orchard.DisplayManagement { - public class DefaultShapeTableFactory : IShapeTableFactory { - private readonly IEnumerable _shapeProviders; - - public DefaultShapeTableFactory(IEnumerable shapeProviders) { - _shapeProviders = shapeProviders; - } - - public ShapeTable CreateShapeTable() { - var table = new ShapeTable { Entries = GetEntries().ToDictionary(e => e.ShapeType) }; - return table; - } - - private IEnumerable GetEntries() { - foreach (var shapeProvider in _shapeProviders) { - foreach (var methodInfo in shapeProvider.GetType().GetMethods().Where(IsAcceptableMethod)) { - var info = methodInfo; - var provider = shapeProvider; - yield return new ShapeTable.Entry { - ShapeType = methodInfo.Name, - Target = ctx => PerformInvoke(ctx, info, provider) - }; - } - } - } - - private object PerformInvoke(DisplayContext displayContext, MethodInfo methodInfo, IShapeDriver shapeDriver) { - // oversimplification for the sake of evolving - dynamic shape = displayContext.Value; - var arguments = methodInfo.GetParameters() - .Select(parameter => BindParameter(displayContext, parameter)); - - return methodInfo.Invoke(shapeDriver, arguments.ToArray()); - } - - private object BindParameter(DisplayContext displayContext, ParameterInfo parameter) { - if (parameter.Name == "Shape") - return displayContext.Value; - - if (parameter.Name == "Display") - return displayContext.Display; - - var result = ((dynamic)(displayContext.Value))[parameter.Name]; - var converter = _converters.GetOrAdd( - parameter.ParameterType, - CompileConverter); - return converter(result); - } - - static Func CompileConverter(Type targetType) { - var valueParameter = Expression.Parameter(typeof (object), "value"); - - return Expression.Lambda>( - Expression.Convert( - Expression.Dynamic( - Binder.Convert(CSharpBinderFlags.ConvertExplicit, targetType, null), - targetType, - valueParameter), - typeof (object)), - valueParameter).Compile(); - } - - static readonly ConcurrentDictionary> _converters = - new ConcurrentDictionary>(); - - static bool IsAcceptableMethod(MethodInfo methodInfo) { - if (methodInfo.IsSpecialName) - return false; - if (methodInfo.DeclaringType == typeof(object)) - return false; - return true; - } - } -} diff --git a/src/Orchard/DisplayManagement/Speculation/IShapeTableFactory.cs b/src/Orchard/DisplayManagement/Speculation/IShapeTableFactory.cs deleted file mode 100644 index 8d12583c4..000000000 --- a/src/Orchard/DisplayManagement/Speculation/IShapeTableFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Orchard.DisplayManagement { - public interface IShapeTableFactory : IDependency { - ShapeTable CreateShapeTable(); - } -} diff --git a/src/Orchard/DisplayManagement/Speculation/ShapeTable.cs b/src/Orchard/DisplayManagement/Speculation/ShapeTable.cs deleted file mode 100644 index 6e7bbd9e4..000000000 --- a/src/Orchard/DisplayManagement/Speculation/ShapeTable.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using Orchard.DisplayManagement.Implementation; - -namespace Orchard.DisplayManagement { - public class ShapeTable { - public IDictionary Entries { get; set; } - - public class Entry { - public string ShapeType { get; set; } - public Func Target { get; set; } - } - } -} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 3c0e5db95..8b48a8259 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -377,25 +377,29 @@ Code + + + + + - + - - + + - - + + -