{"record":{"id":"3e6ad50af012cd5a","repo":"OrchardCMS/OrchardCore","slug":"zone-is-required-call-zone-before-calling-build","errorCode":null,"errorMessage":"Zone is required. Call Zone() before calling Build().","messagePattern":"Zone is required\\. Call Zone\\(\\) before calling Build\\(\\)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/PlacementLocationBuilder.cs","lineNumber":170,"sourceCode":"        _cachedPlacementInfo = null;\n        return this;\n    }\n\n    /// <summary>\n    /// Builds a <see cref=\"PlacementInfo\"/> instance from the current builder state.\n    /// </summary>\n    /// <returns>A new <see cref=\"PlacementInfo\"/> with the configured location.</returns>\n    /// <exception cref=\"InvalidOperationException\">Thrown when <see cref=\"Zone\"/> has not been called.</exception>\n    public PlacementInfo Build()\n    {\n        if (_cachedPlacementInfo != null)\n        {\n            return _cachedPlacementInfo;\n        }\n\n        if (string.IsNullOrEmpty(_zone))\n        {\n            throw new InvalidOperationException(\"Zone is required. Call Zone() before calling Build().\");\n        }\n\n        // Create GroupingMetadata directly without string allocation.\n        var tabGrouping = !string.IsNullOrEmpty(_tabName)\n            ? new GroupingMetadata(_tabName, _tabPosition)\n            : GroupingMetadata.Empty;\n\n        var cardGrouping = !string.IsNullOrEmpty(_cardName)\n            ? new GroupingMetadata(_cardName, _cardPosition)\n            : GroupingMetadata.Empty;\n\n        var columnGrouping = !string.IsNullOrEmpty(_columnName)\n            ? new GroupingMetadata(_columnName, _columnPosition, _columnWidth)\n            : GroupingMetadata.Empty;\n\n        // Split zones only once - use the zone string directly if no dots.\n        var zones = _zone.Contains('.')\n            ? _zone.Split('.')","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/PlacementLocationBuilder.cs#L152-L188","documentation":"PlacementLocationBuilder fluently builds a PlacementInfo (zone, position, alternates, etc.). Build() requires a zone to have been set via Zone(); otherwise the resulting PlacementInfo would be unusable for shape placement, so it throws InvalidOperationException(\"Zone is required. Call Zone() before calling Build().\").","triggerScenarios":"Calling Build() (directly or via ToString()/Location) on a PlacementLocationBuilder without ever calling Zone(...) — e.g., setting only Position or Tab in placement code.","commonSituations":"Programmatic placement (IDisplayDriver placement methods) where a code path skips Zone() conditionally; malformed placement.json entries that omit the zone but set other keys converted into a builder; refactors that dropped the Zone() call.","solutions":["Ensure every code path in your placement logic calls Zone(\"...\") before returning/locating.","In placement.json, provide a \"place\" value that includes a zone (e.g., \"Content:after\" or \"Sidebar:10\"), not just a tab or wrapper.","Check conditional builder chains: if placement depends on a setting, fall back to a default zone."],"exampleFix":"// before\nvar location = new PlacementLocationBuilder()\n    .Position(\"5\")\n    .Build();\n// after\nvar location = new PlacementLocationBuilder()\n    .Zone(\"Content\")\n    .Position(\"5\")\n    .Build();","handlingStrategy":"validation","validationCode":"var builder = new PlacementLocationBuilder();\n// ensure zone is set before Build():\nvar location = builder.Zone(\"Content\").Build();","typeGuard":null,"tryCatchPattern":"catch (InvalidOperationException ex) when (ex.Message.Contains(\"Zone is required\")) { logger.LogError(ex, \"Placement for {ShapeType} omitted a zone\", shapeType); }","preventionTips":["Always call Zone() first when chaining PlacementLocationBuilder","Validate placement.json entries include a zone in their 'place' value","Default conditional placement paths to a fallback zone"],"tags":["placement","display","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}