Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Documentation on how to work with Umbraco Deploy.



Get an overview of the changes and fixes in each version of Umbraco Deploy.
EditorSizemediumHow to upgrade Umbraco Deploy

#!/bin/sh
echo > src/UmbracoProject/umbraco/Deploy/deploy-on-startWith the Deploy Dashboard, we have made it possible to get an overview of your Umbraco Deploy installation and perform Deploy operations.








# Navigate to the repository folder
cd mySite
# Check status of the repository for pending changes
git status
# Add pending changes
git add -A
# Commit staged files
git commit -m "Adding updated schema changed"
# Push to the environment
git push
# If the push is rejected you will need to pull first
git pull
# Try to push again if there were no conflicts
git pushusing Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Deploy.Core;
using Umbraco.Deploy.Core.OperationStatus;
using Umbraco.Deploy.Infrastructure.Extensions;
internal sealed class DeployImportOnStartupComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.DeployArtifactImportOnStartupProviders()
.Append<PhysicalDirectoryArtifactImportOnStartupProvider>();
private sealed class PhysicalDirectoryArtifactImportOnStartupProvider : IArtifactImportOnStartupProvider
{
private readonly IArtifactImportExportService _artifactImportExportService;
private readonly ILogger _logger;
private readonly string _artifactsPath;
public PhysicalDirectoryArtifactImportOnStartupProvider(IArtifactImportExportService artifactImportExportService, ILogger<PhysicalDirectoryArtifactImportOnStartupProvider> logger, IHostEnvironment hostEnvironment)
{
_artifactImportExportService = artifactImportExportService;
_logger = logger;
_artifactsPath = hostEnvironment.MapPathContentRoot("~/umbraco/Deploy/ImportOnStartup");
}
public Task<bool> CanImportAsync(CancellationToken cancellationToken = default)
=> Task.FromResult(Directory.Exists(_artifactsPath));
public async Task<Attempt<ImportArtifactsOperationStatus>> ImportAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("Importing Umbraco content and/or schema import at startup from directory {FilePath}.", _artifactsPath);
Attempt<ImportArtifactsOperationStatus> attempt = await _artifactImportExportService.ImportArtifactsAsync(_artifactsPath, default, null, cancellationToken);
_logger.LogInformation("Imported Umbraco content and/or schema import at startup from directory {FilePath} with status: {OperationStatus}.", _artifactsPath, attempt.Result);
if (attempt.Success)
{
Directory.Delete(_artifactsPath, true);
_logger.LogInformation("Deleted physical directory after successful import on startup {FilePath}.", _artifactsPath);
}
return attempt;
}
}
}
SaveContentTypeContentTypeConnectorBase// Before
"RelationTypes": [
{
"Alias": "relateParentDocumentOnDelete",
"Mode": "Weak",
},
{
"Alias": "relateShopItemOnCreate",
"Mode": "Exclude",
}
],
// After
"RelationTypes": {
"relateParentDocumentOnDelete": "Weak",
"relateShopItemOnCreate": "Exclude"
},// Before
"ValueConnectors": [
{
"Alias": "nuPickers.DotNetCheckBoxPicker",
"TypeName": "Umbraco.Deploy.Contrib.Connectors.ValueConnectors.NuPickersValueConnector,Umbraco.Deploy.Contrib.Connectors",
}
],
// After
"ValueConnectors": {
"nuPickers.DotNetCheckBoxPicker": "Umbraco.Deploy.Contrib.Connectors.ValueConnectors.NuPickersValueConnector, Umbraco.Deploy.Contrib.Connectors"
},trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
vsSolutionName: DeployOnPremSite
vsProjectName: DeployOnPremSite
umbracoDeployTriggerDeploy: $(Build.SourcesDirectory)\$(vsSolutionName)\$(vsProjectName)\TriggerDeploy.ps1
umbracoDeployReason: AzureDeployment
deployApiSecret: <set in Azure Pipeline secret>
azureSubscription: <set in Azure Pipeline variable>
webAppName: <set in Azure Pipeline variable>
resourceGroupName: <set in Azure Pipeline variable>
deploySlotName: <set in Azure Pipeline variable>
deployBaseUrl: <set in Azure Pipeline variable>
steps:
#1 NuGet Tool Install
- task: NuGetToolInstaller@1
displayName: Install NuGet
#2 NuGet Restore
- task: NuGetCommand@2
displayName: Restore NuGet packages
inputs:
restoreSolution: '$(solution)'
#3 Build the VS Solution and publish the output to a zip file
- task: VSBuild@1
displayName: Build solution
inputs:
solution: '$(solution)'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
#4 Deploy to an Azure web app slot
- task: AzureRmWebAppDeployment@4
displayName: Deploy to web app
inputs:
ConnectionType: 'AzureRM'
azureSubscription: '$(azureSubscription)'
appType: 'webApp'
WebAppName: '$(webAppName)'
deployToSlotOrASE: true
ResourceGroupName: '$(resourceGroupName)'
SlotName: '$(deploySlotName)'
packageForLinux: '$(build.artifactStagingDirectory)\WebApp.zip'
#5 Trigger the Umbraco Deploy extraction
- task: PowerShell@2
displayName: Run PowerShell script
inputs:
filePath: '$(umbracoDeployTriggerDeploy)'
arguments: '-InformationAction:Continue -Action TriggerWithStatus -ApiSecret $(deployApiSecret) -BaseUrl $(deployBaseUrl) -Reason $(umbracoDeployReason) -Verbose'



