Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 17, 2025

Description

Dynamic shape creation using _shapeFactory.New.ContentCard(...) has been replaced with strongly typed shapes using CreateAsync<T> for better performance and type safety.

Changes

New shape classes in OrchardCore.Flows.Core/Models:

  • ContentCardShape - encapsulates all ContentCard properties including CanInsert
  • ContentCardFieldsEditShape - for ContentCard_Fields_Edit shape
  • ContentCardFrameShape - for ContentCard_Frame wrapper shape

Updated shape creation pattern:

Controllers:

// Before
var contentCard = await _shapeFactory.New.ContentCard(
    Updater: _updateModelAccessor.ModelUpdater,
    CollectionShapeType: cardCollectionType,
    ContentItem: contentItem,
    // ... more properties
);

// After
var contentCard = await _shapeFactory.CreateAsync<ContentCardShape>("ContentCard", shape =>
{
    shape.Updater = _updateModelAccessor.ModelUpdater;
    shape.CollectionShapeType = cardCollectionType;
    shape.ContentItem = contentItem;
    // ... more properties
});

Views:

// Before
dynamic contentCard = await New.ContentCard(...);

// After
var contentCard = await Factory.CreateAsync<ContentCardShape>("ContentCard", shape => { ... });

Shape handler updates:

  • Updated ContentCardShapes.cs to properly access strongly typed shape properties by casting to the shape class first (e.g., (shape as ContentCardShape)?.Property) with fallback to GetProperty for backward compatibility
  • Added null checks to prevent NullReferenceException when accessing nested properties
  • This ensures compatibility with both strongly typed shapes and any existing dynamic shapes

Bug fixes:

  • Fixed NullReferenceException when accessing nested properties in shape handlers by adding proper null checks and using pattern matching with fallback to GetProperty
  • Added missing CanInsert property to ContentCardShape class (defaults to true) - required by ContentCard-FlowPart.Edit.cshtml and ContentCard-WidgetsListPart.Edit.cshtml views

Architecture improvements:

  • Moved shape classes to Core project: ContentCard shapes moved from OrchardCore.Widgets/Models to OrchardCore.Flows.Core/Models to follow proper architecture pattern where module-to-module references are not allowed
  • Removed module-to-module reference: Removed the improper OrchardCore.Widgets reference from OrchardCore.Flows module. Both modules now properly reference the shared OrchardCore.Flows.Core project
  • Use Factory property from base class: Removed unnecessary @inject IShapeFactory ShapeFactory declarations from all views and use the Factory property available in the RazorPage base class instead
  • Added necessary dependencies to OrchardCore.Flows.Core project (ContentTypes.Abstractions, DisplayManagement.Abstractions)
  • Added using OrchardCore.DisplayManagement; to ViewImports for extension method resolution

Modules affected

  • OrchardCore.Flows.Core (new shape classes)
  • OrchardCore.Flows (1 controller, 2 views, project references)
  • OrchardCore.Widgets (1 controller, 3 views, shape handler, project references)
Original prompt

This section details on the original issue you should resolve

<issue_title>Remove remaining dynamic shapes</issue_title>
<issue_description>Example: var contentCard = await _shapeFactory.New.ContentCard();

We should create strongly typed shapes.</issue_description>

<agent_instructions>We should try to avoid creating dynamic shapes as they are less optimized. Check how other strongly typed shapes are created, then replace any dynamic s = New.[SHAPETYPE] usage by a custom class that accepts the same properties. Use the IShapeFactory instance. These can be present in views and source code.

Creating a shape from a normal type looks like this:

var factory = context.RequestServices.GetRequiredService<IShapeFactory>();
var carShape = await factory.CreateAsync<Car>(c => c.Brand = "Toyota");

Where Car becomes the shape type (IShape.Type property).

public class Car
{
  public string Brand { get; set; }
}

The key is that at runtime the shape factory will create a proxy that inherits from Car and implements IShape.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@hishamco Shall we do it incrementally, if we do so, we need to remove them from the views first @sebastienros 1- Create class 2- Instantiate class with a shape factory instead of `dynamic` and `New` 3- Push ;)

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits December 17, 2025 00:46
…to use them

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
…ting

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Copilot AI changed the title [WIP] Remove remaining dynamic shapes and replace with strongly typed shapes Replace dynamic ContentCard shapes with strongly typed shapes Dec 17, 2025
Copilot AI requested a review from sebastienros December 17, 2025 01:02
@sebastienros
Copy link
Member

@copilot there is an error when trying to add a widget to the FlowPart:

