Simplify creating content from a blueprint programmatically (#19528)

* Rename `IContentService.CreateContentFromBlueprint` to `CreateBlueprintFromContent`

In reality, this method is used by the core to create a blueprint from content, and not the other way around, which doesn't need new ids. This was causing confusion, so the old name has been marked as deprecated in favor of the new name. If developers want to create content from blueprints they should use `IContentBlueprintEditingService.GetScaffoldedAsync()` instead, which is what is used by the management api.

* Added integration tests to verify that new block ids are generated when creating content from a blueprint

* Return copy of the blueprint in `ContentBlueprintEditingService.GetScaffoldedAsync` instead of the blueprint itself

* Update CreateContentFromBlueprint xml docs to mention both replacement methods

* Fix tests for rich text blocks

* Small re-organization

* Adjusted tests that were still referencing `ContentService.CreateContentFromBlueprint`

* Add default implementation to new CreateBlueprintFromContent method

* Update tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Laura Neto
2025-06-24 13:43:34 +02:00
committed by GitHub
parent b41eecf58c
commit 55506bac3a
10 changed files with 386 additions and 136 deletions

View File

@@ -130,7 +130,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
}
[Test]
public void Create_Content_From_Blueprint()
public void Create_Blueprint_From_Content()
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
@@ -140,22 +140,21 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
var contentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id);
ContentTypeService.Save(contentType);
var blueprint = ContentBuilder.CreateTextpageContent(contentType, "hello", Constants.System.Root);
blueprint.SetValue("title", "blueprint 1");
blueprint.SetValue("bodyText", "blueprint 2");
blueprint.SetValue("keywords", "blueprint 3");
blueprint.SetValue("description", "blueprint 4");
var originalPage = ContentBuilder.CreateTextpageContent(contentType, "hello", Constants.System.Root);
originalPage.SetValue("title", "blueprint 1");
originalPage.SetValue("bodyText", "blueprint 2");
originalPage.SetValue("keywords", "blueprint 3");
originalPage.SetValue("description", "blueprint 4");
ContentService.Save(originalPage);
ContentService.SaveBlueprint(blueprint);
var fromContent = ContentService.CreateBlueprintFromContent(originalPage, "hello world");
ContentService.SaveBlueprint(fromContent);
var fromBlueprint = ContentService.CreateContentFromBlueprint(blueprint, "hello world");
ContentService.Save(fromBlueprint);
Assert.IsTrue(fromBlueprint.HasIdentity);
Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].GetValue());
Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].GetValue());
Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].GetValue());
Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].GetValue());
Assert.IsTrue(fromContent.HasIdentity);
Assert.AreEqual("blueprint 1", fromContent.Properties["title"]?.GetValue());
Assert.AreEqual("blueprint 2", fromContent.Properties["bodyText"]?.GetValue());
Assert.AreEqual("blueprint 3", fromContent.Properties["keywords"]?.GetValue());
Assert.AreEqual("blueprint 4", fromContent.Properties["description"]?.GetValue());
}
}

View File

@@ -286,104 +286,12 @@ internal sealed class ElementSwitchValidatorTests : UmbracoIntegrationTest
Guid elementKey,
Guid? elementSettingKey)
{
Dictionary<string, object> configuration;
switch (editorAlias)
{
case Constants.PropertyEditors.Aliases.BlockGrid:
configuration = GetBlockGridBaseConfiguration();
break;
case Constants.PropertyEditors.Aliases.RichText:
configuration = GetRteBaseConfiguration();
break;
default:
configuration = new Dictionary<string, object>();
break;
}
SetBlockConfiguration(
configuration,
var dataType = DataTypeBuilder.CreateSimpleElementDataType(
IOHelper,
editorAlias,
elementKey,
elementSettingKey,
editorAlias == Constants.PropertyEditors.Aliases.BlockGrid ? true : null);
var dataTypeBuilder = new DataTypeBuilder()
.WithId(0)
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(editorAlias);
switch (editorAlias)
{
case Constants.PropertyEditors.Aliases.BlockGrid:
dataTypeBuilder.WithConfigurationEditor(
new BlockGridConfigurationEditor(IOHelper) { DefaultConfiguration = configuration });
break;
case Constants.PropertyEditors.Aliases.BlockList:
dataTypeBuilder.WithConfigurationEditor(
new BlockListConfigurationEditor(IOHelper) { DefaultConfiguration = configuration });
break;
case Constants.PropertyEditors.Aliases.RichText:
dataTypeBuilder.WithConfigurationEditor(
new RichTextConfigurationEditor(IOHelper) { DefaultConfiguration = configuration });
break;
}
var dataType = dataTypeBuilder.Done()
.Build();
elementSettingKey);
await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
}
private void SetBlockConfiguration(
Dictionary<string, object> dictionary,
Guid? elementKey,
Guid? elementSettingKey,
bool? allowAtRoot)
{
if (elementKey is null)
{
return;
}
dictionary["blocks"] = new[] { BuildBlockConfiguration(elementKey.Value, elementSettingKey, allowAtRoot) };
}
private Dictionary<string, object> GetBlockGridBaseConfiguration()
=> new Dictionary<string, object> { ["gridColumns"] = 12 };
private Dictionary<string, object> GetRteBaseConfiguration()
{
var dictionary = new Dictionary<string, object>
{
["maxImageSize"] = 500,
["mode"] = "Classic",
["toolbar"] = new[]
{
"styles", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist",
"outdent", "indent", "sourcecode", "link", "umbmediapicker", "umbembeddialog"
},
};
return dictionary;
}
private Dictionary<string, object> BuildBlockConfiguration(
Guid? elementKey,
Guid? elementSettingKey,
bool? allowAtRoot)
{
var dictionary = new Dictionary<string, object>();
if (allowAtRoot is not null)
{
dictionary.Add("allowAtRoot", allowAtRoot.Value);
}
dictionary.Add("contentElementTypeKey", elementKey.ToString());
if (elementSettingKey is not null)
{
dictionary.Add("settingsElementTypeKey", elementSettingKey.ToString());
}
return dictionary;
}
}

View File

@@ -54,6 +54,8 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest
private IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();
private IContentBlueprintEditingService ContentBlueprintEditingService => GetRequiredService<IContentBlueprintEditingService>();
private readonly LanguageBuilder _languageBuilder = new();
private readonly UserBuilder _userBuilder = new();
@@ -99,7 +101,7 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest
}
[Test]
public void SectionService_Can_Get_Allowed_Sections_For_User()
public async Task SectionService_Can_Get_Allowed_Sections_For_User()
{
// Arrange
var template = TemplateBuilder.CreateTextPageTemplate();
@@ -116,7 +118,9 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest
ContentService.SaveBlueprint(blueprint);
var fromBlueprint = ContentService.CreateContentFromBlueprint(blueprint, "My test content");
var fromBlueprint = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Key);
Assert.IsNotNull(fromBlueprint);
fromBlueprint.Name = "My test content";
ContentService.Save(fromBlueprint);
IEnumerable<UsageInformation> result = null;