diff --git a/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs b/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs
index 1eb43b95749..d95309fa421 100644
--- a/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs
+++ b/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs
@@ -58,6 +58,11 @@ private Task UpdateContentTypeAsync(ContentTypeDefinition type, ContentTypeDefin
foreach (var part in record.ContentTypePartDefinitionRecords)
{
+ if (string.IsNullOrEmpty(part.PartName))
+ {
+ throw new InvalidOperationException($"Unable to add content-part to the '{type.Name}' content-type. The part name cannot be null or empty.");
+ }
+
builder.WithPart(part.Name, part.PartName, partBuilder => partBuilder.MergeSettings(part.Settings));
}
});
diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj
index 97879b76cfe..28995c4eb9f 100644
--- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj
+++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj
@@ -29,6 +29,7 @@
+
@@ -44,6 +45,7 @@
+
diff --git a/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs b/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs
index 3bcdd2d742d..979b6f350a3 100644
--- a/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs
+++ b/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs
@@ -8,6 +8,7 @@
using OrchardCore.Recipes.Models;
using OrchardCore.Recipes.Services;
using OrchardCore.Scripting;
+using OrchardCore.Tests.Apis.Context;
namespace OrchardCore.Recipes
{
@@ -50,6 +51,27 @@ public async Task ShouldTrimValidScriptExpression(string recipeName, string expe
});
}
+ [Fact]
+ public async Task ContentDefinitionStep_WhenPartNameIsMissing_ThrowInvalidOperationException()
+ {
+ var context = new BlogContext();
+ await context.InitializeAsync();
+ await context.UsingTenantScopeAsync(async scope =>
+ {
+ var recipeExecutor = scope.ServiceProvider.GetRequiredService();
+ // Act
+ var executionId = Guid.NewGuid().ToString("n");
+ var recipeDescriptor = new RecipeDescriptor { RecipeFileInfo = GetRecipeFileInfo("recipe6") };
+
+ var exception = await Assert.ThrowsAsync(async () =>
+ {
+ await recipeExecutor.ExecuteAsync(executionId, recipeDescriptor, new Dictionary(), CancellationToken.None);
+ });
+
+ Assert.Equal("Unable to add content-part to the 'Message' content-type. The part name cannot be null or empty.", exception.Message);
+ });
+ }
+
private static Task GetScopeAsync() => ShellScope.Context.CreateScopeAsync();
private static ShellContext CreateShellContext() => new()
diff --git a/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json b/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json
new file mode 100644
index 00000000000..c113645d8f0
--- /dev/null
+++ b/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json
@@ -0,0 +1,27 @@
+{
+ "name": "Recipe6",
+ "displayName": "Recipe 6",
+ "description": "This recipe is designed to uncover potential bugs during the content-definition import process. It serves as a validation for the import functionality.",
+ "author": "Tony Han",
+ "website": "",
+ "version": "1.0.0",
+ "issetuprecipe": false,
+ "categories": [ "test" ],
+ "steps": [
+ {
+ "name": "ContentDefinition",
+ "ContentTypes": [
+ {
+ "Name": "Message",
+ "DisplayName": "Message",
+ "ContentTypePartDefinitionRecords": [
+ {
+ // "PartName": "TitlePart", // Test PartName validation.
+ "Name": "TitlePart"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}