Skip to content

Commit f088a9b

Browse files
authored
Merge pull request #11127 from umbraco/v9/feature/localdb-install-option
Add LocalDB database install option and implement automatic database creation
2 parents baa51d9 + f638636 commit f088a9b

27 files changed

+402
-383
lines changed

src/Umbraco.Core/Configuration/ConfigConnectionString.cs

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,88 @@ namespace Umbraco.Cms.Core.Configuration
55
{
66
public class ConfigConnectionString
77
{
8+
public string Name { get; }
9+
10+
public string ConnectionString { get; }
11+
12+
public string ProviderName { get; }
13+
814
public ConfigConnectionString(string name, string connectionString, string providerName = null)
915
{
1016
Name = name ?? throw new ArgumentNullException(nameof(name));
11-
ConnectionString = connectionString;
12-
13-
ProviderName = string.IsNullOrEmpty(providerName) ? ParseProvider(connectionString) : providerName;
17+
ConnectionString = ParseConnectionString(connectionString, ref providerName);
18+
ProviderName = providerName;
1419
}
1520

16-
public string ConnectionString { get; }
17-
public string ProviderName { get; }
18-
public string Name { get; }
19-
20-
private static bool IsSqlCe(DbConnectionStringBuilder builder) => (builder.TryGetValue("Data Source", out var ds)
21-
|| builder.TryGetValue("DataSource", out ds)) &&
22-
ds is string dataSource &&
23-
dataSource.EndsWith(".sdf");
21+
private static string ParseConnectionString(string connectionString, ref string providerName)
22+
{
23+
if (string.IsNullOrEmpty(connectionString))
24+
{
25+
return connectionString;
26+
}
2427

25-
private static bool IsSqlServer(DbConnectionStringBuilder builder) =>
26-
!string.IsNullOrEmpty(GetServer(builder)) &&
27-
((builder.TryGetValue("Database", out var db) && db is string database &&
28-
!string.IsNullOrEmpty(database)) ||
29-
(builder.TryGetValue("AttachDbFileName", out var a) && a is string attachDbFileName &&
30-
!string.IsNullOrEmpty(attachDbFileName)) ||
31-
(builder.TryGetValue("Initial Catalog", out var i) && i is string initialCatalog &&
32-
!string.IsNullOrEmpty(initialCatalog)));
28+
var builder = new DbConnectionStringBuilder
29+
{
30+
ConnectionString = connectionString
31+
};
3332

34-
private static string GetServer(DbConnectionStringBuilder builder)
35-
{
36-
if(builder.TryGetValue("Server", out var s) && s is string server)
33+
// Replace data directory placeholder
34+
const string attachDbFileNameKey = "AttachDbFileName";
35+
const string dataDirectoryPlaceholder = "|DataDirectory|";
36+
if (builder.TryGetValue(attachDbFileNameKey, out var attachDbFileNameValue) &&
37+
attachDbFileNameValue is string attachDbFileName &&
38+
attachDbFileName.Contains(dataDirectoryPlaceholder))
3739
{
38-
return server;
40+
var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
41+
if (!string.IsNullOrEmpty(dataDirectory))
42+
{
43+
builder[attachDbFileNameKey] = attachDbFileName.Replace(dataDirectoryPlaceholder, dataDirectory);
44+
45+
// Mutate the existing connection string (note: the builder also lowercases the properties)
46+
connectionString = builder.ToString();
47+
}
3948
}
4049

41-
if ((builder.TryGetValue("Data Source", out var ds)
42-
|| builder.TryGetValue("DataSource", out ds)) && ds is string dataSource)
50+
// Also parse provider name now we already have a builder
51+
if (string.IsNullOrEmpty(providerName))
4352
{
44-
return dataSource;
53+
providerName = ParseProviderName(builder);
4554
}
4655

47-
return "";
56+
return connectionString;
4857
}
4958

50-
private static string ParseProvider(string connectionString)
59+
/// <summary>
60+
/// Parses the connection string to get the provider name.
61+
/// </summary>
62+
/// <param name="connectionString">The connection string.</param>
63+
/// <returns>
64+
/// The provider name or <c>null</c> is the connection string is empty.
65+
/// </returns>
66+
public static string ParseProviderName(string connectionString)
5167
{
5268
if (string.IsNullOrEmpty(connectionString))
5369
{
5470
return null;
5571
}
5672

57-
var builder = new DbConnectionStringBuilder {ConnectionString = connectionString};
58-
if (IsSqlCe(builder))
73+
var builder = new DbConnectionStringBuilder
5974
{
60-
return Constants.DbProviderNames.SqlCe;
61-
}
75+
ConnectionString = connectionString
76+
};
6277

78+
return ParseProviderName(builder);
79+
}
6380

64-
if (IsSqlServer(builder))
81+
private static string ParseProviderName(DbConnectionStringBuilder builder)
82+
{
83+
if ((builder.TryGetValue("Data Source", out var dataSource) || builder.TryGetValue("DataSource", out dataSource)) &&
84+
dataSource?.ToString().EndsWith(".sdf", StringComparison.OrdinalIgnoreCase) == true)
6585
{
66-
return Constants.DbProviderNames.SqlServer;
86+
return Constants.DbProviderNames.SqlCe;
6787
}
6888

69-
throw new ArgumentException("Cannot determine provider name from connection string",
70-
nameof(connectionString));
89+
return Constants.DbProviderNames.SqlServer;
7190
}
7291
}
7392
}

