OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.37k stars 2.38k forks source link

Layer content reference "The collection was modified" #9333

Open jptissot opened 3 years ago

jptissot commented 3 years ago

Describe the bug

Rendering a widget in a layer zone that references content that also renders in the same zone throws exeption.

I have a Widget that has a content picker and the template renders the items.

{%comment%}
Widget-ContentReference.liquid
This widget is used to render the referenced items
{%endcomment%}

{% assign contentItems = Model.ContentItem.Content.ContentReference.Items.ContentItemIds | content_item_id %}
{% for contentItem in contentItems %}
    {{ contentItem | shape_build_display | shape_render }}
{% endfor %}
{%comment%}
Content-Modal.liquid
This widget is used to define a bootstrap modal in the modal zone
{%endcomment%}
{% zone "Modal" %}
<div id="{{Model.ContentItem.DisplayText | slugify}}" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> 
  {{Model.Content.LiquidPart | shape_render}}
</div>
{% endzone %}

If I place the ContentReference widget in a layer zone called Modal and reference content that also outputs to the same Modal zone. I get the exception below.

Screenshots

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      => SpanId:2698407e84210c4d, TraceId:5eba4da84c5ace459591755bfb0f2de1, ParentId:0000000000000000 => ConnectionId:0HM8CL88UPVSF => RequestPath:/t22ms895/ RequestId:0HM8CL88UPVSF:00000001
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
         at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
         at OrchardCore.DisplayManagement.Zones.ZoneShapes.Zone(IDisplayHelper DisplayAsync, IEnumerable`1 Shape)
         at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.<ProcessAsync>g__Awaited|11_0(Task`1 task)
         at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
         at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
         at OrchardCore.DisplayManagement.Theming.ThemeLayout.ExecuteAsync()
         at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
         at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
         at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter)
         at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
         at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
         at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
         at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
         at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
         at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)    
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope 
scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
         at OrchardCore.Diagnostics.DiagnosticsStartupFilter.<>c__DisplayClass3_0.<<Configure>b__1>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.<>c__DisplayClass6_0.<<UseStatusCodePagesWithReExecute>b__0>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
         at OrchardCore.ContentPreview.PreviewStartupFilter.<>c.<<Configure>b__1_1>d.MoveNext()
      --- End of stack trace from previous location ---
         at OrchardCore.Modules.ModularTenantRouterMiddleware.Invoke(HttpContext httpContext)
         at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
         at OrchardCore.Modules.ModularTenantContainerMiddleware.Invoke(HttpContext httpContext)
         at NetEscapades.AspNetCore.SecurityHeaders.SecurityHeadersMiddleware.Invoke(HttpContext context) in C:\projects\netescapades-aspnetcore-securityheaders\src\NetEscapades.AspNetCore.SecurityHeaders\SecurityHeadersMiddleware.cs:line 68
         at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
sebastienros commented 3 years ago

@deanmarcussen says you are rendering two things in the same zone (same list of shapes). While the zone is iterated to be rendered, something else is added to it. You can't add something to the "Modal" zone while you are rendering "Modal". You should create a distinct one.

jptissot commented 3 years ago

What if a template that I am referencing via a content picker renders something in the same zone I am rendering? Would it be possible to insert the value at the end of the zone ?

deanmarcussen commented 3 years ago

technically possible @jptissot but weird, because zones (all shapes) support both being in the zone, and the position in the zone, so realizing the collection first, then adding an extra to it, would leave it unsorted and weird.

The solution here, is probably to use placement to locate it. Because placement is applied before rendering, getting around the issue entirely.

Or as suggested above use two zones - something I've done many times, so in your case you might have Modal and ModalContent

ns8482e commented 3 years ago

Content are executed first and zone rendering are extracted and placed in defined zones.

It’s bad idea to place a shape to a rendering zone. Here it’s rendering on same zone. But if you are trying to place a shape to Header zone while rending Footer zone, will not render the shape to header zone as header zone is already processed before Footer

If you are using {% zone "Modal" %} in template then always render that shape inside Content