The troubleshooting section for Umbraco Deploy





Steps and examples on how to setup a build and deployment pipeline for Umbraco Deploy using GitHub Actions.
How to import and export content and schema between Umbraco environments and projects













# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy ASP.Net Core app to Azure Web App - Jonathan-deploy-v10
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout the git repository
uses: actions/checkout@v3
- name: Setup dotnet 6
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: Build with dotnet
working-directory: 'Jonathan-Deploy-V10'
run: dotnet build --configuration Release
- name: Publish with dotnet
working-directory: 'Jonathan-Deploy-V10'
run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: .net-app
path: ${{env.DOTNET_ROOT}}/myapp
deploy:
runs-on: windows-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
env:
deployBaseUrl: https://jonathan-testing-deploy-v10.testsite.net
umbracoDeployReason: DeployingMySite
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: .net-app
- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'Jonathan-Deploy-V10'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_ABC78A5A9E9FG07F87E8R5G9H9J0J7J8 }}
package: .
- name: Run Deploy Powershell - triggers deployment on remote env
shell: powershell
run: .\TriggerDeploy.ps1 -InformationAction:Continue -Action TriggerWithStatus -ApiSecret ${{ secrets.deployApiSecret }} -BaseUrl ${{ env.deployBaseUrl }} -Reason ${{ env.umbracoDeployReason }} -Verbose <ItemGroup>
<Content Include="umbraco/Licenses/umbracoDeploy.lic" CopyToOutputDirectory="Always"/>
<Content Include="TriggerDeploy.ps1" CopyToOutputDirectory="Always"/>
</ItemGroup>{
"Umbraco": {
"CMS": {
"Unattended": {
"InstallUnattended": true
}
}
}
}using System.IO.Compression;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Deploy;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Deploy.Core;
using Umbraco.Deploy.Core.Connectors.ServiceConnectors;
using Umbraco.Deploy.Infrastructure;
using Umbraco.Deploy.Infrastructure.Extensions;
internal class ArtifactImportExportComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.AddNotificationAsyncHandler<UmbracoApplicationStartedNotification, ArtifactImportExportStartedAsyncHandler>();
private sealed class ArtifactImportExportStartedAsyncHandler : INotificationAsyncHandler<UmbracoApplicationStartedNotification>
{
private readonly IHostEnvironment _hostEnvironment;
private readonly IArtifactImportExportService _diskImportExportService;
private readonly IServiceConnectorFactory _serviceConnectorFactory;
private readonly IFileTypeCollection _fileTypeCollection;
public ArtifactImportExportStartedAsyncHandler(IHostEnvironment hostEnvironment, IArtifactImportExportService diskImportExportService, IServiceConnectorFactory serviceConnectorFactory, IFileTypeCollection fileTypeCollection)
{
_hostEnvironment = hostEnvironment;
_diskImportExportService = diskImportExportService;
_serviceConnectorFactory = serviceConnectorFactory;
_fileTypeCollection = fileTypeCollection;
}
public async Task HandleAsync(UmbracoApplicationStartedNotification notification, CancellationToken cancellationToken)
{
var deployPath = _hostEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/Deploy");
await ImportAsync(Path.Combine(deployPath, "import.zip"));
Directory.CreateDirectory(deployPath);
await ExportAsync(Path.Combine(deployPath, $"export-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.zip"));
}
private async Task ImportAsync(string zipFilePath)
{
if (File.Exists(zipFilePath))
{
using ZipArchive zipArchive = ZipFile.OpenRead(zipFilePath);
await _diskImportExportService.ImportArtifactsAsync(zipArchive);
}
}
private async Task ExportAsync(string zipFilePath)
{
using ZipArchive zipArchive = ZipFile.Open(zipFilePath, ZipArchiveMode.Create);
IEnumerable<Udi> udis = DeployEntityTypes.GetEntityTypes(_fileTypeCollection, DeployEntityTypeCategories.ContentAndSchema).Select(Udi.Create);
var contextCache = new DictionaryCache();
string[] dependencyEntityTypes = DeployEntityTypes.GetEntityTypes(_fileTypeCollection, DeployEntityTypeCategories.All);
await _diskImportExportService.ExportArtifactsAsync(_serviceConnectorFactory, udis, Constants.DeploySelector.ThisAndDescendants, contextCache, zipArchive, dependencyEntityTypes: dependencyEntityTypes);
}
}
}using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
namespace TestSite.Business.NotificationHandlers;
public class ContentCacheRefresherNotificationHandler : INotificationHandler<ContentCacheRefresherNotification>
{
private readonly ILogger<ContentCacheRefresherNotificationHandler> _logger;
private readonly IContentService _contentService;
public ContentCacheRefresherNotificationHandler(
ILogger<ContentCacheRefresherNotificationHandler> logger,
IContentService contentService)
{
_logger = logger;
_contentService = contentService;
}
public void Handle(ContentCacheRefresherNotification notification)
{
// We only want to handle the RefreshByPayload message type.
if (notification.MessageType != MessageType.RefreshByPayload)
{
return;
}
// Cast the payload to the expected type.
var payload = (ContentCacheRefresher.JsonPayload[])notification.MessageObject;
// Handle each content item in the payload.
foreach (ContentCacheRefresher.JsonPayload item in payload)
{
// Retrieve the content item.
var contentItemId = item.Id;
IContent? contentItem = _contentService.GetById(contentItemId);
if (contentItem is null)
{
_logger.LogWarning(
"ContentCacheRefresherNotification handled for type {MessageType} but content item with Id {Id} could not be found.",
notification.MessageType,
contentItemId);
return;
}
// Do something with the content item. Here we'll just log some details.
_logger.LogInformation(
"ContentCacheRefresherNotification handled for type {MessageType} and id {Id}. " +
"Key: {Key}, Name: {Name}",
notification.MessageType,
contentItemId,
contentItem.Key,
contentItem.Name);
}
}
}using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
namespace TestSite.Business.NotificationHandlers;
public class DictionaryCacheRefresherNotificationHandler : INotificationHandler<DictionaryCacheRefresherNotification>
{
private readonly ILogger<DictionaryCacheRefresherNotificationHandler> _logger;
private readonly ILocalizationService _localizationService;
public DictionaryCacheRefresherNotificationHandler(
ILogger<DictionaryCacheRefresherNotificationHandler> logger,
ILocalizationService localizationService)
{
_logger = logger;
_localizationService = localizationService;
}
public void Handle(DictionaryCacheRefresherNotification notification)
{
// We only want to handle the RefreshById message type.
if (notification.MessageType != MessageType.RefreshById)
{
return;
}
// Retrieve the dictionary item.
var dictionaryItemId = (int)notification.MessageObject;
IDictionaryItem? dictionaryItem = _localizationService.GetDictionaryItemById(dictionaryItemId);
if (dictionaryItem is null)
{
_logger.LogWarning(
"DictionaryCacheRefresherNotification handled for type {MessageType} but dictionary item with Id {Id} could not be found.",
notification.MessageType,
dictionaryItemId);
return;
}
// Do something with the dictionary item. Here we'll just log some details.
_logger.LogInformation(
"DictionaryCacheRefresherNotification handled for type {MessageType} and id {Id}. " +
"Key: {Key}, Default Value: {DefaultValue}",
notification.MessageType,
dictionaryItemId,
dictionaryItem.ItemKey,
dictionaryItem.GetDefaultValue());
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Notifications;
using TestSite.Business.NotificationHandlers;
namespace TestSite.Business;
public class SiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<ContentCacheRefresherNotification, ContentCacheRefresherNotificationHandler>();
builder.AddNotificationHandler<DictionaryCacheRefresherNotification, DictionaryCacheRefresherNotificationHandler>();
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Contrib.Migrators.Legacy;
internal class LegacyImportComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DeployArtifactTypeResolvers()
.AddLegacyTypeResolver();
builder.DeployArtifactMigrators()
.AddLegacyMigrators()
.Append<ElementTypeArtifactMigrator>();
builder.DeployPropertyTypeMigrators()
.AddLegacyMigrators(); // Available from Deploy Contrib 13.3.0 and 14.2.0+
}
private class ElementTypeArtifactMigrator : ElementTypeArtifactMigratorBase
{
public ElementTypeArtifactMigrator()
: base("testElement")
{ }
}
} Config/UmbracoDeploy.config
Config/UmbracoDeploy.Settings.config<?xml version="1.0"?>
<configSections>
<sectionGroup name="umbraco.deploy">
<section name="environments" type="Umbraco.Deploy.Configuration.DeployEnvironmentsSection, Umbraco.Deploy" requirePermission="false" />
<section name="settings" type="Umbraco.Deploy.Configuration.DeploySettingsSection, Umbraco.Deploy" requirePermission="false" />
</sectionGroup>
</configSections>
<umbraco.deploy>
<environments configSource="config\UmbracoDeploy.config" />
<settings configSource="config\UmbracoDeploy.Settings.config" />
</umbraco.deploy>
</configuration>Umbraco.ContentPickerusing System;
using System.Security.Cryptography;
byte[] secret = new byte[64];
RandomNumberGenerator.Create().GetBytes(secret);
string apiSecret = Convert.ToBase64String(secret);
Console.Write(apiSecret);$secret = [byte[]]::new(64); [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($secret); return [System.Convert]::ToBase64String($secret)using System;
using System.Security.Cryptography;
byte[] secret = new byte[32];
RandomNumberGenerator.Create().GetBytes(secret);
var apiKey = new StringBuilder(secret.Length * 2);
for (int i = 0; i < secret.Length; i++)
{
apiKey.AppendFormat("{0:X2}", secret[i]);
}
Console.Write(apiKey.ToString());$secret = [byte[]]::new(32); [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($secret); return -join ($secret | %{ '{0:X2}' -f $_ })"Umbraco_Deploy_OnPrem": "YOUR_LICENSE_KEY"**/media/*
# Umbraco deploy specific
**/umbraco/Deploy/deploy*{
"Umbraco": {
"Deploy": {
"Settings": {
"ApiSecret": "<your API secret here>"
},
"Project": {
"CurrentWorkspaceName": "Live",
"Workspaces": [
{
"Id": "efef5e89-a19b-434b-b68a-26e022a0ad52",
"Name": "Live",
"Type": "live",
"Url" :"https://localhost:44307"
}
]
}
}
}
}[guid]::NewGuid().ToString() "Umbraco": {
"CMS": {
...
},
"Licenses": {
"Umbraco.Deploy.OnPrem": "<your license key>"
},
"Deploy": {
...
}
{
"Umbraco": {
"Deploy": {
"Settings": {
"ApiSecret": "<your API secret here>",
}
}
}
}{
"Umbraco": {
"Deploy": {
"Settings": {
"ApiKey": "<your API key here>",
}
}
}
}How to import content and schema while migrating them into newer alternatives
gridRow_ (this can be customized by overriding MigrateGridLayout());using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Deploy.Infrastructure.Migrators;
using Umbraco.Extensions;
internal class ArtifactMigratorsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DeployArtifactMigrators()
.Append<ReplaceNestedContentDataTypeArtifactMigrator>()
.Append<ReplaceMediaPickerDataTypeArtifactMigrator>();
builder.DeployPropertyTypeMigrators()
.Append<NestedContentPropertyTypeMigrator>()
.Append<MediaPickerPropertyTypeMigrator>();
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Infrastructure.Migrators;
internal sealed class DeployMigratorsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DeployArtifactMigrators()
.Append<ReplaceGridDataTypeArtifactMigrator>();
builder.DeployPropertyTypeMigrators()
.Append<GridPropertyTypeMigrator>();
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Infrastructure.Migrators;
internal sealed class DeployMigratorsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DeployArtifactMigrators()
.Append<ReplaceDocTypeGridEditorDataTypeArtifactMigrator>();
builder.DeployPropertyTypeMigrators()
.Append<DocTypeGridEditorPropertyTypeMigrator>();
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Infrastructure.Migrators;
internal sealed class DeployMigratorsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DeployArtifactMigrators()
.Append<ReplaceMatryoshkaArtifactMigrator>();
}
}using System.Globalization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Deploy.Infrastructure.Migrators;
public class ReplaceNestedContentDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase<NestedContentConfiguration, BlockListConfiguration>
{
private readonly IContentTypeService _contentTypeService;
public ReplaceNestedContentDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer, IContentTypeService contentTypeService)
: base(Constants.PropertyEditors.Aliases.NestedContent, Constants.PropertyEditors.Aliases.BlockList, propertyEditors, configurationEditorJsonSerializer)
=> _contentTypeService = contentTypeService;
protected override BlockListConfiguration? MigrateConfiguration(NestedContentConfiguration configuration)
{
var blockListConfiguration = new BlockListConfiguration()
{
UseInlineEditingAsDefault = true // Similar to how Nested Content looks/works
};
if (configuration.MinItems > 0)
{
blockListConfiguration.ValidationLimit.Min = configuration.MinItems;
}
if (configuration.MaxItems > 0)
{
blockListConfiguration.ValidationLimit.Max = configuration.MaxItems;
}
if (configuration.ContentTypes is not null)
{
var blocks = new List<BlockListConfiguration.BlockConfiguration>();
foreach (NestedContentConfiguration.ContentType nestedContentType in configuration.ContentTypes)
{
if (nestedContentType.Alias is not null &&
GetContentTypeKey(nestedContentType.Alias) is Guid contentTypeKey)
{
blocks.Add(new BlockListConfiguration.BlockConfiguration()
{
Label = nestedContentType.Template,
ContentElementTypeKey = contentTypeKey
});
}
}
blockListConfiguration.Blocks = blocks.ToArray();
}
if (blockListConfiguration.ValidationLimit.Min == 1 &&
blockListConfiguration.ValidationLimit.Max == 1 &&
blockListConfiguration.Blocks.Length == 1)
{
blockListConfiguration.UseSingleBlockMode = true;
}
return blockListConfiguration;
}
protected virtual Guid? GetContentTypeKey(string alias)
{
if (_contentTypeService.Get(alias) is IContentType contentTypeByAlias)
{
return contentTypeByAlias.Key;
}
// New content types are initially saved by Deploy with a custom postfix (to avoid duplicate aliases), so try to get the first matching item
string aliasPrefix = alias + "__";
foreach (IContentType contentType in _contentTypeService.GetAll())
{
if (contentType.Alias.StartsWith(aliasPrefix) &&
int.TryParse(contentType.Alias[aliasPrefix.Length..], NumberStyles.HexNumber, null, out _))
{
return contentType.Key;
}
}
return null;
}
}using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Deploy;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Services;
using Umbraco.Deploy.Core;
using Umbraco.Deploy.Core.Migrators;
using Umbraco.Deploy.Infrastructure.Extensions;
public class NestedContentPropertyTypeMigrator : PropertyTypeMigratorBase
{
private readonly ILogger<NestedContentPropertyTypeMigrator> _logger;
private readonly IContentTypeService _contentTypeService;
public NestedContentPropertyTypeMigrator(ILogger<NestedContentPropertyTypeMigrator> logger, IContentTypeService contentTypeService)
: base(Constants.PropertyEditors.Aliases.NestedContent, Constants.PropertyEditors.Aliases.BlockList)
{
_logger = logger;
_contentTypeService = contentTypeService;
}
public override object? Migrate(IPropertyType propertyType, object? value, IDictionary<string, string> propertyEditorAliases, IContextCache contextCache)
{
if (value is not string stringValue || !stringValue.TryParseJson(out NestedContentItem[]? nestedContentItems) || nestedContentItems is null)
{
if (value is not null)
{
_logger.LogWarning("Skipping migration of Nested Content items ({PropertyTypeAlias}), because value could not be parsed: {Value}.", propertyType.Alias, value);
}
return null;
}
var layoutItems = new List<BlockListLayoutItem>();
var contentData = new List<BlockItemData>();
foreach (NestedContentItem nestedContentItem in nestedContentItems)
{
IContentType? contentType = contextCache.GetContentTypeByAlias(_contentTypeService, nestedContentItem.ContentTypeAlias);
if (contentType is null)
{
_logger.LogWarning("Skipping migration of Nested Content item ({Id}), because content type does not exist: {ContentTypeAlias}.", nestedContentItem.Id, nestedContentItem.ContentTypeAlias);
continue;
}
var udi = new GuidUdi(Constants.UdiEntityType.Element, nestedContentItem.Id);
layoutItems.Add(new BlockListLayoutItem()
{
ContentUdi = udi
});
contentData.Add(new BlockItemData()
{
Udi = udi,
ContentTypeKey = contentType.Key,
RawPropertyValues = nestedContentItem.RawPropertyValues
});
}
var blockValue = new BlockValue()
{
Layout = new Dictionary<string, JToken>()
{
{ Constants.PropertyEditors.Aliases.BlockList, JToken.FromObject(layoutItems) }
},
ContentData = contentData
};
return JsonConvert.SerializeObject(blockValue, Formatting.None);
}
internal class NestedContentItem
{
[JsonProperty("key")]
public Guid Id { get; set; } = Guid.NewGuid(); // Ensure a unique key is set, even if the JSON doesn't have one
[JsonProperty("name")]
public string? Name { get; set; }
[JsonIgnore]
public object? PropType { get; set; } // Ensure this property is ignored
[JsonProperty("ncContentTypeAlias")]
public string ContentTypeAlias { get; set; } = null!;
[JsonExtensionData]
public Dictionary<string, object?> RawPropertyValues { get; set; } = null!;
}
}headline - the default 'Textstring', falling back to the first Umbraco.TextBox editor.Learn about the different settings and configurations available in Umbraco Deploy.
{
...
"Umbraco": {
"CMS": {
...
},
"Deploy": {
...
}
}
}Restore - None, ScheduledPublishing, Examine, DocumentCache, Signatures, AllFileAction - None will leave the file on disk (and potentially import it again on the next start-up), Archive renames the file to end with .imported and Delete (the default) will remove the file on successful import{
...
"Umbraco": {
"Deploy": {
"Settings": {
"ApiKey": "<your API key here>",
"ApiSecret": "<your API secret here>",
"Edition": "Default",
"ExcludedEntityTypes": [],
"RelationTypes" : [],
"ValueConnectors": [],
"SessionTimeout": "0.0:20:00",
"SourceDeployTimeout": "0.0:20:00",
"DatabaseCommandTimeout": "0.0:20:00",
"EnableSignatureCacheReads": true,
"HttpClientTimeout": "0.0:20:00",
"DiskOperationsTimeout": "0.0:05:00",
"SourceDeployBatchSize": null,
"PackageBatchSize": null,
"AllowIgnoreDependenciesOperations": "None",
"IgnoreBrokenDependenciesBehavior": "Restore",
"AcceptInvalidCertificates": false,
"TransferFormsAsContent": true,
"TransferDictionaryAsContent": false,
"IgnoreMissingLanguagesForDictionaryItems": false,
"SetEmptyDictionaryItemsOnTransfer": true,
"AllowMembersDeploymentOperations": "None",
"TransferMemberGroupsAsContent": false,
"ExportMemberGroups": true,
"ReloadMemoryCacheFollowingDiskReadOperation": false,
"AllowDomainsDeploymentOperations": "None",
"AllowWebhooksDeploymentOperations": "None",
"TrashedContentDeploymentOperations": "Import",
"PostDeploySchemaOperation": "None",
"PreferLocalDbConnectionString": false,
"MediaFileChecksumCalculationMethod": "PartialFileContents",
"NumberOfSignaturesToUseAllRelationCache": 100,
"ContinueOnMediaFilePathTooLongException": false,
"SuppressCacheRefresherNotifications": false,
"ResolveUserInTargetEnvironment": false,
"Suspensions": {
"DiskRead": "All",
"PartialRestore": "All",
"Restore": "All",
"Deploy": "All",
"Import": "All",
"Export": "All"
},
"HideConfigurationDetails": false,
"HideVersionDetails": false,
"ValidateDependenciesOnImport": true
},
"ImportOnStartup": {
"Enabled": true,
"Files": ["~/umbraco/Deploy/import-on-startup.zip"],
"FileAction": "Delete",
"WarningsAsErrors": false,
"EntityTypes": [],
"Cultures": [],
"Username": null
}
}
}
}"ExcludedEntityTypes": ["media-file"],"RelationTypes": {
"relateParentDocumentOnDelete": "Weak",
"relateShopItemOnCreate": "Exclude"
},"ValueConnectors": {
"nuPickers.DotNetCheckBoxPicker": "Umbraco.Deploy.Contrib.Connectors.ValueConnectors.NuPickersValueConnector, Umbraco.Deploy.Contrib.Connectors"
},"IgnoreBrokenDependenciesBehavior": "Restore","IgnoreBrokenDependencies": true,
"IgnoreBrokenDependenciesBehavior": "Restore","ReloadMemoryCacheFollowingDiskReadOperation": true,"AllowDomainsDeploymentOperations": "None|Culture|AbsolutePath|Hostname|All","AllowPublicAccessDeploymentOperations": "None|AddOrUpdate|Remove|All","AllowWebhooksDeploymentOperations": "None|All","TrashedContentDeploymentOperations": "None|Export|Import|All""PreferLocalDbConnectionString": true"Suspensions": {
"PartialRestore": "ScheduledPublishing, DocumentCache"
},"Suspensions": "ScheduledPublishing, DocumentCache, Signatures",using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Core.Configuration.DeployConfiguration;
internal sealed class DeploySuspensionsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.Services.Configure<DeploySettings>(options =>
{
// No suspensions during import
options.Suspensions.Import = SuspensionOptions.None;
// No Examine suspensions on all operations (using bitwise negation operator)
options.Suspensions &= ~SuspensionOptions.Examine;
// Add scheduled publishing suspension to all operations
options.Suspensions |= SuspensionOptions.ScheduledPublishing;
});
}using Umbraco.Cms.Core.Composing;
using Umbraco.Deploy.Infrastructure.Extensions;
internal sealed class DeployWebhookEventsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.WebhookEvents().AddDeploy(deployBuilder => deployBuilder.AddTask());
}{
"Id": "dc93c9ec-fbc9-4be4-91ad-f195e0c12f43",
"WorkItemType": "DiskReadWorkItem",
"Environment": {
"Name": "Live",
"Uri": "https://localhost:44309"
},
"Result": "Completed",
"Duration": "00:00:00.8125947",
"Udis": [
"umb://document-type/a0b5933193b8460c8eda57408f088a2e"
]
}How to extend Umbraco Deploy to synchronize custom data
public class Example
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}public class ExampleArtifact : DeployArtifactBase<GuidUdi>
{
public ExampleArtifact(GuidUdi udi, IEnumerable<ArtifactDependency> dependencies = null)
: base(udi, dependencies)
{ }
public string Description { get; set; }
}[JsonConverter(typeof(RoundingDecimalJsonConverter), 2)]
public decimal Amount { get; set; }[UdiDefinition("mypackage-example", UdiType.GuidUdi)]
public class ExampleServiceConnector : ServiceConnectorBase<ExampleArtifact, GuidUdi, ArtifactDeployState<ExampleArtifact, Example>>
{
private readonly IExampleDataService _exampleDataService;
public ExampleServiceConnector(IExampleDataService exampleDataService) => _exampleDataService = exampleDataService;
public override ExampleArtifact GetArtifact(object o)
{
var entity = o as Example;
if (entity == null)
{
throw new InvalidEntityTypeException($"Unexpected entity type \"{o.GetType().FullName}\".");
}
return GetArtifact(entity.GetUdi(), entity);
}
public override ExampleArtifact GetArtifact(GuidUdi udi)
{
EnsureType(udi);
var entity = _exampleDataService.GetExampleById(udi.Guid);
return GetArtifact(udi, entity);
}
private ExampleArtifact GetArtifact(GuidUdi udi, Example entity)
{
if (entity == null)
{
return null;
}
var dependencies = new ArtifactDependencyCollection();
var artifact = Map(udi, entity, dependencies);
artifact.Dependencies = dependencies;
return artifact;
}
private ExampleArtifact Map(GuidUdi udi, Example entity, ICollection<ArtifactDependency> dependencies)
{
var artifact = new ExampleArtifact(udi);
artifact.Description = example.Description;
return artifact;
}
private string[] ValidOpenSelectors => new[]
{
DeploySelector.This,
DeploySelector.ThisAndDescendants,
DeploySelector.DescendantsOfThis
};
private const string OpenUdiName = "All Examples";
public override void Explode(UdiRange range, List<Udi> udis)
{
EnsureType(range.Udi);
if (range.Udi.IsRoot)
{
EnsureSelector(range, ValidOpenSelectors);
udis.AddRange(_exampleDataService.GetExamples().Select(e => e.GetUdi()));
}
else
{
var entity = _exampleDataService.GetExampleById(((GuidUdi)range.Udi).Guid);
if (entity == null)
{
return;
}
EnsureSelector(range.Selector, DeploySelector.This);
udis.Add(entity.GetUdi());
}
}
public override NamedUdiRange GetRange(string entityType, string sid, string selector)
{
if (sid == "-1")
{
EnsureSelector(selector, ValidOpenSelectors);
return new NamedUdiRange(Udi.Create("mypackage-example"), OpenUdiName, selector);
}
if (!Guid.TryParse(sid, out Guid id))
{
throw new ArgumentException("Invalid identifier.", nameof(sid));
}
var entity = _exampleDataService.GetExampleById(id);
if (entity == null)
{
throw new ArgumentException("Could not find an entity with the specified identifier.", nameof(sid));
}
return GetRange(entity, selector);
}
public override NamedUdiRange GetRange(GuidUdi udi, string selector)
{
EnsureType(udi);
if (udi.IsRoot)
{
EnsureSelector(selector, ValidOpenSelectors);
return new NamedUdiRange(udi, OpenUdiName, selector);
}
var entity = _exampleDataService.GetExampleById(udi.Guid);
if (entity == null)
{
throw new ArgumentException("Could not find an entity with the specified identifier.", nameof(udi));
}
return GetRange(entity, selector);
}
private static NamedUdiRange GetRange(Example e, string selector) => new NamedUdiRange(e.GetUdi(), e.Name, selector);
public override ArtifactDeployState<ExampleArtifact, Example> ProcessInit(ExampleArtifact art, IDeployContext context)
{
EnsureType(art.Udi);
var entity = _exampleDataService.GetExampleById(art.Udi.Guid);
return ArtifactDeployState.Create(art, entity, this, 1);
}
public override void Process(ArtifactDeployState<ExampleArtifact, Example> state, IDeployContext context, int pass)
{
switch (pass)
{
case 1:
Pass1(state, context);
state.NextPass = 2;
break;
default:
state.NextPass = -1; // exit
break;
}
}
private void Pass1(ArtifactDeployState<ExampleArtifact, Example> state, IDeployContext context)
{
var artifact = state.Artifact;
artifact.Udi.EnsureType("mypackage-example");
var isNew = state.Entity == null;
var entity = state.Entity ?? new Example { Id = artifact.Udi.Guid };
entity.Name = artifact.Name;
entity.Description = artifact.Description;
if (isNew)
{
_exampleDataService.AddExample(entity);
}
else
{
_exampleDataService.UpdateExample(entity);
}
}
}public static GuidUdi GetUdi(this Example entity)
{
if (entity == null) throw new ArgumentNullException("entity");
return new GuidUdi("mypackage-example", entity.Id).EnsureClosed();
}private PersonArtifact Map(GuidUdi udi, Person person, ICollection<ArtifactDependency> dependencies)
{
var artifact = new PersonArtifact(udi)
{
Alias = person.Name,
Name = person.Name,
DepartmentId = person.Department.Id,
};
// Department node must exist to deploy the person.
dependencies.Add(new ArtifactDependency(person.Department.GetUdi(), true, ArtifactDependencyMode.Exist));
return artifact;
}using Umbraco.Cms.Core.Deploy;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core;
using Microsoft.Extensions.Logging;
using Umbraco.Deploy.Core.Connectors.ValueConnectors;
namespace MyExtensions;
public class MyMediaPropertyValueConnector : ValueConnectorBase
{
private readonly IEntityService _entityService;
private readonly ILogger<MyMediaPropertyValueConnector> _logger;
public MyMediaPropertyValueConnector(IEntityService entityService, ILogger<MyMediaPropertyValueConnector> logger)
{
_entityService = entityService;
_logger = logger;
}
public override IEnumerable<string> PropertyEditorAliases => new[] { "MyMediaPropertyEditor" };
public override string? ToArtifact(object? value, IPropertyType propertyType, ICollection<ArtifactDependency> dependencies, IContextCache contextCache)
{
var svalue = value as string;
if (string.IsNullOrWhiteSpace(svalue))
{
return null;
}
if (!int.TryParse(svalue, out var intvalue))
{
return null;
}
Attempt<Guid> getKeyAttempt = _entityService.GetKey(intvalue, UmbracoObjectTypes.Media);
if (getKeyAttempt.Success)
{
var udi = new GuidUdi(Constants.UdiEntityType.Media, getKeyAttempt.Result);
dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist));
return udi.ToString();
}
else
{
_logger.LogDebug($"Couldn't convert integer value #{intvalue} to UDI");
}
return null;
}
public override object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (!UdiParser.TryParse(value, out GuidUdi? udi) || udi!.Guid == Guid.Empty)
{
return null;
}
Attempt<int> getIdAttempt = _entityService.GetId(udi.Guid, UmbracoObjectTypes.Media);
if (!getIdAttempt.Success)
{
return null;
}
return getIdAttempt.Result.ToString();
}
}{
"gridEditors": [
{
"name": "TestImagePicker",
"alias": "testImagePicker",
"view": "/App_Plugins/TestGridEditor/editor.html",
"icon": "icon-code"
}
]
}<div>
<label>Enter The Integer Id Of An Image:</label>
<input ng-model="control.value" type="number">
</div>using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Deploy;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Deploy.Core;
namespace Umbraco.Deploy.Infrastructure.Connectors.GridCellValueConnectors
{
public class CustomMediaGridCellValueConnector : GridCellValueConnectorBase2
{
public CustomMediaGridCellValueConnector(IEntityService entityService, ILocalLinkParser localLinkParser)
: base(entityService, localLinkParser)
{
}
public override bool IsConnector(string view) => string.Equals(view, "/App_Plugins/TestGridEditor/editor.html", StringComparison.OrdinalIgnoreCase);
public override string? GetValue(GridValue.GridControl control, ICollection<ArtifactDependency> dependencies, IContextCache contextCache)
{
var value = control.Value?.ToString();
if (string.IsNullOrEmpty(value))
{
return null;
}
if (!int.TryParse(value, out var valueAsInt))
{
return null;
}
Udi? mediaUdi = GetUdi(valueAsInt, UmbracoObjectTypes.Media);
if (mediaUdi == null)
{
return null;
}
dependencies.Add(new ArtifactDependency(mediaUdi, false, ArtifactDependencyMode.Exist));
return mediaUdi.ToString();
}
public override void SetValue(GridValue.GridControl control, IContextCache contextCache)
{
var value = control.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return;
}
var guidUdi = UdiParser.Parse(value.ToString() ?? string.Empty) as GuidUdi;
if (guidUdi == null)
{
return;
}
var mediaId = GetNodeId(guidUdi);
if (!mediaId.HasValue)
{
return;
}
control.Value = mediaId.Value;
}
}
}UdiParser.RegisterUdiType("mypackage-example", UdiType.GuidUdi);public class ExampleDataDeployComponent : IComponent
{
private readonly IDiskEntityService _diskEntityService;
private readonly IServiceConnectorFactory _serviceConnectorFactory;
private readonly IExampleDataService _exampleDataService;
public ExampleDataDeployComponent(
IDiskEntityService diskEntityService,
IServiceConnectorFactory serviceConnectorFactory,
IExampleDataService exampleDataService)
{
_diskEntityService = diskEntityService;
_serviceConnectorFactory = serviceConnectorFactory;
_exampleDataService = exampleDataService;
}
public void Initialize()
{
_diskEntityService.RegisterDiskEntityType("mypackage-example");
_exampleDataService.ExampleSaved += ExampleOnSaved;
}
private void ExampleOnSaved(object sender, ExampleEventArgs e)
{
var artifact = GetExampleArtifactFromEvent(e);
_diskEntityService.WriteArtifacts(new[] { artifact });
}
private IArtifact GetExampleArtifactFromEvent(ExampleEventArgs e)
{
var udi = e.Example.GetUdi();
return _serviceConnectorFactory.GetConnector(udi.EntityType).GetArtifact(e.Example);
}
public void Terminate()
{
}
}_diskEntityService.RegisterDiskEntityType(
"mypackage-example",
"Examples",
false,
_exampleDataService.GetAll().Select(x => new DeployDiskRegisteredEntityTypeDetail.InstalledEntityDetail(x.GetUdi(), x.Name, x))));public class ExampleDataDeployComponent : IComponent
{
private readonly ITransferEntityService _transferEntityService;
private readonly IExampleDataService exampleDataService;
public ExampleDataDeployComponent(
ITransferEntityService transferEntityService,
IExampleDataService exampleDataService)
{
_transferEntityService = transferEntityService;
_exampleDataService = exampleDataService;
}
public void Initialize()
{
_transferEntityService.RegisterTransferEntityType(
"mypackage-example",
"Examples",
new DeployRegisteredEntityTypeDetailOptions
{
SupportsQueueForTransfer = true,
SupportsQueueForTransferOfDescendents = true,
SupportsRestore = true,
PermittedToRestore = true,
SupportsPartialRestore = true,
},
false,
"exampleTreeAlias",
(string routePath, HttpContext httpContext) => true,
(string nodeId, HttpContext httpContext) => true,
(string nodeId, HttpContext httpContext, out Guid entityId) => Guid.TryParse(nodeId, out entityId),
new DeployRegisteredEntityTypeDetail.RemoteTreeDetail(FormsTreeHelper.GetExampleTree, "example", "externalExampleTree"),
entitiesGetter: () => _exampleDataService.Get());
}
public void Terminate()
{
}
}_transferEntityService.RegisterTransferEntityType(
...
"teams",
(string routePath, HttpContext httpContext) => routePath.Contains($"/teamEdit/") || routePath.Contains($"/teamsOverview"),
(string nodeId, HttpContext httpContext) => nodeId == "-1" || nodeId.StartsWith("team-"),
(string nodeId, HttpContext httpContext, out Guid entityId) => Guid.TryParse(nodeId.Substring("team-".Length), out entityId);_transferEntityService.RegisterTransferEntityType(
...
"teams",
(string routePath) => routePath.Contains($"/riderEdit/"),
(string nodeId) => nodeId.StartsWith("rider-"),
(string nodeId, HttpContext httpContext, out Guid entityId) => Guid.TryParse(nodeId.Substring("rider-".Length), out entityId);var localizationService = httpContext.RequestServices.GetRequiredService<ILocalizationService>();public static IEnumerable<RemoteTreeNode> GetExampleTree(string parentId, HttpContext httpContext)
{
var exampleDataService = httpContext.RequestServices.GetRequiredService<IExampleDataService>();
var items = exampleDataService.GetItems(parentId);
return items
.Select(x => new RemoteTreeNode
{
Id = x.Id,,
Title = x.Name,
Icon = "icon-box",
ParentId = parentId,
HasChildren = true,
})
.ToList();
}[Tree(DeployConstants.SectionAlias, "externalExampleTree", TreeUse = TreeUse.Dialog)]
public class ExternalDataSourcesTreeController : ExternalTreeControllerBase
{
public ExternalDataSourcesTreeController(
ILocalizedTextService localizedTextService,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
IEventAggregator eventAggregator,
IExtractEnvironmentInfo environmentInfoExtractor,
LinkGenerator linkGenerator,
ILoggerFactory loggerFactory,
IOptions<DeploySettings> deploySettings)
: base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator, environmentInfoExtractor, linkGenerator, loggerFactory, deploySettings, "mypackage-example")
{
}
}(function () {
"use strict";
function MyController($scope, $routeParams, myResource, formHelper, notificationsService, editorState, pluginEntityService) {
var vm = this;
vm.page = {};
vm.entity = {};
...
vm.page.defaultButton = {
alias: "save",
hotKey: "ctrl+s",
hotKeyWhenHidden: true,
labelKey: "buttons_save",
letter: "S",
handler: function () { vm.save(); }
};
vm.page.subButtons = [];
function init() {
...
if (!$routeParams.create) {
myResource.getById($routeParams.id).then(function (entity) {
vm.entity = entity;
// Augment with additional details necessary for identifying the node for a deployment.
vm.entity.metaData = { treeAlias: $routeParams.tree };
...
});
}
pluginEntityService.addInstantDeployButton(vm.page.subButtons);
}
...
init();
}
angular.module("umbraco").controller("MyController", MyController);
})();public class ExampleDataDeployComponent : IComponent
{
...
private readonly ISignatureService _signatureService;
public ExampleDataDeployComponent(
...
ISignatureService signatureService)
{
_signatureService = signatureService;
}
public void Initialize()
{
...
_signatureService.RegisterHandler<ExampleDataService, ExampleEventArgs>(nameof(IExampleDataService.ExampleSaved), (refresher, args) => refresher.SetSignature(GetExampleArtifactFromEvent(args)));
}
...
}