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.







ContentTypeConnectorBase// 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"
},Get an overview of the changes and fixes in each version of Umbraco Deploy.
Steps and examples on how Umbraco Deploy can be integrated into an automated build and deployment pipeline


post-merge.sampleIArtifactImportOnStartupProvider and registering it using builder.DeployArtifactImportOnStartupProviders(). The default Umbraco.Deploy.Infrastructure.SettingsArtifactImportOnStartupProvider implementation uses the above settings and inherits from Umbraco.Deploy.Infrastructure.ArtifactImportOnStartupProviderZipArchiveBase (which can be used for your own custom implementation).#!/bin/sh
echo > src/UmbracoProject/umbraco/Deploy/deploy-on-startusing 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;
}
}
}How to restore content in Umbraco Deploy using the deployment dashboard
How deleting meta data and files work in Umbraco Deploy
# 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 push






Steps and examples on how to setup a build and deployment pipeline for Umbraco Deploy using GitHub Actions.
using 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>",
}
}
}
}# 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 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.ContentPickertrigger:
- 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'





How to import and export content and schema between Umbraco environments and projects
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);
}
}
}






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!;
}
}headlineUmbraco.TextBoxLearn 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)));
}
...
}