Merge branch 'release/12.3.0' into v12/dev

This commit is contained in:
Zeegaan
2023-11-02 08:53:11 +01:00
11 changed files with 252 additions and 19 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Umbraco.Cms.Core;
using Umbraco.Cms.Persistence.EFCore.Migrations;
using Umbraco.Extensions;
@@ -10,7 +11,7 @@ public class SqlServerMigrationProvider : IMigrationProvider
public SqlServerMigrationProvider(IDbContextFactory<UmbracoDbContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
public string ProviderName => "Microsoft.Data.SqlClient";
public string ProviderName => Constants.ProviderNames.SQLServer;
public async Task MigrateAsync(EFCoreMigration migration)
{

View File

@@ -1,11 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Umbraco.Cms.Core;
using Umbraco.Cms.Persistence.EFCore.Migrations;
namespace Umbraco.Cms.Persistence.EFCore.SqlServer;
public class SqlServerMigrationProviderSetup : IMigrationProviderSetup
{
public string ProviderName => "Microsoft.Data.SqlClient";
public string ProviderName => Constants.ProviderNames.SQLServer;
public void Setup(DbContextOptionsBuilder builder, string? connectionString)
{

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Umbraco.Cms.Core;
using Umbraco.Cms.Persistence.EFCore.Migrations;
using Umbraco.Extensions;
@@ -11,7 +12,7 @@ public class SqliteMigrationProvider : IMigrationProvider
public SqliteMigrationProvider(IDbContextFactory<UmbracoDbContext> dbContextFactory)
=> _dbContextFactory = dbContextFactory;
public string ProviderName => "Microsoft.Data.Sqlite";
public string ProviderName => Constants.ProviderNames.SQLLite;
public async Task MigrateAsync(EFCoreMigration migration)
{

View File

@@ -1,11 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Umbraco.Cms.Core;
using Umbraco.Cms.Persistence.EFCore.Migrations;
namespace Umbraco.Cms.Persistence.EFCore.Sqlite;
public class SqliteMigrationProviderSetup : IMigrationProviderSetup
{
public string ProviderName => "Microsoft.Data.Sqlite";
public string ProviderName => Constants.ProviderNames.SQLLite;
public void Setup(DbContextOptionsBuilder builder, string? connectionString)
{

View File

@@ -20,7 +20,7 @@ public class UmbracoEFCoreComposer : IComposer
builder.AddNotificationAsyncHandler<DatabaseSchemaAndDataCreatedNotification, EFCoreCreateTablesNotificationHandler>();
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, EFCoreCreateTablesNotificationHandler>();
builder.Services.AddUmbracoEFCoreContext<UmbracoDbContext>((options, connectionString, providerName) =>
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((options) =>
{
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();

View File

@@ -1,7 +1,9 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Serilog;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DistributedLocking;
@@ -16,6 +18,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
{
public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString);
[Obsolete("Use AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null) instead.")]
public static IServiceCollection AddUmbracoEFCoreContext<T>(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null)
where T : DbContext
{
@@ -24,7 +27,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
sp =>
{
SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder);
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(),optionsBuilder.Options);
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
});
services.AddPooledDbContextFactory<T>((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder));
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
@@ -38,6 +41,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
return services;
}
[Obsolete("Use AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null) instead.")]
public static IServiceCollection AddUmbracoEFCoreContext<T>(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null)
where T : DbContext
{
@@ -52,8 +56,8 @@ public static class UmbracoEFCoreServiceCollectionExtensions
services.TryAddSingleton<IDbContextFactory<T>>(
sp =>
{
SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder);
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(),optionsBuilder.Options);
defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString);
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
});
services.AddPooledDbContextFactory<T>(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString));
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
@@ -67,12 +71,117 @@ public static class UmbracoEFCoreServiceCollectionExtensions
return services;
}
/// <summary>
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="services"></param>
/// <param name="optionsAction"></param>
/// <returns></returns>
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null)
where T : DbContext
{
return AddUmbracoDbContext<T>(services, (IServiceProvider _, DbContextOptionsBuilder options) =>
{
optionsAction?.Invoke(options);
});
}
/// <summary>
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="services"></param>
/// <param name="optionsAction"></param>
/// <returns></returns>
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
where T : DbContext
{
optionsAction ??= (sp, options) => { };
var optionsBuilder = new DbContextOptionsBuilder<T>();
services.TryAddSingleton<IDbContextFactory<T>>(sp =>
{
optionsAction.Invoke(sp, optionsBuilder);
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
});
services.AddPooledDbContextFactory<T>(optionsAction);
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
services.AddUnique<IEFCoreScopeAccessor<T>, EFCoreScopeAccessor<T>>();
services.AddUnique<IEFCoreScopeProvider<T>, EFCoreScopeProvider<T>>();
services.AddSingleton<IDistributedLockingMechanism, SqliteEFCoreDistributedLockingMechanism<T>>();
services.AddSingleton<IDistributedLockingMechanism, SqlServerEFCoreDistributedLockingMechanism<T>>();
return services;
}
/// <summary>
/// Sets the database provider. I.E UseSqlite or UseSqlServer based on the provider name.
/// </summary>
/// <param name="builder"></param>
/// <param name="providerName"></param>
/// <param name="connectionString"></param>
/// <exception cref="InvalidDataException"></exception>
/// <remarks>
/// Only supports the databases normally supported in Umbraco.
/// </remarks>
public static void UseDatabaseProvider(this DbContextOptionsBuilder builder, string providerName, string connectionString)
{
switch (providerName)
{
case Constants.ProviderNames.SQLServer:
builder.UseSqlServer(connectionString);
break;
case Constants.ProviderNames.SQLLite:
builder.UseSqlite(connectionString);
break;
default:
throw new InvalidDataException($"The provider {providerName} is not supported. Manually add the add the UseXXX statement to the options. I.E UseNpgsql()");
}
}
/// <summary>
/// Sets the database provider to use based on the Umbraco connection string.
/// </summary>
/// <param name="builder"></param>
/// <param name="serviceProvider"></param>
public static void UseUmbracoDatabaseProvider(this DbContextOptionsBuilder builder, IServiceProvider serviceProvider)
{
ConnectionStrings connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>().CurrentValue;
// Replace data directory
string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString();
if (string.IsNullOrEmpty(dataDirectory) is false)
{
connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory);
}
if (string.IsNullOrEmpty(connectionStrings.ProviderName))
{
Log.Warning("No database provider was set. ProviderName is null");
return;
}
if (string.IsNullOrEmpty(connectionStrings.ConnectionString))
{
Log.Warning("No database provider was set. Connection string is null");
return;
}
builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString);
}
[Obsolete]
private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder)
{
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName);
}
[Obsolete]
private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider)
{
ConnectionStrings connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>().CurrentValue;

View File

@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Persistence.EFCore.Migrations;
@@ -77,7 +76,7 @@ public class UmbracoDbContext : DbContext
foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes())
{
entity.SetTableName(Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName());
entity.SetTableName(Core.Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName());
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Umbraco.Cms.Core;
public static partial class Constants
{
public static class ProviderNames
{
public const string SQLLite = "Microsoft.Data.Sqlite";
public const string SQLServer = "Microsoft.Data.SqlClient";
}
}

View File

@@ -35,15 +35,18 @@
$scope.activeApp = null;
//initializes any watches
function startWatches(content) {
var watchers = [];
$scope.$watchGroup(['culture', 'segment'],
function startWatches(content) {
clearWatchers();
watchers.push($scope.$watchGroup(['culture', 'segment'],
function (value, oldValue) {
createPreviewButton($scope.content, value[0], value[1]);
});
}));
//watch for changes to isNew, set the page.isNew accordingly and load the breadcrumb if we can
$scope.$watch('isNew', function (newVal, oldVal) {
watchers.push($scope.$watch('isNew', function (newVal, oldVal) {
$scope.page.isNew = Object.toBoolean(newVal);
@@ -59,8 +62,12 @@
});
}
}
});
}));
}
function clearWatchers () {
watchers.forEach(w => w());
watchers = [];
}
//this initializes the editor with the data which will be called more than once if the data is re-loaded
@@ -109,6 +116,7 @@
bindEvents();
resetVariantFlags();
startWatches($scope.content);
}
function loadBreadcrumb() {
@@ -241,7 +249,6 @@
appendRuntimeData();
init();
startWatches($scope.content);
syncTreeNode($scope.content, $scope.content.path, true);
@@ -265,7 +272,6 @@
appendRuntimeData();
init();
startWatches($scope.content);
resetLastListPageNumber($scope.content);
@@ -346,7 +352,10 @@
labelKey: "buttons_saveAndPreview"
};
const activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId);
let activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId);
/* if we can't find the active variant and there is only one variant available, we will use that.
this happens if we have a node that can vary by culture but there is only one language available. */
activeVariant = !activeVariant && content.variants.length === 1 ? content.variants[0] : activeVariant;
$scope.previewSubButtons = activeVariant?.additionalPreviewUrls?.map((additionalPreviewUrl) => {
return {

View File

@@ -0,0 +1,101 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTest
{
[Test]
public void Can_Register_Custom_DbContext_And_Resolve()
{
var dbContext = Services.GetRequiredService<CustomDbContext>();
Assert.IsNotNull(dbContext);
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
}
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
{
options.UseUmbracoDatabaseProvider(serviceProvider);
});
}
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
}
}
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest
{
[Test]
public void Can_Register_Custom_DbContext_And_Resolve()
{
var dbContext = Services.GetRequiredService<CustomDbContext>();
Assert.IsNotNull(dbContext);
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
}
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
{
options.UseSqlite("Data Source=:memory:;Version=3;New=True;");
});
}
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
}
}
[Obsolete]
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class CustomDbContextLegacyExtensionProviderTests : UmbracoIntegrationTest
{
[Test]
public void Can_Register_Custom_DbContext_And_Resolve()
{
var dbContext = Services.GetRequiredService<CustomDbContext>();
Assert.IsNotNull(dbContext);
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
}
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
builder.Services.AddUmbracoEFCoreContext<CustomDbContext>("Data Source=:memory:;Version=3;New=True;", "Microsoft.Data.Sqlite", (options, connectionString, providerName) =>
{
options.UseSqlite(connectionString);
});
}
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
}
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "12.3.0-rc",
"version": "12.3.0",
"assemblyVersion": {
"precision": "build"
},