fail: OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay[0]
      NullReferenceException thrown from Func`2 by System.Func`2[[OrchardCore.DisplayManagement.Implementation.ShapeDisplayContext, OrchardCore.DisplayManagement, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Threading.Tasks.Task, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
      System.NullReferenceException: Object reference not set to an instance of an object.
         at OrchardCore.DisplayManagement.IShapeExtensions.GetProperty[T](IShape shape, String key, T value) in D:\orchardcore\src\OrchardCore\OrchardCore.DisplayManagement\IShape.cs:line 56
         at OrchardCore.DisplayManagement.IShapeExtensions.GetProperty[T](IShape shape, String key) in D:\orchardcore\src\OrchardCore\OrchardCore.DisplayManagement\IShape.cs:line 51
         at OrchardCore.Widgets.ContentCardShapes.<>c.<DiscoverAsync>b__3_2(ShapeDisplayContext context) in D:\orchardcore\src\OrchardCore.Modules\OrchardCore.Widgets\ContentCardShapes.cs:line 139
         at OrchardCore.DisplayManagement.Descriptors.ShapeAlterationBuilder.<>c__DisplayClass12_0.<OnDisplaying>b__0(ShapeDisplayContext ctx) in D:\orchardcore\src\OrchardCore\OrchardCore.DisplayManagement\Descriptors\ShapeAlterationBuilder.cs:line 114
         at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.<>c.<ExecuteAsync>b__9_2(Func`2 action, ShapeDisplayContext displayContext) in D:\orchardcore\src\OrchardCore\OrchardCore.DisplayManagement\Implementation\DefaultHtmlDisplay.cs:line 97
         at OrchardCore.Modules.InvokeExtensions.InvokeAsync[TEvents,T1](IEnumerable`1 events, Func`3 dispatch, T1 arg1, ILogger logger) in D:\orchardcore\src\OrchardCore\OrchardCore.Abstractions\Modules\Extensions\InvokeExtensions.cs:line 130

…ties in ContentCardShapes

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 17, 2025

@copilot there is an error when trying to add a widget to the FlowPart:

...

Fixed in commit adfa0f5. The issue was that ContentCardShapes.cs was using GetProperty to access nested strongly typed shape properties, which was causing NullReferenceException. Updated the shape handlers to cast to strongly typed shapes first (e.g., (contentCardEditor as ContentCardShape)?.CollectionShapeType) with fallback to GetProperty for backward compatibility. Added null checks to prevent exceptions when accessing nested properties.

@sebastienros
Copy link
Member

@copilot

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Castle.Proxies.ContentCardShapeProxy' does not contain a definition for 'CanInsert'
         at CallSite.Target(Closure, CallSite, Object)
         at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
         at AspNetCoreGeneratedDocument.Views_ContentCard_FlowPart_Edit.ExecuteAsync() in D:\orchardcore\src\OrchardCore.Modules\OrchardCore.Flows\Views\ContentCard-FlowPart.Edit.cshtml:line 42

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 17, 2025

@copilot

...

Fixed in commit 6af2cb8. The issue was that the ContentCardShape class was missing the CanInsert property which is used in ContentCard-FlowPart.Edit.cshtml and ContentCard-WidgetsListPart.Edit.cshtml. Added the property with a default value of true to maintain the same behavior as the dynamic shape.

@sebastienros
Copy link
Member

Seems to work

FlowPart

image

BagPart

image

@sebastienros sebastienros marked this pull request as ready for review December 17, 2025 02:12
@sebastienros
Copy link
Member

I'd someone else to try this as I haven't used these two parts in a while.

/cc @buzznick

@model BagPartEditViewModel
@inject IContentManager ContentManager
@inject OrchardCore.ContentManagement.Display.IContentItemDisplayManager ContentItemDisplayManager
@inject OrchardCore.DisplayManagement.IShapeFactory ShapeFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shape factory is already available in the Factory property of the base class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot The shape factory is already available in the Factory property of the base class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit ba14487. Removed @inject IShapeFactory ShapeFactory from all views and now using the Factory property that's available in the RazorPage base class.

<ProjectReference Include="..\..\OrchardCore\OrchardCore.Liquid.Abstractions\OrchardCore.Liquid.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Module.Targets\OrchardCore.Module.Targets.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ResourceManagement\OrchardCore.ResourceManagement.csproj" />
<ProjectReference Include="..\OrchardCore.Widgets\OrchardCore.Widgets.csproj" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module to Module references should not be done. The shape classes need to go to a common 'Core' or 'Abstractions' project.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Module to Module references should not be done. The shape classes need to go to a common 'Core' or 'Abstractions' project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit ba14487. Moved the ContentCard shape classes from OrchardCore.Widgets/Models to OrchardCore.Flows.Core/Models. Removed the module-to-module reference from Flows to Widgets. Both modules now reference the shared OrchardCore.Flows.Core project.

… from base class

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
@sebastienros
Copy link
Member

@gvkries should the shapes not be in Flow but in a new Widgets.Core intead?

@gvkries
Copy link
Member

gvkries commented Dec 18, 2025

@gvkries should the shapes not be in Flow but in a new Widgets.Core intead?

Conceptually yes, but adding another project? IMHO, we already have too many projects, maybe we should consolidate those types into a single assembly. What about OrchardCore.DisplayManagement.Shapes, there are already some core shapes there.
But we can also leave this as an exercise for the future... ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove remaining dynamic shapes

3 participants