diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor
index 3446ccbf..6ad2be28 100644
--- a/Oqtane.Client/App.razor
+++ b/Oqtane.Client/App.razor
@@ -1,15 +1,31 @@
@using Oqtane.Shared
@using Oqtane.Client.Shared
+@using Oqtane.Services
+@inject IInstallationService InstallationService
-
-
-
-
-
+@if (!Installed)
+{
+
+}
+else
+{
+
+
+
+
+
+}
@code {
+ private bool Installed = false;
private PageState PageState { get; set; }
+ protected override async Task OnInitAsync()
+ {
+ var response = await InstallationService.IsInstalled();
+ Installed = response.Success;
+ }
+
private void ChangeState(PageState pagestate)
{
PageState = pagestate;
diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor
index 3071d436..c381e244 100644
--- a/Oqtane.Client/Modules/Admin/Login/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Login/Index.razor
@@ -44,7 +44,7 @@
@code {
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
-public string Message { get; set; } = "
Use host/password For Demo Access
";
+public string Message { get; set; } = "";
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public bool Remember { get; set; } = false;
diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor
index 4bdd8aaf..63c4b2f3 100644
--- a/Oqtane.Client/Modules/HtmlText/Edit.razor
+++ b/Oqtane.Client/Modules/HtmlText/Edit.razor
@@ -33,7 +33,7 @@
protected override async Task OnInitAsync()
{
- HtmlTextService htmltextservice = new HtmlTextService(http, sitestate);
+ HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper);
List htmltextlist = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltextlist != null)
{
@@ -44,7 +44,7 @@
private async Task SaveContent()
{
- HtmlTextService htmltextservice = new HtmlTextService(http, sitestate);
+ HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper);
if (htmltext != null)
{
htmltext.Content = content;
diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor
index cc679988..90d86153 100644
--- a/Oqtane.Client/Modules/HtmlText/Index.razor
+++ b/Oqtane.Client/Modules/HtmlText/Index.razor
@@ -5,6 +5,7 @@
@using Oqtane.Client.Modules.Controls
@using Oqtane.Shared;
@inherits ModuleBase
+@inject IUriHelper UriHelper
@inject HttpClient http
@inject SiteState sitestate
@@ -17,7 +18,7 @@
protected override async Task OnInitAsync()
{
- HtmlTextService htmltextservice = new HtmlTextService(http, sitestate);
+ HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper);
List htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
index c6f2443b..e8257b00 100644
--- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
+++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
@@ -13,16 +13,18 @@ namespace Oqtane.Client.Modules.HtmlText.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public HtmlTextService(HttpClient http, SiteState sitestate)
+ public HtmlTextService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "HtmlText"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "HtmlText"); }
}
public async Task> GetHtmlTextAsync(int ModuleId)
diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs
index 88aa9683..1d29f68a 100644
--- a/Oqtane.Client/Services/AliasService.cs
+++ b/Oqtane.Client/Services/AliasService.cs
@@ -11,17 +11,19 @@ namespace Oqtane.Services
public class AliasService : ServiceBase, IAliasService
{
private readonly HttpClient http;
+ private readonly SiteState sitestate;
private readonly IUriHelper urihelper;
- public AliasService(HttpClient http, IUriHelper urihelper)
+ public AliasService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
+ this.sitestate = sitestate;
this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(urihelper.GetAbsoluteUri(), "Alias"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Alias"); }
}
public async Task> GetAliasesAsync()
diff --git a/Oqtane.Client/Services/IInstallationService.cs b/Oqtane.Client/Services/IInstallationService.cs
new file mode 100644
index 00000000..37f71fef
--- /dev/null
+++ b/Oqtane.Client/Services/IInstallationService.cs
@@ -0,0 +1,12 @@
+using Oqtane.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Oqtane.Services
+{
+ public interface IInstallationService
+ {
+ Task IsInstalled();
+ Task Install(string connectionstring);
+ }
+}
diff --git a/Oqtane.Client/Services/InstallationService.cs b/Oqtane.Client/Services/InstallationService.cs
new file mode 100644
index 00000000..399140ef
--- /dev/null
+++ b/Oqtane.Client/Services/InstallationService.cs
@@ -0,0 +1,39 @@
+using Oqtane.Models;
+using System.Threading.Tasks;
+using System.Net.Http;
+using System.Linq;
+using Microsoft.AspNetCore.Components;
+using System.Collections.Generic;
+using Oqtane.Shared;
+
+namespace Oqtane.Services
+{
+ public class InstallationService : ServiceBase, IInstallationService
+ {
+ private readonly HttpClient http;
+ private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
+
+ public InstallationService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
+ {
+ this.http = http;
+ this.sitestate = sitestate;
+ this.urihelper = urihelper;
+ }
+
+ private string apiurl
+ {
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Installation"); }
+ }
+
+ public async Task IsInstalled()
+ {
+ return await http.GetJsonAsync(apiurl + "/installed");
+ }
+
+ public async Task Install(string connectionstring)
+ {
+ return await http.PostJsonAsync(apiurl, connectionstring);
+ }
+ }
+}
diff --git a/Oqtane.Client/Services/ModuleDefinitionService.cs b/Oqtane.Client/Services/ModuleDefinitionService.cs
index 75b06b2f..31fff1ba 100644
--- a/Oqtane.Client/Services/ModuleDefinitionService.cs
+++ b/Oqtane.Client/Services/ModuleDefinitionService.cs
@@ -14,16 +14,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public ModuleDefinitionService(HttpClient http, SiteState sitestate)
+ public ModuleDefinitionService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "ModuleDefinition"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "ModuleDefinition"); }
}
public async Task> GetModuleDefinitionsAsync()
diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs
index ede01dc5..60f22eeb 100644
--- a/Oqtane.Client/Services/ModuleService.cs
+++ b/Oqtane.Client/Services/ModuleService.cs
@@ -12,16 +12,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public ModuleService(HttpClient http, SiteState sitestate)
+ public ModuleService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "Module"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Module"); }
}
public async Task> GetModulesAsync(int PageId)
diff --git a/Oqtane.Client/Services/PageModuleService.cs b/Oqtane.Client/Services/PageModuleService.cs
index 240b4c44..8a65de2d 100644
--- a/Oqtane.Client/Services/PageModuleService.cs
+++ b/Oqtane.Client/Services/PageModuleService.cs
@@ -12,16 +12,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public PageModuleService(HttpClient http, SiteState sitestate)
+ public PageModuleService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "PageModule"); }
+ get { return CreateApiUrl(sitestate.Alias, "PageModule", urihelper.GetAbsoluteUri()); }
}
public async Task> GetPageModulesAsync()
diff --git a/Oqtane.Client/Services/PageService.cs b/Oqtane.Client/Services/PageService.cs
index fb54c32c..d0124c79 100644
--- a/Oqtane.Client/Services/PageService.cs
+++ b/Oqtane.Client/Services/PageService.cs
@@ -12,16 +12,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public PageService(HttpClient http, SiteState sitestate)
+ public PageService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "Page"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Page"); }
}
public async Task> GetPagesAsync(int SiteId)
diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs
index ca7c588f..b970c373 100644
--- a/Oqtane.Client/Services/ServiceBase.cs
+++ b/Oqtane.Client/Services/ServiceBase.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.AspNetCore.Components;
using Oqtane.Models;
using Oqtane.Shared;
@@ -6,21 +7,24 @@ namespace Oqtane.Services
{
public class ServiceBase
{
- // method for alias agnostic api call
- public string CreateApiUrl(string absoluteUri, string serviceName)
- {
- Uri uri = new Uri(absoluteUri);
- string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/" + serviceName;
- return apiurl;
- }
- // method for alias specific api call
- public string CreateApiUrl(Alias alias, string serviceName)
+ public string CreateApiUrl(Alias alias, string absoluteUri, string serviceName)
{
- string apiurl = alias.Url + "/";
- if (alias.Path == "")
+ string apiurl = "";
+ if (alias != null)
{
- apiurl += "~/";
+ // build a url which passes the alias that may include a subfolder for multi-tenancy
+ apiurl = alias.Url + "/";
+ if (alias.Path == "")
+ {
+ apiurl += "~/";
+ }
+ }
+ else
+ {
+ // build a url which ignores any subfolder for multi-tenancy
+ Uri uri = new Uri(absoluteUri);
+ apiurl = uri.Scheme + "://" + uri.Authority + "/~/";
}
apiurl += "api/" + serviceName;
return apiurl;
diff --git a/Oqtane.Client/Services/SiteService.cs b/Oqtane.Client/Services/SiteService.cs
index 519155a5..83cfad34 100644
--- a/Oqtane.Client/Services/SiteService.cs
+++ b/Oqtane.Client/Services/SiteService.cs
@@ -12,16 +12,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public SiteService(HttpClient http, SiteState sitestate)
+ public SiteService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "Site"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Site"); }
}
public async Task> GetSitesAsync()
diff --git a/Oqtane.Client/Services/TenantService.cs b/Oqtane.Client/Services/TenantService.cs
index 27054504..4da871fb 100644
--- a/Oqtane.Client/Services/TenantService.cs
+++ b/Oqtane.Client/Services/TenantService.cs
@@ -12,16 +12,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public TenantService(HttpClient http, SiteState sitestate)
+ public TenantService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "Tenant"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Tenant"); }
}
public async Task> GetTenantsAsync()
diff --git a/Oqtane.Client/Services/ThemeService.cs b/Oqtane.Client/Services/ThemeService.cs
index b6c2201e..a38a9e28 100644
--- a/Oqtane.Client/Services/ThemeService.cs
+++ b/Oqtane.Client/Services/ThemeService.cs
@@ -14,16 +14,18 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
+ private readonly IUriHelper urihelper;
- public ThemeService(HttpClient http, SiteState sitestate)
+ public ThemeService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
+ this.urihelper = urihelper;
}
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "Theme"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Theme"); }
}
public async Task> GetThemesAsync()
diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs
index d83dc260..eeb19433 100644
--- a/Oqtane.Client/Services/UserService.cs
+++ b/Oqtane.Client/Services/UserService.cs
@@ -24,7 +24,7 @@ namespace Oqtane.Services
private string apiurl
{
- get { return CreateApiUrl(sitestate.Alias, "User"); }
+ get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "User"); }
}
public async Task> GetUsersAsync()
diff --git a/Oqtane.Client/Shared/Installer.razor b/Oqtane.Client/Shared/Installer.razor
new file mode 100644
index 00000000..321f8d3d
--- /dev/null
+++ b/Oqtane.Client/Shared/Installer.razor
@@ -0,0 +1,187 @@
+@using Oqtane.Services
+@using Oqtane.Models
+@inject IUriHelper UriHelper
+@inject IInstallationService InstallationService
+@inject IUserService UserService
+
+
+
+
+

+
+
+
+
Database Configuration
+
+
+
Application Administrator
+
+
+
+
+ @((MarkupString)@Message)
+
+
+
+
+
+@code {
+
+private string DatabaseType = "LocalDB";
+private string ServerName = "(LocalDb)\\MSSQLLocalDB";
+private string DatabaseName = "Oqtane-" + DateTime.Now.ToString("yyyyMMddHHmm");
+private bool IntegratedSecurity = true;
+private string Username = "";
+private string Password = "";
+private string HostUsername = "host";
+private string HostPassword = "";
+private string Message = "";
+
+private string IntegratedSecurityDisplay = "display:none;";
+private string LoadingDisplay = "display:none;";
+
+private void SetIntegratedSecurity(UIChangeEventArgs e)
+{
+ if (Convert.ToBoolean(e.Value))
+ {
+ IntegratedSecurityDisplay = "display:none;";
+ }
+ else
+ {
+ IntegratedSecurityDisplay = "";
+ }
+}
+
+private async Task Install()
+{
+ if (HostPassword.Length >= 6)
+ {
+ LoadingDisplay = "";
+ StateHasChanged();
+
+ string connectionstring = "";
+ if (DatabaseType == "LocalDB")
+ {
+ connectionstring = "Data Source=" + ServerName + ";AttachDbFilename=|DataDirectory|\\" + DatabaseName + ".mdf;Initial Catalog=" + DatabaseName + ";Integrated Security=SSPI;";
+ }
+ else
+ {
+ connectionstring = "Data Source=" + ServerName + ";Initial Catalog=" + DatabaseName + ";";
+ if (IntegratedSecurityDisplay == "display:none;")
+ {
+ connectionstring += "Integrated Security=SSPI;";
+ }
+ else
+ {
+ connectionstring += "User ID=" + Username + ";Password=" + Password;
+
+ }
+ }
+ GenericResponse response = await InstallationService.Install(connectionstring);
+ if (response.Success)
+ {
+ User user = new User();
+ user.Username = HostUsername;
+ user.DisplayName = HostUsername;
+ user.Password = HostPassword;
+ user.IsSuperUser = true;
+ user.Roles = "";
+ await UserService.AddUserAsync(user);
+ UriHelper.NavigateTo("", true);
+ }
+ else
+ {
+ Message = "" + response.Message + "
";
+ LoadingDisplay = "display:none;";
+ }
+ }
+ else
+ {
+ Message = "Password Must Be 6 Characters Or Greater
";
+ }
+}
+}
diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs
index b817e046..1c64ca52 100644
--- a/Oqtane.Client/Startup.cs
+++ b/Oqtane.Client/Startup.cs
@@ -36,6 +36,7 @@ namespace Oqtane.Client
// register scoped core services
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/Oqtane.Client/wwwroot/css/site.css b/Oqtane.Client/wwwroot/css/site.css
index 6774c2e9..193ec6f0 100644
--- a/Oqtane.Client/wwwroot/css/site.css
+++ b/Oqtane.Client/wwwroot/css/site.css
@@ -246,3 +246,13 @@ app {
text-align: center;
color: gray;
}
+
+.loading {
+ background: rgba(0,0,0,0.2) url('../loading.gif') no-repeat 50% 50%;
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 999;
+}
\ No newline at end of file
diff --git a/Oqtane.Client/wwwroot/js/interop.js b/Oqtane.Client/wwwroot/js/interop.js
index a322ca40..db61ecbe 100644
--- a/Oqtane.Client/wwwroot/js/interop.js
+++ b/Oqtane.Client/wwwroot/js/interop.js
@@ -20,6 +20,14 @@ window.interop = {
}
return "";
},
+ getElementByName: function (name) {
+ var elements = document.getElementsByName(name);
+ if (elements.length) {
+ return elements[0].value;
+ } else {
+ return "";
+ }
+ },
addCSS: function (fileName) {
var head = document.head;
var link = document.createElement("link");
diff --git a/Oqtane.Client/wwwroot/loading.gif b/Oqtane.Client/wwwroot/loading.gif
new file mode 100644
index 00000000..cc70a7a8
Binary files /dev/null and b/Oqtane.Client/wwwroot/loading.gif differ
diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs
new file mode 100644
index 00000000..3297c572
--- /dev/null
+++ b/Oqtane.Server/Controllers/InstallationController.cs
@@ -0,0 +1,192 @@
+using DbUp;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Oqtane.Models;
+using System;
+using System.Data.SqlClient;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+
+namespace Oqtane.Controllers
+{
+ [Route("{site}/api/[controller]")]
+ public class InstallationController : Controller
+ {
+ private readonly IConfigurationRoot _config;
+
+ public InstallationController(IConfigurationRoot config)
+ {
+ _config = config;
+ }
+
+ // POST api/
+ [HttpPost]
+ public GenericResponse Post([FromBody] string connectionString)
+ {
+ var response = new GenericResponse { Success = false, Message = "" };
+
+ if (ModelState.IsValid)
+ {
+ bool exists = IsInstalled().Success;
+
+ if (!exists)
+ {
+ string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
+ connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
+
+ SqlConnection connection = new SqlConnection(connectionString);
+ try
+ {
+ using (connection)
+ {
+ connection.Open();
+ }
+ exists = true;
+ }
+ catch
+ {
+ // database does not exist
+ }
+
+ // try to create database if it does not exist
+ if (!exists)
+ {
+ string masterConnectionString = "";
+ string databaseName = "";
+ string[] fragments = connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries);
+ foreach (string fragment in fragments)
+ {
+ if (fragment.ToLower().Contains("initial catalog=") || fragment.ToLower().Contains("database="))
+ {
+ databaseName = fragment.Substring(fragment.IndexOf("=") + 1);
+ }
+ else
+ {
+ if (!fragment.ToLower().Contains("attachdbfilename="))
+ {
+ masterConnectionString += fragment + ";";
+ }
+ }
+ }
+ connection = new SqlConnection(masterConnectionString);
+ try
+ {
+ using (connection)
+ {
+ connection.Open();
+ SqlCommand command;
+ if (connectionString.ToLower().Contains("attachdbfilename=")) // LocalDB
+ {
+ command = new SqlCommand("CREATE DATABASE [" + databaseName + "] ON ( NAME = '" + databaseName + "', FILENAME = '" + datadirectory + "\\" + databaseName + ".mdf')", connection);
+ }
+ else
+ {
+ command = new SqlCommand("CREATE DATABASE [" + databaseName + "]", connection);
+ }
+ command.ExecuteNonQuery();
+ exists = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ response.Message = "Can Not Create Database - " + ex.Message;
+ }
+
+ // sleep to allow SQL server to attach new database
+ Thread.Sleep(5000);
+ }
+
+ if (exists)
+ {
+ // get master initialization script and update connectionstring and alias in seed data
+ string initializationScript = "";
+ using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\Scripts\\Master.sql"))
+ {
+ initializationScript = reader.ReadToEnd();
+ }
+ initializationScript = initializationScript.Replace("{ConnectionString}", connectionString);
+ initializationScript = initializationScript.Replace("{Alias}", HttpContext.Request.Host.Value);
+
+ var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
+ .WithScript(new DbUp.Engine.SqlScript("Master.sql", initializationScript))
+ .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()); // tenant scripts should be added to /Scripts folder as Embedded Resources
+ var dbUpgrade = dbUpgradeConfig.Build();
+ if (dbUpgrade.IsUpgradeRequired())
+ {
+ var result = dbUpgrade.PerformUpgrade();
+ if (!result.Successful)
+ {
+ response.Message = result.Error.Message;
+ }
+ else
+ {
+ // update appsettings
+ string config = "";
+ using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\appsettings.json"))
+ {
+ config = reader.ReadToEnd();
+ }
+ connectionString = connectionString.Replace(datadirectory, "|DataDirectory|");
+ connectionString = connectionString.Replace(@"\", @"\\");
+ config = config.Replace("DefaultConnection\": \"", "DefaultConnection\": \"" + connectionString);
+ using (StreamWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + "\\appsettings.json"))
+ {
+ writer.WriteLine(config);
+ }
+ _config.Reload();
+ response.Success = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ response.Message = "Application Is Already Installed";
+ }
+ }
+ return response;
+ }
+
+ // GET api//installed
+ [HttpGet("installed")]
+ public GenericResponse IsInstalled()
+ {
+ var response = new GenericResponse { Success = false, Message = "" };
+
+ string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
+ string connectionString = _config.GetConnectionString("DefaultConnection");
+ connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
+
+ SqlConnection connection = new SqlConnection(connectionString);
+ try
+ {
+ using (connection)
+ {
+ connection.Open();
+ }
+ response.Success = true;
+ }
+ catch
+ {
+ // database does not exist
+ response.Message = "Database Does Not Exist";
+ }
+
+ if (response.Success)
+ {
+ var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
+ .WithScript(new DbUp.Engine.SqlScript("Master.sql", ""));
+ var dbUpgrade = dbUpgradeConfig.Build();
+ response.Success = !dbUpgrade.IsUpgradeRequired();
+ if (!response.Success)
+ {
+ response.Message = "Scripts Have Not Been Run";
+ }
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index 759515ee..86f1fa8a 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -86,20 +86,9 @@ namespace Oqtane.Controllers
[HttpPost("login")]
public async Task Login([FromBody] User user)
{
- // TODO: seed host user - this logic should be moved to installation
- IdentityUser identityuser = await identityUserManager.FindByNameAsync("host");
- if (identityuser == null)
- {
- var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password");
- if (result.Succeeded)
- {
- users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" });
- }
- }
-
if (ModelState.IsValid)
{
- identityuser = await identityUserManager.FindByNameAsync(user.Username);
+ IdentityUser identityuser = await identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
{
var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
diff --git a/Oqtane.Server/Filters/UpgradeFilter.cs b/Oqtane.Server/Filters/UpgradeFilter.cs
deleted file mode 100644
index b08203aa..00000000
--- a/Oqtane.Server/Filters/UpgradeFilter.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using System.Reflection;
-using DbUp;
-using System.Data.SqlClient;
-using System.Threading;
-using System.IO;
-using Microsoft.AspNetCore.Identity;
-
-namespace Oqtane.Filters
-{
- public class UpgradeFilter : IStartupFilter
- {
- private readonly IConfiguration _config;
-
- public UpgradeFilter(IConfiguration config)
- {
- _config = config;
- }
-
- public Action Configure(Action next)
- {
- string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
- string connectionString = _config.GetConnectionString("DefaultConnection");
- connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
-
- // check if database exists
- SqlConnection connection = new SqlConnection(connectionString);
- bool databaseExists;
- try
- {
- using (connection)
- {
- connection.Open();
- }
- databaseExists = true;
- }
- catch
- {
- databaseExists = false;
- }
-
- // create database if it does not exist
- if (!databaseExists)
- {
- string masterConnectionString = "";
- string databaseName = "";
- string[] fragments = connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries);
- foreach(string fragment in fragments)
- {
- if (fragment.ToLower().Contains("initial catalog=") || fragment.ToLower().Contains("database="))
- {
- databaseName = fragment.Substring(fragment.IndexOf("=") + 1);
- }
- else
- {
- if (!fragment.ToLower().Contains("attachdbfilename="))
- {
- masterConnectionString += fragment + ";";
- }
- }
- }
- connection = new SqlConnection(masterConnectionString);
- try
- {
- using (connection)
- {
- connection.Open();
- SqlCommand command;
- if (connectionString.ToLower().Contains("attachdbfilename=")) // LocalDB
- {
- command = new SqlCommand("CREATE DATABASE [" + databaseName + "] ON ( NAME = '" + databaseName + "', FILENAME = '" + datadirectory + "\\" + databaseName + ".mdf')", connection);
- }
- else
- {
- command = new SqlCommand("CREATE DATABASE [" + databaseName + "]", connection);
- }
- command.ExecuteNonQuery();
- }
- }
- catch (Exception ex)
- {
- throw ex;
- }
-
- // sleep to allow SQL server to attach new database
- Thread.Sleep(5000);
- }
-
- // get master initialization script and update connectionstring in seed data
- string initializationScript = "";
- using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\Scripts\\Master.sql"))
- {
- initializationScript = reader.ReadToEnd();
- }
- initializationScript = initializationScript.Replace("{ConnectionString}", connectionString);
-
- // handle upgrade scripts
- var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
- .WithScript(new DbUp.Engine.SqlScript("Master.sql", initializationScript))
- .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()); // upgrade scripts should be added to /Scripts folder as Embedded Resources
- var dbUpgrade = dbUpgradeConfig.Build();
- if (dbUpgrade.IsUpgradeRequired())
- {
- var result = dbUpgrade.PerformUpgrade();
- if (!result.Successful)
- {
- throw new Exception();
- }
- }
-
- return next;
- }
- }
-}
diff --git a/Oqtane.Server/Program.cs b/Oqtane.Server/Program.cs
index 8e6d1632..180d79f3 100644
--- a/Oqtane.Server/Program.cs
+++ b/Oqtane.Server/Program.cs
@@ -1,11 +1,8 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Blazor.Hosting;
-using Microsoft.AspNetCore;
using Microsoft.Extensions.Configuration;
-using System.IO;
-using System.Text;
-using System;
+using Microsoft.AspNetCore;
namespace Oqtane.Server
{
@@ -14,7 +11,6 @@ namespace Oqtane.Server
#if DEBUG || RELEASE
public static void Main(string[] args)
{
- PrepareConfiguration();
CreateHostBuilder(args).Build().Run();
}
@@ -29,7 +25,6 @@ namespace Oqtane.Server
#if WASM
public static void Main(string[] args)
{
- PrepareConfiguration();
BuildWebHost(args).Run();
}
@@ -42,24 +37,5 @@ namespace Oqtane.Server
.Build();
#endif
- private static void PrepareConfiguration()
- {
- string config = "";
- using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\appsettings.json"))
- {
- config = reader.ReadToEnd();
- }
- // if using LocalDB create a unique database name
- if (config.Contains("AttachDbFilename=|DataDirectory|\\\\Oqtane.mdf"))
- {
- string timestamp = DateTime.Now.ToString("yyyyMMddHHmm");
- config = config.Replace("Initial Catalog=Oqtane", "Initial Catalog=Oqtane-" + timestamp)
- .Replace("AttachDbFilename=|DataDirectory|\\\\Oqtane.mdf", "AttachDbFilename=|DataDirectory|\\\\Oqtane-" + timestamp + ".mdf");
- using (StreamWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + "\\appsettings.json"))
- {
- writer.WriteLine(config);
- }
- }
- }
}
}
diff --git a/Oqtane.Server/Repository/AliasRepository.cs b/Oqtane.Server/Repository/AliasRepository.cs
index 0cc3b365..ca9f3059 100644
--- a/Oqtane.Server/Repository/AliasRepository.cs
+++ b/Oqtane.Server/Repository/AliasRepository.cs
@@ -9,10 +9,10 @@ namespace Oqtane.Repository
{
public class AliasRepository : IAliasRepository
{
- private HostContext db;
+ private MasterContext db;
private readonly IMemoryCache _cache;
- public AliasRepository(HostContext context, IMemoryCache cache)
+ public AliasRepository(MasterContext context, IMemoryCache cache)
{
db = context;
_cache = cache;
diff --git a/Oqtane.Server/Repository/HostContext.cs b/Oqtane.Server/Repository/MasterContext.cs
similarity index 62%
rename from Oqtane.Server/Repository/HostContext.cs
rename to Oqtane.Server/Repository/MasterContext.cs
index f0f77023..21dca1dd 100644
--- a/Oqtane.Server/Repository/HostContext.cs
+++ b/Oqtane.Server/Repository/MasterContext.cs
@@ -3,9 +3,9 @@ using Oqtane.Models;
namespace Oqtane.Repository
{
- public class HostContext : DbContext
+ public class MasterContext : DbContext
{
- public HostContext(DbContextOptions options) : base(options) { }
+ public MasterContext(DbContextOptions options) : base(options) { }
public virtual DbSet Alias { get; set; }
public virtual DbSet Tenant { get; set; }
diff --git a/Oqtane.Server/Repository/TenantRepository.cs b/Oqtane.Server/Repository/TenantRepository.cs
index 9ee2c188..da7c6be9 100644
--- a/Oqtane.Server/Repository/TenantRepository.cs
+++ b/Oqtane.Server/Repository/TenantRepository.cs
@@ -10,10 +10,10 @@ namespace Oqtane.Repository
{
public class TenantRepository : ITenantRepository
{
- private HostContext db;
+ private MasterContext db;
private readonly IMemoryCache _cache;
- public TenantRepository(HostContext context, IMemoryCache cache)
+ public TenantRepository(MasterContext context, IMemoryCache cache)
{
db = context;
_cache = cache;
diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs
index 4685f9cd..bd78f450 100644
--- a/Oqtane.Server/Repository/TenantResolver.cs
+++ b/Oqtane.Server/Repository/TenantResolver.cs
@@ -1,20 +1,18 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Caching.Memory;
namespace Oqtane.Repository
{
public class TenantResolver : ITenantResolver
{
- private HostContext db;
+ private MasterContext db;
private readonly string aliasname;
private readonly IAliasRepository _aliasrepository;
private readonly ITenantRepository _tenantrepository;
- public TenantResolver(HostContext context, IHttpContextAccessor accessor, IAliasRepository aliasrepository, ITenantRepository tenantrepository)
+ public TenantResolver(MasterContext context, IHttpContextAccessor accessor, IAliasRepository aliasrepository, ITenantRepository tenantrepository)
{
db = context;
_aliasrepository = aliasrepository;
diff --git a/Oqtane.Server/Scripts/Master.sql b/Oqtane.Server/Scripts/Master.sql
index 1d76ad91..9c2cb0b4 100644
--- a/Oqtane.Server/Scripts/Master.sql
+++ b/Oqtane.Server/Scripts/Master.sql
@@ -54,10 +54,10 @@ GO
SET IDENTITY_INSERT [dbo].[Alias] ON
GO
INSERT [dbo].[Alias] ([AliasId], [Name], [TenantId], [SiteId])
-VALUES (1, N'localhost:44357', 1, 1)
+VALUES (1, N'{Alias}', 1, 1)
GO
INSERT [dbo].[Alias] ([AliasId], [Name], [TenantId], [SiteId])
-VALUES (2, N'localhost:44357/site2', 1, 2)
+VALUES (2, N'{Alias}/site2', 1, 2)
GO
SET IDENTITY_INSERT [dbo].[Alias] OFF
GO
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index ab2de131..d071f537 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -11,7 +11,6 @@ using System.Reflection;
using Microsoft.Extensions.Hosting;
using Oqtane.Modules;
using Oqtane.Repository;
-using Oqtane.Filters;
using System.IO;
using System.Runtime.Loader;
using Oqtane.Services;
@@ -25,7 +24,7 @@ namespace Oqtane.Server
{
public class Startup
{
- public IConfiguration Configuration { get; }
+ public IConfigurationRoot Configuration { get; }
public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
@@ -68,6 +67,7 @@ namespace Oqtane.Server
// register scoped core services
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
@@ -101,7 +101,7 @@ namespace Oqtane.Server
services.AddSingleton();
- services.AddDbContext(options =>
+ services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
));
@@ -143,10 +143,8 @@ namespace Oqtane.Server
services.AddMvc().AddNewtonsoftJson();
- // register database install/upgrade filter
- services.AddTransient();
-
// register singleton scoped core services
+ services.AddSingleton(Configuration);
services.AddSingleton();
services.AddSingleton();
@@ -237,7 +235,7 @@ namespace Oqtane.Server
{
services.AddSingleton();
- services.AddDbContext(options =>
+ services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
));
@@ -279,10 +277,8 @@ namespace Oqtane.Server
services.AddMvc().AddNewtonsoftJson();
- // register database install/upgrade filter
- services.AddTransient();
-
// register singleton scoped core services
+ services.AddSingleton(Configuration);
services.AddSingleton();
services.AddSingleton();
diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json
index 9e23f3b1..8138a005 100644
--- a/Oqtane.Server/appsettings.json
+++ b/Oqtane.Server/appsettings.json
@@ -1,5 +1,19 @@
{
"ConnectionStrings": {
- "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane.mdf;Initial Catalog=Oqtane;Integrated Security=SSPI;"
+ "DefaultConnection": ""
}
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/css/site.css b/Oqtane.Server/wwwroot/css/site.css
index 6774c2e9..193ec6f0 100644
--- a/Oqtane.Server/wwwroot/css/site.css
+++ b/Oqtane.Server/wwwroot/css/site.css
@@ -246,3 +246,13 @@ app {
text-align: center;
color: gray;
}
+
+.loading {
+ background: rgba(0,0,0,0.2) url('../loading.gif') no-repeat 50% 50%;
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 999;
+}
\ No newline at end of file
diff --git a/Oqtane.Server/wwwroot/loading.gif b/Oqtane.Server/wwwroot/loading.gif
new file mode 100644
index 00000000..cc70a7a8
Binary files /dev/null and b/Oqtane.Server/wwwroot/loading.gif differ
diff --git a/Oqtane.Shared/Models/GenericResponse.cs b/Oqtane.Shared/Models/GenericResponse.cs
new file mode 100644
index 00000000..2281d79f
--- /dev/null
+++ b/Oqtane.Shared/Models/GenericResponse.cs
@@ -0,0 +1,8 @@
+namespace Oqtane.Models
+{
+ public class GenericResponse
+ {
+ public bool Success { get; set; }
+ public string Message { get; set; }
+ }
+}