diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index da9c2e59ef..ded5be40fd 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,11 +1,13 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Persistence.EFCore.Factories; using Umbraco.Cms.Persistence.EFCore.Locking; -using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Cms.Persistence.EFCore.Scoping; namespace Umbraco.Extensions; @@ -17,6 +19,13 @@ public static class UmbracoEFCoreServiceCollectionExtensions public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { + var optionsBuilder = new DbContextOptionsBuilder(); + services.TryAddSingleton>( + sp => + { + SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + }); services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -39,6 +48,13 @@ public static class UmbracoEFCoreServiceCollectionExtensions connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); } + var optionsBuilder = new DbContextOptionsBuilder(); + services.TryAddSingleton>( + sp => + { + SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + }); services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs b/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs new file mode 100644 index 0000000000..de0f7db200 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Persistence.EFCore.Factories; + +/// +internal class UmbracoPooledDbContextFactory : PooledDbContextFactory + where TContext : DbContext +{ + private readonly IRuntimeState _runtimeState; + private readonly DbContextOptions _options; + + /// + public UmbracoPooledDbContextFactory(IRuntimeState runtimeState, DbContextOptions options, int poolSize = 1024 /*DbContextPool.DefaultPoolSize*/) : base(options, poolSize) + { + _runtimeState = runtimeState; + _options = options; + } + + /// + public override TContext CreateDbContext() + { + if (_runtimeState.Level == RuntimeLevel.Run) + { + return base.CreateDbContext(); + } + else + { + return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); + } + } + + /// + public override async Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + if (_runtimeState.Level == RuntimeLevel.Run) + { + return await base.CreateDbContextAsync(cancellationToken); + } + else + { + return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/JsonValueConverter.cs index 4eaa24ecf9..e6345ab61d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -1,10 +1,12 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Text.Json.Nodes; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors.DeliveryApi; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -16,7 +18,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; /// Since this is a default (umbraco) converter it will be ignored if another converter found conflicts with this one. /// [DefaultPropertyValueConverter] -public class JsonValueConverter : PropertyValueConverterBase +public class JsonValueConverter : PropertyValueConverterBase, IDeliveryApiPropertyValueConverter { private readonly ILogger _logger; private readonly PropertyEditorCollection _propertyEditors; @@ -76,5 +78,14 @@ public class JsonValueConverter : PropertyValueConverterBase return sourceString; } - // TODO: Now to convert that to XPath! + public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) + => GetPropertyCacheLevel(propertyType); + + public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) + => typeof(JsonNode); + + public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding) + => inter is JObject jObject + ? JsonNode.Parse(jObject.ToString()) + : null; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/JsonValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/JsonValueConverterTests.cs new file mode 100644 index 0000000000..ac22f3985f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/JsonValueConverterTests.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi; + +[TestFixture] +public class JsonValueConverterTests : PropertyValueConverterTests +{ + [Test] + public void JsonValueConverterTests_ConvertsCustomPropertyWithValueTypeJson() + { + var valueEditor = Mock.Of(x => x.ValueType == ValueTypes.Json); + var dataEditor = Mock.Of(x => x.GetValueEditor() == valueEditor); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor })); + var propertyType = Mock.Of(x => x.EditorAlias == "My.Custom.Json"); + + var valueConverter = new JsonValueConverter(propertyEditors, Mock.Of>()); + var inter = valueConverter.ConvertSourceToIntermediate(Mock.Of(), propertyType, "{\"message\": \"Hello, JSON\"}", false); + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), propertyType, PropertyCacheLevel.Element, inter, false, false); + Assert.IsTrue(result is JsonNode); + JsonNode jsonNode = (JsonNode)result; + Assert.AreEqual("Hello, JSON", jsonNode["message"]!.GetValue()); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs index b3537c4659..d046064af2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Logging; using Moq; +using Newtonsoft.Json.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -133,4 +135,19 @@ public class PropertyEditorValueConverterTests Assert.AreEqual(expected, result); } + + [Test] + public void CanConvertManifestBasedPropertyWithValueTypeJson() + { + var valueEditor = Mock.Of(x => x.ValueType == ValueTypes.Json); + var dataEditor = Mock.Of(x => x.GetValueEditor() == valueEditor); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor })); + var propertyType = Mock.Of(x => x.EditorAlias == "My.Custom.Json"); + + var valueConverter = new JsonValueConverter(propertyEditors, Mock.Of>()); + var inter = valueConverter.ConvertSourceToIntermediate(Mock.Of(), propertyType, "{\"message\": \"Hello, JSON\"}", false); + var result = valueConverter.ConvertIntermediateToObject(Mock.Of(), propertyType, PropertyCacheLevel.Element, inter, false) as JObject; + Assert.IsNotNull(result); + Assert.AreEqual("Hello, JSON", result["message"]!.Value()); + } }