Last active
April 24, 2017 14:56
-
-
Save Xceno/f79a3def10b4a5100538ef9fa34138a5 to your computer and use it in GitHub Desktop.
An override for Orchards DefaultShapeFactory, implementing changes discussed in #7073
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Orchard.Tools | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Dynamic; | |
using System.Linq; | |
using Orchard.DisplayManagement; | |
using Orchard.DisplayManagement.Descriptors; | |
using Orchard.DisplayManagement.Implementation; | |
using Orchard.DisplayManagement.Shapes; | |
using Orchard.Environment.Extensions; | |
/// <summary> | |
/// An override for Orchards DefaultShapeFactory, implementing changes discussed in #7073, to allow better support for strongly typed viewModels.<br/> | |
/// See those discussions:<br/> | |
/// https://github.com/OrchardCMS/Orchard/issues/5514<br/> | |
/// https://github.com/OrchardCMS/Orchard/issues/7073 | |
/// </summary> | |
[OrchardSuppressDependency("Orchard.DisplayManagement.Implementation.DefaultShapeFactory")] | |
public class ViewModelFriendlyShapeFactoy : Composite, IShapeFactory | |
{ | |
private readonly IEnumerable<Lazy<IShapeFactoryEvents>> events; | |
private readonly Lazy<IShapeTableLocator> shapeTableLocator; | |
public ViewModelFriendlyShapeFactoy( | |
IEnumerable<Lazy<IShapeFactoryEvents>> events, | |
Lazy<IShapeTableLocator> shapeTableLocator) | |
{ | |
this.events = events; | |
this.shapeTableLocator = shapeTableLocator; | |
} | |
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) | |
{ | |
result = this.Create(binder.Name, Arguments.From(args, binder.CallInfo.ArgumentNames)); | |
return true; | |
} | |
public IShape Create(string shapeType) | |
{ | |
return this.Create(shapeType, Arguments.Empty(), () => new Shape()); | |
} | |
public IShape Create(string shapeType, INamedEnumerable<object> parameters) | |
{ | |
return this.Create(shapeType, parameters, () => new Shape()); | |
} | |
public IShape Create(string shapeType, INamedEnumerable<object> parameters, Func<dynamic> createShape) | |
{ | |
var defaultShapeTable = this.shapeTableLocator.Value.Lookup(null); | |
ShapeDescriptor shapeDescriptor; | |
defaultShapeTable.Descriptors.TryGetValue(shapeType, out shapeDescriptor); | |
parameters = parameters ?? Arguments.Empty(); | |
var creatingContext = new ShapeCreatingContext | |
{ | |
New = this, | |
ShapeFactory = this, | |
ShapeType = shapeType, | |
OnCreated = new List<Action<ShapeCreatedContext>>() | |
}; | |
IEnumerable<object> positional = parameters.Positional.ToList(); | |
var baseType = positional.FirstOrDefault() as Type; | |
if ( baseType == null ) | |
{ | |
// default to common base class | |
creatingContext.Create = createShape ?? ( () => new Shape() ); | |
} | |
else | |
{ | |
// consume the first argument | |
positional = positional.Skip(1); | |
var initializerObject = positional.SingleOrDefault(); | |
// We need to make sure that the initializerShape is of the same type as the specified baseType. | |
// This should prevent any issues when someone passes in another type as the second, unnamed parameter like this: | |
// shapeFactory.Parts_MyPart(typeof(MyPart), new UnrelatedViewModelWithExtraProps()) | |
// If this scenario occurs, we skip the whole init process and proceed as normal. | |
var initializerHasCorrectType = initializerObject != null && initializerObject.GetType() == baseType; | |
dynamic initializerShape = initializerObject as IShape; // The cast preserves the Metadata property. | |
if ( initializerHasCorrectType && initializerShape != null ) | |
{ | |
// note: | |
// we need to clear alternates here because when using the same initializer shape twice like ... | |
// | |
// @Display.MyShapeHeaderBefore(Model.Header.GetType(), Model.Header) *1 | |
// @Display.MyShapeHeader(Model.Header.GetType(), Model.Header) *2 | |
// | |
// ... it might be possible that *1 results in an alternate being added to the initializer | |
// shape (e.g. via IShapeTableProvider) whereas *2 would then render the alternate instead of the normal shape | |
ShapeMetadata initShapeMetadata = initializerShape.Metadata; | |
if ( initShapeMetadata != null ) | |
initShapeMetadata.Alternates.Clear(); | |
creatingContext.Create = () => initializerShape; // return initializer shape directly on create call instead of creating a new instance ... | |
positional = positional.Skip(1); // ... and thus we are able to skip updating the copy (which is very costly on complex shape scenarios), see *3 | |
} | |
else | |
{ | |
creatingContext.Create = () => Activator.CreateInstance(baseType); | |
} | |
} | |
// "creating" events may add behaviors and alter base type) | |
foreach ( var ev in this.events ) | |
{ | |
ev.Value.Creating(creatingContext); | |
} | |
if ( shapeDescriptor != null ) | |
{ | |
foreach ( var ev in shapeDescriptor.Creating ) | |
{ | |
ev(creatingContext); | |
} | |
} | |
// create the new instance | |
var createdContext = new ShapeCreatedContext | |
{ | |
New = creatingContext.New, | |
ShapeType = creatingContext.ShapeType, | |
Shape = creatingContext.Create() | |
}; | |
if ( !( createdContext.Shape is IShape ) ) | |
{ | |
throw new InvalidOperationException("Invalid base type for shape: " + createdContext.Shape.GetType().ToString()); | |
} | |
ShapeMetadata shapeMetadata = createdContext.Shape.Metadata; | |
createdContext.Shape.Metadata.Type = shapeType; | |
if ( shapeDescriptor != null ) | |
shapeMetadata.Wrappers = shapeMetadata.Wrappers.Concat(shapeDescriptor.Wrappers).ToList(); | |
// "created" events provides default values and new object initialization | |
foreach ( var ev in this.events ) | |
{ | |
ev.Value.Created(createdContext); | |
} | |
if ( shapeDescriptor != null ) | |
{ | |
foreach ( var ev in shapeDescriptor.Created ) | |
{ | |
ev(createdContext); | |
} | |
} | |
foreach ( var ev in creatingContext.OnCreated ) | |
{ | |
ev(createdContext); | |
} | |
// other properties passed with call overlay any defaults, so are after the created events | |
// only one non-Type, non-named argument is remaining | |
var initializer = positional.SingleOrDefault(); | |
if ( initializer != null ) | |
{ | |
foreach ( var prop in initializer.GetType().GetProperties() ) | |
{ | |
createdContext.Shape[prop.Name] = prop.GetValue(initializer, null); | |
} | |
} | |
foreach ( var kv in parameters.Named ) | |
{ | |
createdContext.Shape[kv.Key] = kv.Value; | |
} | |
return createdContext.Shape; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment