diff --git a/uSync.AutoTemplates/AutoTemplateComposer.cs b/uSync.AutoTemplates/AutoTemplateComposer.cs index c056436ff..1b36d0521 100644 --- a/uSync.AutoTemplates/AutoTemplateComposer.cs +++ b/uSync.AutoTemplates/AutoTemplateComposer.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Core.Composing; +using System.Linq; + +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; namespace uSync.AutoTemplates; @@ -7,6 +9,8 @@ public class AutoTemplateComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - builder.AdduSyncAutoTemplates(); + // only load when the backoffice is enabled. + if (builder.Services.Any(s => s.ServiceType == typeof(IBackOfficeEnabledMarker))) + builder.AdduSyncAutoTemplates(); } } diff --git a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs index 923eafb1d..e9cf81466 100644 --- a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs +++ b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs @@ -137,7 +137,9 @@ public static void UpdateActions(this List actions, Guid k public static bool RequiresSave(this SyncAttempt attempt) => attempt.Success && attempt.Change > Core.ChangeType.NoChange && !attempt.Saved && attempt.Item != null; - + /// + /// return the uSyncAction as an ActionView (used in the controllers) + /// public static uSyncActionView AsActionView(this uSyncAction action) { var msg = string.IsNullOrWhiteSpace(action.Message) is false diff --git a/uSync.BackOffice/HealthChecks/SyncFolderIntegrityChecks.cs b/uSync.BackOffice/HealthChecks/SyncFolderIntegrityChecks.cs index 926db7bdc..866e292b7 100644 --- a/uSync.BackOffice/HealthChecks/SyncFolderIntegrityChecks.cs +++ b/uSync.BackOffice/HealthChecks/SyncFolderIntegrityChecks.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; @@ -20,8 +21,10 @@ namespace uSync.BackOffice.HealthChecks; Group = "uSync")] public class SyncFolderIntegrityChecks : HealthCheck { - private readonly ISyncConfigService _configService; - private readonly ISyncFileService _fileService; + private readonly ISyncConfigService? _configService; + private readonly ISyncFileService? _fileService; + + public SyncFolderIntegrityChecks() { } /// /// Constructor @@ -41,6 +44,9 @@ public override HealthCheckStatus ExecuteAction(HealthCheckAction action) /// public override Task> GetStatusAsync() { + if (_configService is null || _fileService is null) + return Task.FromResult(Enumerable.Empty()); + var items = new List { CheckuSyncFolder(), @@ -52,6 +58,9 @@ public override Task> GetStatusAsync() private HealthCheckStatus CheckuSyncFolder() { + if (_configService is null || _fileService is null) + return new HealthCheckStatus("Unable to check uSync folder integrity"); + var root = _fileService.GetAbsPath(_configService.GetWorkingFolder()); if (_fileService.DirectoryExists(root) is false) @@ -85,6 +94,8 @@ private HealthCheckStatus CheckuSyncFolder() private List CheckFolder(string folder) { + if (_fileService is null) return []; + var _keys = new Dictionary(); var clashes = new List(); @@ -128,6 +139,9 @@ private List CheckFolder(string folder) private HealthCheckStatus CheckConfigFolderValidity() { + if (_configService is null || _fileService is null) + return new HealthCheckStatus("Unable to check uSync folder integrity"); + var root = _fileService.GetAbsPath(_configService.GetWorkingFolder()); if (_fileService.DirectoryExists(root) is false) diff --git a/uSync.BackOffice/Services/ISyncVersionFileService.cs b/uSync.BackOffice/Services/ISyncVersionFileService.cs index b3355932e..901526663 100644 --- a/uSync.BackOffice/Services/ISyncVersionFileService.cs +++ b/uSync.BackOffice/Services/ISyncVersionFileService.cs @@ -2,8 +2,20 @@ namespace uSync.BackOffice.Services; +/// +/// Controls the version file we write to disk on syncs (used to warn if sync is old) +/// public interface ISyncVersionFileService { + /// + /// get the Sync file version information for a folder. + /// Task GetSyncFileInfo(string folder); + + /// + /// write the version information to disk. + /// + /// + /// Task WriteVersionFileAsync(string folder); } \ No newline at end of file diff --git a/uSync.BackOffice/Services/SyncVersionFileService.cs b/uSync.BackOffice/Services/SyncVersionFileService.cs index af146b8e9..fee5c6404 100644 --- a/uSync.BackOffice/Services/SyncVersionFileService.cs +++ b/uSync.BackOffice/Services/SyncVersionFileService.cs @@ -119,9 +119,23 @@ private bool HmacValuesMatch(XElement node) } } +/// +/// results of a check of the version file +/// public class SyncFileVersionCheckResult { + /// + /// the sync on disk is current to the current format we are writing. + /// public bool IsCurrent { get; set; } + + /// + /// the version we are writing to disk + /// public string? FormatVersion { get; set; } + + /// + /// the hmac value for the folders matches. (reserved) + /// public bool HmacMatch { get; set; } } diff --git a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs index 0acd656b1..b734ebab2 100644 --- a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs +++ b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs @@ -1005,10 +1005,9 @@ protected virtual async Task> Export_DoExportAsync(TObject await syncFileService.SaveXElementAsync(attempt.Item, filename); } - if (config.CreateClean && await HasChildrenAsync(item)) - { + if (config.CreateClean) await CreateCleanFileAsync(GetItemKey(item), filename); - } + } else { diff --git a/uSync.BackOffice/uSyncBackOffice.cs b/uSync.BackOffice/uSyncBackOffice.cs index 2ac79b1fe..8f9f519ec 100644 --- a/uSync.BackOffice/uSyncBackOffice.cs +++ b/uSync.BackOffice/uSyncBackOffice.cs @@ -7,6 +7,9 @@ namespace uSync.BackOffice; /// public class uSync { + /// + /// assembly version for uSync + /// public static Version Version => typeof(uSync).Assembly.GetName().Version ?? new Version(15, 0, 0); /// diff --git a/uSync.BackOffice/uSyncBackOfficeComposer.cs b/uSync.BackOffice/uSyncBackOfficeComposer.cs index f661170d3..71512ef93 100644 --- a/uSync.BackOffice/uSyncBackOfficeComposer.cs +++ b/uSync.BackOffice/uSyncBackOfficeComposer.cs @@ -1,7 +1,13 @@  +using Microsoft.Extensions.Logging; + +using System.Linq; + using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using uSync.Core.Extensions; + namespace uSync.BackOffice; /// @@ -12,10 +18,12 @@ public class uSyncBackOfficeComposer : IComposer /// public void Compose(IUmbracoBuilder builder) { - // the composers add uSync, but the extension methods - // will only add the values if uSync hasn't already - // been added, so you can for example add uSync to your - // startup.cs file. and then the composers don't fire - builder.AdduSync(); + if (builder.IsUmbracoBackOfficeEnabled() is true) { + // the composers add uSync, but the extension methods + // will only add the values if uSync hasn't already + // been added, so you can for example add uSync to your + // startup.cs file. and then the composers don't fire + builder.AdduSync(); + } } } diff --git a/uSync.Backoffice.Management.Api/ApiComposer.cs b/uSync.Backoffice.Management.Api/ApiComposer.cs index dccf297a0..bfce92e71 100644 --- a/uSync.Backoffice.Management.Api/ApiComposer.cs +++ b/uSync.Backoffice.Management.Api/ApiComposer.cs @@ -7,6 +7,7 @@ using uSync.Backoffice.Management.Api.Configuration; using uSync.Backoffice.Management.Api.Services; using uSync.BackOffice; +using uSync.Core.Extensions; namespace uSync.Backoffice.Management.Api; @@ -15,10 +16,10 @@ public class ApiComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - // builder.Services.AddSingleton(); + if (builder.IsUmbracoBackOfficeEnabled() is false) + return; builder.AddSyncOpenApi(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); } diff --git a/uSync.Backoffice.Management.Client/uSyncManifestReader.cs b/uSync.Backoffice.Management.Client/uSyncManifestReader.cs index 2a664bb6e..b72b385c7 100644 --- a/uSync.Backoffice.Management.Client/uSyncManifestReader.cs +++ b/uSync.Backoffice.Management.Client/uSyncManifestReader.cs @@ -10,6 +10,7 @@ using uSync.BackOffice.Configuration; using uSync.BackOffice.Extensions; +using uSync.Core.Extensions; namespace uSync.Backoffice.Management.Client; @@ -18,8 +19,12 @@ public class uSyncManifestComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + if (builder.IsUmbracoBackOfficeEnabled()) + { + // only load this when the backoffice is enabled. + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + } } } diff --git a/uSync.Core/Extensions/SyncBuilderExtensions.cs b/uSync.Core/Extensions/SyncBuilderExtensions.cs new file mode 100644 index 000000000..4ac0d055e --- /dev/null +++ b/uSync.Core/Extensions/SyncBuilderExtensions.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.DependencyInjection; + +namespace uSync.Core.Extensions; + +public static class SyncBuilderExtensions +{ + public static bool IsUmbracoBackOfficeEnabled(this IUmbracoBuilder builder) + => builder.Services.Any(s => s.ServiceType == typeof(IBackOfficeEnabledMarker)); +} diff --git a/uSync.Core/Mapping/Mappers/ImagePathMapper.cs b/uSync.Core/Mapping/Mappers/ImagePathMapper.cs index 807fd9c7b..a8730d286 100644 --- a/uSync.Core/Mapping/Mappers/ImagePathMapper.cs +++ b/uSync.Core/Mapping/Mappers/ImagePathMapper.cs @@ -2,8 +2,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Text.RegularExpressions; - using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Media; @@ -11,7 +9,6 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -using uSync.Core.Dependency; using uSync.Core.Extensions; using uSync.Core.Serialization; @@ -28,49 +25,33 @@ namespace uSync.Core.Mapping; /// becomes /// {"src":"/media/2cud1lzo/15656993711_ccd199b83e_k.jpg","crops":null} /// -public class ImagePathMapper : SyncValueMapperBase, ISyncMapper +public class ImagePathMapper : ImagePathMapperBase, ISyncMapper { - private const string _genericMediaPath = "/media"; - - private readonly string _siteRoot; - private string? _mediaFolder; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; private readonly IImageUrlGenerator _imageUrlGenerator; public ImagePathMapper( - IConfiguration configuration, - IOptionsMonitor _globalOptions, IEntityService entityService, ILogger logger, - IImageUrlGenerator imageUrlGenerator) : base(entityService) + IConfiguration configuration, + IOptionsMonitor globalOptions, + IImageUrlGenerator imageUrlGenerator) : base(entityService, logger, configuration, globalOptions) { - _logger = logger; - _configuration = configuration; - - // todo: site root might need us to include extra NuGet. - _siteRoot = ""; - - _mediaFolder = GetMediaFolderSetting(_globalOptions.CurrentValue.UmbracoMediaPath.TrimStart('~')); - _globalOptions.OnChange(x => _mediaFolder = GetMediaFolderSetting(x.UmbracoMediaPath.TrimStart('~'))); - - if (logger.IsEnabled(LogLevel.Debug)) - logger.LogDebug("Media Folders: [{media}]", _mediaFolder ?? "(Blank)"); - _imageUrlGenerator = imageUrlGenerator; } public override string Name => "ImageCropper Mapper"; public override string[] Editors => [ - Constants.PropertyEditors.Aliases.ImageCropper, - Constants.PropertyEditors.Aliases.UploadField + Constants.PropertyEditors.Aliases.ImageCropper ]; public override Task GetExportValueAsync(object value, string editorAlias) { return uSyncTaskHelper.FromResultOf(() => { + if (_logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("Getting export value for ImageCropper with value {Value}", value); + var stringValue = value?.ToString(); if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; @@ -107,76 +88,6 @@ public ImagePathMapper( }); } - private string StripSitePath(string filePath) - { - var path = filePath; - if (_siteRoot.Length > 0 && !string.IsNullOrWhiteSpace(filePath) && filePath.InvariantStartsWith(_siteRoot)) - path = filePath.Substring(_siteRoot.Length); - - return ReplacePath(path, _mediaFolder, _genericMediaPath); - } - - private string PrePendSitePath(string filePath) - { - var path = filePath; - if (_siteRoot.Length > 0 && !string.IsNullOrEmpty(filePath)) - path = $"{_siteRoot}{filePath}"; - - return ReplacePath(path, _genericMediaPath, _mediaFolder); - } - - - /// - /// makes a specific media path generic. - /// - /// - /// sometimes paths may be defined by umbraco settings, (especially blob settings) - /// that mean they are not stored as /media - /// - /// for the sake of generic importing we want the folder stored to be /media. - /// so we re-write the setting on import and export - /// - /// assumes you have a app setting in the web.config - /// - /// /someFolder - /// - /// - /// - private static string ReplacePath(string filePath, string? currentPath, string? targetPath) - { - if (!string.IsNullOrWhiteSpace(targetPath) - && !string.IsNullOrWhiteSpace(currentPath) - && !currentPath.Equals(targetPath)) - { - return Regex.Replace(filePath, $"^{currentPath}", targetPath, RegexOptions.IgnoreCase); - } - - return filePath; - } - - /// - /// Get the media rewrite folder - /// - /// - /// looks in appSettings for uSync:mediaFolder - /// - /// - /// - /// or in uSync8.config for media setting - /// - /// - /// - /// /someFolder - /// - /// - /// - private string GetMediaFolderSetting(string umbracoMediaPath) - { - var folder = this._configuration.GetValue("uSync:MediaFolder", string.Empty); - if (!string.IsNullOrEmpty(folder)) return folder; - - return umbracoMediaPath; - } public override Task GetImportValueAsync(string value, string editorAlias, SyncSerializerOptions options) { @@ -202,49 +113,4 @@ private string GetMediaFolderSetting(string umbracoMediaPath) return json.SerializeJsonNode(true); }); } - - /// - /// Get the actual media file as a dependency. - /// - public override Task> GetDependenciesAsync(object value, string editorAlias, DependencyFlags flags) - { - return uSyncTaskHelper.FromResultOf>(() => - { - - var stringValue = value?.ToString(); - if (string.IsNullOrWhiteSpace(stringValue)) - return []; - - var stringPath = GetImagePath(stringValue).TrimStart('/').ToLower(); - - if (!string.IsNullOrWhiteSpace(stringPath)) - { - return [new uSyncDependency() - { - Name = $"File: {Path.GetFileName(stringPath)}", - Udi = Udi.Create(Constants.UdiEntityType.MediaFile, stringPath), - Flags = flags, - Order = DependencyOrders.OrderFromEntityType(Constants.UdiEntityType.MediaFile), - Level = 0 - }]; - } - - return []; - }); - } - - private string GetImagePath(string stringValue) - { - if (stringValue.TryParseToJsonObject(out var json) is false || json is null) - return StripSitePath(stringValue); - - - if (json.TryGetPropertyValue("src", out var srcNode) is true) - { - var source = srcNode?.GetValue() ?? string.Empty; - if (string.IsNullOrWhiteSpace(source) is false) return source; - } - - return string.Empty; - } } diff --git a/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs b/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs new file mode 100644 index 000000000..b60b55796 --- /dev/null +++ b/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs @@ -0,0 +1,163 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using System.Text.RegularExpressions; + +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +using uSync.Core.Dependency; +using uSync.Core.Extensions; + +namespace uSync.Core.Mapping; + +public abstract class ImagePathMapperBase : SyncValueMapperBase +{ + private readonly IConfiguration _configuration; + protected readonly ILogger _logger; + + private const string _genericMediaPath = "/media"; + private readonly string _siteRoot; + private string? _mediaFolder; + + public ImagePathMapperBase( + IEntityService entityService, + ILogger logger, + IConfiguration configuration, + IOptionsMonitor globalOptions + ) : base(entityService) + { + _configuration = configuration; + _logger = logger; + + // todo: site root might need us to include extra NuGet. + _siteRoot = ""; + + _mediaFolder = GetMediaFolderSetting(globalOptions.CurrentValue.UmbracoMediaPath.TrimStart('~')); + globalOptions.OnChange(x => _mediaFolder = GetMediaFolderSetting(x.UmbracoMediaPath.TrimStart('~'))); + + if (logger.IsEnabled(LogLevel.Debug)) + logger.LogDebug("Media Folders: [{media}]", _mediaFolder ?? "(Blank)"); + + } + + + protected string StripSitePath(string filePath) + { + var path = filePath; + if (_siteRoot.Length > 0 && !string.IsNullOrWhiteSpace(filePath) && filePath.InvariantStartsWith(_siteRoot)) + path = filePath.Substring(_siteRoot.Length); + + return ReplacePath(path, _mediaFolder, _genericMediaPath); + } + + protected string PrePendSitePath(string filePath) + { + var path = filePath; + if (_siteRoot.Length > 0 && !string.IsNullOrEmpty(filePath)) + path = $"{_siteRoot}{filePath}"; + + return ReplacePath(path, _genericMediaPath, _mediaFolder); + } + + + /// + /// makes a specific media path generic. + /// + /// + /// sometimes paths may be defined by umbraco settings, (especially blob settings) + /// that mean they are not stored as /media + /// + /// for the sake of generic importing we want the folder stored to be /media. + /// so we re-write the setting on import and export + /// + /// assumes you have a app setting in the web.config + /// + /// /someFolder + /// + /// + /// + private static string ReplacePath(string filePath, string? currentPath, string? targetPath) + { + if (!string.IsNullOrWhiteSpace(targetPath) + && !string.IsNullOrWhiteSpace(currentPath) + && !currentPath.Equals(targetPath)) + { + return Regex.Replace(filePath, $"^{currentPath}", targetPath, RegexOptions.IgnoreCase); + } + + return filePath; + } + + /// + /// Get the media rewrite folder + /// + /// + /// looks in appSettings for uSync:mediaFolder + /// + /// + /// + /// or in uSync8.config for media setting + /// + /// + /// + /// /someFolder + /// + /// + /// + private string GetMediaFolderSetting(string umbracoMediaPath) + { + var folder = this._configuration.GetValue("uSync:MediaFolder", string.Empty); + if (!string.IsNullOrEmpty(folder)) return folder; + + return umbracoMediaPath; + } + + /// + /// Get the actual media file as a dependency. + /// + public override Task> GetDependenciesAsync(object value, string editorAlias, DependencyFlags flags) + { + return uSyncTaskHelper.FromResultOf>(() => + { + + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) + return []; + + var stringPath = GetImagePath(stringValue).TrimStart('/').ToLower(); + + if (!string.IsNullOrWhiteSpace(stringPath)) + { + return [new uSyncDependency() + { + Name = $"File: {Path.GetFileName(stringPath)}", + Udi = Udi.Create(Constants.UdiEntityType.MediaFile, stringPath), + Flags = flags, + Order = DependencyOrders.OrderFromEntityType(Constants.UdiEntityType.MediaFile), + Level = 0 + }]; + } + + return []; + }); + } + + private string GetImagePath(string stringValue) + { + if (stringValue.TryParseToJsonObject(out var json) is false || json is null) + return StripSitePath(stringValue); + + + if (json.TryGetPropertyValue("src", out var srcNode) is true) + { + var source = srcNode?.GetValue() ?? string.Empty; + if (string.IsNullOrWhiteSpace(source) is false) return source; + } + + return string.Empty; + } +} diff --git a/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs b/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs new file mode 100644 index 000000000..bd47dbf48 --- /dev/null +++ b/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; + +using uSync.Core.Extensions; +using uSync.Core.Serialization; + +namespace uSync.Core.Mapping; + +/// +/// image uploads don't store any of the json, stuff, so they are similar to image croppers, +/// but a bit simpler. +/// +public class ImageUploadMapper : ImagePathMapperBase, ISyncMapper +{ + public ImageUploadMapper( + IEntityService entityService, + ILogger logger, + IConfiguration configuration, + IOptionsMonitor globalOptions) : base(entityService, logger, configuration, globalOptions) + { } + + public override string Name => "Image Upload Mapper"; + public override string[] Editors => [Constants.PropertyEditors.Aliases.UploadField]; + public override Task GetExportValueAsync(object value, string editorAlias) + { + return uSyncTaskHelper.FromResultOf(() => + { + if (_logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("Getting export value for ImageUpload with value {Value}", value); + + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; + return StripSitePath(stringValue); + }); + } + + public override Task GetImportValueAsync(string value, string editorAlias, SyncSerializerOptions options) + { + return uSyncTaskHelper.FromResultOf(() => + { + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; + return PrePendSitePath(stringValue); + }); + } +} diff --git a/uSync.Core/Mapping/SyncBlockMapperBase.cs b/uSync.Core/Mapping/SyncBlockMapperBase.cs index 70e8b344c..500009cdc 100644 --- a/uSync.Core/Mapping/SyncBlockMapperBase.cs +++ b/uSync.Core/Mapping/SyncBlockMapperBase.cs @@ -62,17 +62,11 @@ public SyncBlockMapperBase( _logger.LogDebug("Importing block value for {PropertyEditorAlias} {valueType}", propertyType.PropertyEditorAlias, value?.GetType().Name ?? "blank"); var importString = SyncBlockMapperBase.GetStringValue(value) ?? string.Empty; - var result = await _mapperCollection.Value.GetImportValueAsync(importString, propertyType, options); - // When the original value was a non-string JSON type (array, object, number, etc.), - // convert string results back to JsonNode to preserve the correct JSON type - // and prevent double-encoding when the block value is re-serialized. - if (result is string stringResult && value.IsNonStringJsonValue()) - { - return stringResult.ConvertToJsonNode() ?? result; - } - - return result; + // revert this back to the old way - we don't expand the json we get back because umbraco is very + // sensitve to what the exact format of the blocks is, and if we expand them, then calls during render + // can return null. + return await _mapperCollection.Value.GetImportValueAsync(importString, propertyType, options); } private async Task GetExportProperty(object? value, IPropertyType? propertyType, SyncSerializerOptions options) diff --git a/uSync.History/uSyncHistoryComposer.cs b/uSync.History/uSyncHistoryComposer.cs index f5323f283..61f33789f 100644 --- a/uSync.History/uSyncHistoryComposer.cs +++ b/uSync.History/uSyncHistoryComposer.cs @@ -12,6 +12,7 @@ using uSync.BackOffice; using uSync.BackOffice.Extensions; +using uSync.Core.Extensions; using uSync.History.Service; namespace uSync.History @@ -20,6 +21,10 @@ public class uSyncHistoryComposer : IComposer { public void Compose(IUmbracoBuilder builder) { + // don't load if the backoffice is not loaded as part of the project. + if (builder.IsUmbracoBackOfficeEnabled() is false) + return; + builder.Services.AddSingleton(); builder.AddNotificationAsyncHandler();