src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ private string umbracoDbDSN
2626
/// <summary>
2727
/// Gets or sets a value for the Umbraco database connection string..
2828
/// </summary>
29-
public ConfigConnectionString UmbracoConnectionString { get; set; }
29+
public ConfigConnectionString UmbracoConnectionString { get; set; } = new ConfigConnectionString(Constants.System.UmbracoConnectionName, null);
3030
}
3131
}
Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,15 @@
11
// Copyright (c) Umbraco.
22
// See LICENSE for more details.
33

4-
using System;
5-
using System.IO;
6-
using System.Linq;
7-
using Umbraco.Cms.Core;
84
using Umbraco.Cms.Core.Configuration;
95

106
namespace Umbraco.Extensions
117
{
128
public static class ConfigConnectionStringExtensions
139
{
1410
public static bool IsConnectionStringConfigured(this ConfigConnectionString databaseSettings)
15-
{
16-
var dbIsSqlCe = false;
17-
if (databaseSettings?.ProviderName != null)
18-
{
19-
dbIsSqlCe = databaseSettings.ProviderName == Constants.DbProviderNames.SqlCe;
20-
}
21-
22-
var sqlCeDatabaseExists = false;
23-
if (dbIsSqlCe)
24-
{
25-
var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
26-
var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source="));
27-
if (dataSourcePart != null)
28-
{
29-
var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
30-
var filePath = datasource.Replace("Data Source=", string.Empty);
31-
sqlCeDatabaseExists = File.Exists(filePath);
32-
}
33-
}
34-
35-
// Either the connection details are not fully specified or it's a SQL CE database that doesn't exist yet
36-
if (databaseSettings == null
37-
|| string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) || string.IsNullOrWhiteSpace(databaseSettings.ProviderName)
38-
|| (dbIsSqlCe && sqlCeDatabaseExists == false))
39-
{
40-
return false;
41-
}
42-
43-
return true;
44-
}
11+
=> databaseSettings != null &&
12+
!string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) &&
13+
!string.IsNullOrWhiteSpace(databaseSettings.ProviderName);
4514
}
4615
}

src/Umbraco.Core/Install/Models/DatabaseModel.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
using System.Runtime.Serialization;
1+
using System.Runtime.Serialization;
22

33
namespace Umbraco.Cms.Core.Install.Models
44
{
55
[DataContract(Name = "database", Namespace = "")]
66
public class DatabaseModel
77
{
8-
public DatabaseModel()
9-
{
10-
//defaults
11-
DatabaseType = DatabaseType.SqlCe;
12-
}
13-
148
[DataMember(Name = "dbType")]
15-
public DatabaseType DatabaseType { get; set; }
9+
public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer;
1610

1711
[DataMember(Name = "server")]
1812
public string Server { get; set; }

src/Umbraco.Core/Install/Models/DatabaseType.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
namespace Umbraco.Cms.Core.Install.Models
1+
namespace Umbraco.Cms.Core.Install.Models
22
{
33
public enum DatabaseType
44
{
5+
SqlLocalDb,
56
SqlCe,
67
SqlServer,
78
SqlAzure,

src/Umbraco.Infrastructure/Install/InstallHelper.cs

Lines changed: 15 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Net.Http;
52
using System.Threading.Tasks;
63
using Microsoft.Extensions.Logging;
74
using Microsoft.Extensions.Options;
@@ -10,7 +7,6 @@
107
using Umbraco.Cms.Core.Configuration.Models;
118
using Umbraco.Cms.Core.Install.Models;
129
using Umbraco.Cms.Core.Net;
13-
using Umbraco.Cms.Core.Serialization;
1410
using Umbraco.Cms.Core.Services;
1511
using Umbraco.Cms.Core.Web;
1612
using Umbraco.Cms.Infrastructure.Migrations.Install;
@@ -50,40 +46,26 @@ public InstallHelper(DatabaseBuilder databaseBuilder,
5046
_userAgentProvider = userAgentProvider;
5147
_umbracoDatabaseFactory = umbracoDatabaseFactory;
5248

53-
//We need to initialize the type already, as we can't detect later, if the connection string is added on the fly.
49+
// We need to initialize the type already, as we can't detect later, if the connection string is added on the fly.
5450
GetInstallationType();
5551
}
5652

57-
public InstallationType GetInstallationType()
58-
{
59-
return _installationType ?? (_installationType = IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade).Value;
60-
}
53+
public InstallationType GetInstallationType() => _installationType ??= IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade;
6154

6255
public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg)
6356
{
6457
try
6558
{
6659
var userAgent = _userAgentProvider.GetUserAgent();
6760

68-
// Check for current install Id
69-
var installId = Guid.NewGuid();
70-
61+
// Check for current install ID
7162
var installCookie = _cookieManager.GetCookieValue(Constants.Web.InstallerCookieName);
72-
if (string.IsNullOrEmpty(installCookie) == false)
63+
if (!Guid.TryParse(installCookie, out var installId))
7364
{
74-
if (Guid.TryParse(installCookie, out installId))
75-
{
76-
// check that it's a valid Guid
77-
if (installId == Guid.Empty)
78-
installId = Guid.NewGuid();
79-
}
80-
else
81-
{
82-
installId = Guid.NewGuid(); // Guid.TryParse will have reset installId to Guid.Empty
83-
}
84-
}
65+
installId = Guid.NewGuid();
8566

86-
_cookieManager.SetCookieValue(Constants.Web.InstallerCookieName, installId.ToString());
67+
_cookieManager.SetCookieValue(Constants.Web.InstallerCookieName, installId.ToString());
68+
}
8769

8870
var dbProvider = string.Empty;
8971
if (IsBrandNewInstall == false)
@@ -108,29 +90,14 @@ public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg)
10890
}
10991

11092
/// <summary>
111-
/// Checks if this is a brand new install meaning that there is no configured version and there is no configured database connection
93+
/// Checks if this is a brand new install, meaning that there is no configured database connection or the database is empty.
11294
/// </summary>
113-
private bool IsBrandNewInstall
114-
{
115-
get
116-
{
117-
var databaseSettings = _connectionStrings.CurrentValue.UmbracoConnectionString;
118-
if (databaseSettings.IsConnectionStringConfigured() == false)
119-
{
120-
//no version or conn string configured, must be a brand new install
121-
return true;
122-
}
123-
124-
//now we have to check if this is really a new install, the db might be configured and might contain data
125-
126-
if (databaseSettings.IsConnectionStringConfigured() == false
127-
|| _databaseBuilder.IsDatabaseConfigured == false)
128-
{
129-
return true;
130-
}
131-
132-
return _databaseBuilder.IsUmbracoInstalled() == false;
133-
}
134-
}
95+
/// <value>
96+
/// <c>true</c> if this is a brand new install; otherwise, <c>false</c>.
97+
/// </value>
98+
private bool IsBrandNewInstall => _connectionStrings.CurrentValue.UmbracoConnectionString?.IsConnectionStringConfigured() != true ||
99+
_databaseBuilder.IsDatabaseConfigured == false ||
100+
_databaseBuilder.CanConnectToDatabase == false ||
101+
_databaseBuilder.IsUmbracoInstalled() == false;
135102
}
136103
}

0 commit comments

Comments
 (0)