public virtual async Task <InstallationResult> InstallAsync(InstallationModel model, ILifetimeScope scope, CancellationToken cancelToken = default)
        {
            Guard.NotNull(model, nameof(model));

            UpdateResult(x =>
            {
                x.ProgressMessage = GetResource("Progress.CheckingRequirements");
                x.Completed       = false;
                Logger.Info(x.ProgressMessage);
            });

            if (DataSettings.DatabaseIsInstalled())
            {
                return(UpdateResult(x =>
                {
                    x.Success = true;
                    x.RedirectUrl = _urlHelper.Action("Index", "Home");
                    Logger.Info("Application already installed");
                }));
            }

            DbFactory dbFactory = null;

            try
            {
                dbFactory = DbFactory.Load(model.DataProvider, _appContext.TypeScanner);
            }
            catch (Exception ex)
            {
                return(UpdateResult(x =>
                {
                    x.Errors.Add(ex.Message);
                    Logger.Error(ex);
                }));
            }

            model.DbRawConnectionString = model.DbRawConnectionString?.Trim();

            DbConnectionStringBuilder conStringBuilder = null;

            try
            {
                // Try to create connection string
                if (model.UseRawConnectionString)
                {
                    conStringBuilder = dbFactory.CreateConnectionStringBuilder(model.DbRawConnectionString);
                }
                else
                {
                    // Structural connection string
                    var userId   = model.DbUserId;
                    var password = model.DbPassword;
                    if (model.DataProvider == "sqlserver" && model.DbAuthType == "windows")
                    {
                        userId   = null;
                        password = null;
                    }
                    conStringBuilder = dbFactory.CreateConnectionStringBuilder(model.DbServer, model.DbName, userId, password);
                }
            }
            catch (Exception ex)
            {
                return(UpdateResult(x =>
                {
                    x.Errors.Add(GetResource("ConnectionStringWrongFormat"));
                    Logger.Error(ex, x.Errors.Last());
                }));
            }

            // Check FS access rights
            CheckFileSystemAccessRights(GetInstallResult().Errors);

            if (GetInstallResult().HasErrors)
            {
                return(UpdateResult(x =>
                {
                    x.Completed = true;
                    x.Success = false;
                    x.RedirectUrl = null;
                    Logger.Error("Aborting installation.");
                }));
            }

            ILifetimeScope richScope = null;
            SmartDbContext dbContext = null;
            var            shouldDeleteDbOnFailure = false;

            try
            {
                cancelToken.ThrowIfCancellationRequested();

                var conString = conStringBuilder.ConnectionString;
                var settings  = DataSettings.Instance;

                settings.AppVersion       = SmartstoreVersion.Version;
                settings.DbFactory        = dbFactory;
                settings.ConnectionString = conString;

                // So that DataSettings.DatabaseIsInstalled() returns false during installation.
                DataSettings.SetTestMode(true);

                // Resolve SeedData instance from primary language
                var lazyLanguage = GetAppLanguage(model.PrimaryLanguage);
                if (lazyLanguage == null)
                {
                    return(UpdateResult(x =>
                    {
                        x.Errors.Add(GetResource("Install.LanguageNotRegistered").FormatInvariant(model.PrimaryLanguage));
                        x.Completed = true;
                        x.Success = false;
                        x.RedirectUrl = null;
                        Logger.Error(x.Errors.Last());
                    }));
                }

                // Create the DataContext
                dbContext = (SmartDbContext)dbFactory.CreateApplicationDbContext(
                    conString,
                    _appContext.AppConfiguration.DbMigrationCommandTimeout,
                    SmartDbContext.MigrationHistoryTableName);

                // Delete only on failure if WE created the database.
                var canConnectDatabase = await dbContext.Database.CanConnectAsync(cancelToken);

                shouldDeleteDbOnFailure = !canConnectDatabase;

                cancelToken.ThrowIfCancellationRequested();

                // Create Language domain object from lazyLanguage
                var languages       = dbContext.Languages;
                var primaryLanguage = new Language
                {
                    Name              = lazyLanguage.Metadata.Name,
                    LanguageCulture   = lazyLanguage.Metadata.Culture,
                    UniqueSeoCode     = lazyLanguage.Metadata.UniqueSeoCode,
                    FlagImageFileName = lazyLanguage.Metadata.FlagImageFileName
                };

                // Build the seed configuration model
                var seedConfiguration = new SeedDataConfiguration
                {
                    DefaultUserName     = model.AdminEmail,
                    DefaultUserPassword = model.AdminPassword,
                    SeedSampleData      = model.InstallSampleData,
                    Data                    = lazyLanguage.Value,
                    Language                = primaryLanguage,
                    StoreMediaInDB          = model.MediaStorage == "db",
                    ProgressMessageCallback = msg => UpdateResult(x => x.ProgressMessage = GetResource(msg))
                };

                var seeder = new InstallationDataSeeder(seedConfiguration, Logger, _httpContextAccessor);

                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.BuildingDatabase");
                    Logger.Info(x.ProgressMessage);
                });

                //// TEST
                //return UpdateResult(x =>
                //{
                //    x.Completed = true;
                //    x.Success = true;
                //    //x.RedirectUrl = _urlHelper.Action("Index", "Home");
                //    Logger.Info("Installation completed successfully");
                //});

                // ===>>> Actually performs database creation.
                await dbContext.Database.MigrateAsync(cancelToken);

                cancelToken.ThrowIfCancellationRequested();

                // ===>>> Seeds data.
                await seeder.SeedAsync(dbContext);

                cancelToken.ThrowIfCancellationRequested();

                // ... Install modules

                // Detect media file tracks (must come after plugins installation)
                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.ProcessingMedia");
                    Logger.Info(x.ProgressMessage);
                });

                richScope = scope.BeginLifetimeScope(c =>
                {
                    // At this stage (after the database has been created and seeded completely) we can create a richer service scope
                    // to minimize the risk of dependency resolution exceptions during more complex install operations.
                    c.RegisterInstance(dbContext);
                    c.Register <IStoreContext>(cc => new StoreContext(cc.Resolve <ICacheFactory>(), null, _httpContextAccessor, cc.Resolve <IActionContextAccessor>()));
                    c.Register <ISettingFactory>(cc => new SettingFactory(cc.Resolve <ICacheManager>(), null, _httpContextAccessor));
                });

                var mediaTracker = richScope.Resolve <IMediaTracker>();
                foreach (var album in richScope.Resolve <IAlbumRegistry>().GetAlbumNames(true))
                {
                    await mediaTracker.DetectAllTracksAsync(album, cancelToken);
                }

                cancelToken.ThrowIfCancellationRequested();

                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.Finalizing");
                    Logger.Info(x.ProgressMessage);
                });

                // Now persist settings
                settings.Save();

                // SUCCESS: Redirect to home page
                return(UpdateResult(x =>
                {
                    x.Completed = true;
                    x.Success = true;
                    x.RedirectUrl = _urlHelper.Action("Index", "Home");
                    Logger.Info("Installation completed successfully");
                }));
            }
            catch (Exception ex)
            {
                Logger.Error(ex);

                // Delete Db if it was auto generated
                if (dbContext != null && shouldDeleteDbOnFailure)
                {
                    try
                    {
                        Logger.Debug("Deleting database");
                        await dbContext.Database.EnsureDeletedAsync(cancelToken);
                    }
                    catch { }
                }

                // Clear provider settings if something got wrong
                DataSettings.Delete();

                var msg           = ex.Message;
                var realException = ex;
                while (realException.InnerException != null)
                {
                    realException = realException.InnerException;
                }

                if (!object.Equals(ex, realException))
                {
                    msg += " (" + realException.Message + ")";
                }

                return(UpdateResult(x =>
                {
                    x.Errors.Add(string.Format(GetResource("SetupFailed"), msg));
                    x.Success = false;
                    x.Completed = true;
                    x.RedirectUrl = null;
                }));
            }
            finally
            {
                if (dbContext != null)
                {
                    dbContext.Dispose();
                }

                if (richScope != null)
                {
                    richScope.Dispose();
                }
            }
        }
Esempio n. 2
0
        protected virtual InstallationResult InstallCore(ILifetimeScope scope, InstallModel model)
        {
            UpdateResult(x =>
            {
                x.ProgressMessage = _locService.GetResource("Progress.CheckingRequirements");
                x.Completed       = false;
                Logger.Info(x.ProgressMessage);
            });

            if (DataSettings.DatabaseIsInstalled())
            {
                return(UpdateResult(x =>
                {
                    x.Success = true;
                    x.RedirectUrl = Url.Action("Index", "Home");
                    Logger.Info("Application already installed");
                }));
            }

            // set page timeout to 5 minutes
            this.Server.ScriptTimeout = 300;

            if (model.DatabaseConnectionString != null)
            {
                model.DatabaseConnectionString = model.DatabaseConnectionString.Trim();
            }

            // SQL Server
            if (model.DataProvider.Equals("sqlserver", StringComparison.InvariantCultureIgnoreCase))
            {
                if (model.SqlConnectionInfo.Equals("sqlconnectioninfo_raw", StringComparison.InvariantCultureIgnoreCase))
                {
                    // raw connection string
                    if (string.IsNullOrEmpty(model.DatabaseConnectionString))
                    {
                        UpdateResult(x =>
                        {
                            x.Errors.Add(_locService.GetResource("ConnectionStringRequired"));
                            Logger.Error(x.Errors.Last());
                        });
                    }

                    try
                    {
                        // try to create connection string
                        new SqlConnectionStringBuilder(model.DatabaseConnectionString);
                    }
                    catch (Exception ex)
                    {
                        UpdateResult(x =>
                        {
                            x.Errors.Add(_locService.GetResource("ConnectionStringWrongFormat"));
                            Logger.Error(ex, x.Errors.Last());
                        });
                    }
                }
                else
                {
                    // values
                    if (string.IsNullOrEmpty(model.SqlServerName))
                    {
                        UpdateResult(x =>
                        {
                            x.Errors.Add(_locService.GetResource("SqlServerNameRequired"));
                            Logger.Error(x.Errors.Last());
                        });
                    }

                    if (string.IsNullOrEmpty(model.SqlDatabaseName))
                    {
                        UpdateResult(x =>
                        {
                            x.Errors.Add(_locService.GetResource("DatabaseNameRequired"));
                            Logger.Error(x.Errors.Last());
                        });
                    }

                    // authentication type
                    if (model.SqlAuthenticationType.Equals("sqlauthentication", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // SQL authentication
                        if (string.IsNullOrEmpty(model.SqlServerUsername))
                        {
                            UpdateResult(x =>
                            {
                                x.Errors.Add(_locService.GetResource("SqlServerUsernameRequired"));
                                Logger.Error(x.Errors.Last());
                            });
                        }

                        if (string.IsNullOrEmpty(model.SqlServerPassword))
                        {
                            UpdateResult(x =>
                            {
                                x.Errors.Add(_locService.GetResource("SqlServerPasswordRequired"));
                                Logger.Error(x.Errors.Last());
                            });
                        }
                    }
                }
            }


            // Consider granting access rights to the resource to the ASP.NET request identity.
            // ASP.NET has a base process identity
            // (typically {MACHINE}\ASPNET on IIS 5 or Network Service on IIS 6 and IIS 7,
            // and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating.
            // If the application is impersonating via <identity impersonate="true"/>,
            // the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user.
            var webHelper = scope.Resolve <IWebHelper>();
            // validate permissions
            var dirsToCheck = FilePermissionHelper.GetDirectoriesWrite(webHelper);

            foreach (string dir in dirsToCheck)
            {
                if (!FilePermissionHelper.CheckPermissions(dir, false, true, true, false))
                {
                    UpdateResult(x =>
                    {
                        x.Errors.Add(string.Format(_locService.GetResource("ConfigureDirectoryPermissions"), WindowsIdentity.GetCurrent().Name, dir));
                        Logger.Error(x.Errors.Last());
                    });
                }
            }

            var filesToCheck = FilePermissionHelper.GetFilesWrite(webHelper);

            foreach (string file in filesToCheck)
            {
                if (!FilePermissionHelper.CheckPermissions(file, false, true, true, true))
                {
                    UpdateResult(x =>
                    {
                        x.Errors.Add(string.Format(_locService.GetResource("ConfigureFilePermissions"), WindowsIdentity.GetCurrent().Name, file));
                        Logger.Error(x.Errors.Last());
                    });
                }
            }

            if (GetInstallResult().HasErrors)
            {
                return(UpdateResult(x =>
                {
                    x.Completed = true;
                    x.Success = false;
                    x.RedirectUrl = null;
                    Logger.Error("Aborting installation.");
                }));
            }
            else
            {
                SmartObjectContext dbContext = null;
                var shouldDeleteDbOnFailure  = false;

                try
                {
                    string connectionString = null;
                    if (model.DataProvider.Equals("sqlserver", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //SQL Server

                        if (model.SqlConnectionInfo.Equals("sqlconnectioninfo_raw", StringComparison.InvariantCultureIgnoreCase))
                        {
                            //raw connection string

                            //we know that MARS option is required when using Entity Framework
                            //let's ensure that it's specified
                            var sqlCsb = new SqlConnectionStringBuilder(model.DatabaseConnectionString);
                            sqlCsb.MultipleActiveResultSets = true;
                            connectionString = sqlCsb.ToString();
                        }
                        else
                        {
                            // values
                            connectionString = CreateConnectionString(
                                model.SqlAuthenticationType == "windowsauthentication",
                                model.SqlServerName, model.SqlDatabaseName,
                                model.SqlServerUsername, model.SqlServerPassword);
                        }

                        if (model.SqlServerCreateDatabase)
                        {
                            if (!SqlServerDatabaseExists(connectionString))
                            {
                                //create database
                                var collation             = model.UseCustomCollation ? model.Collation : "";
                                var errorCreatingDatabase = CreateDatabase(connectionString, collation);
                                if (errorCreatingDatabase.HasValue())
                                {
                                    return(UpdateResult(x =>
                                    {
                                        x.Errors.Add(errorCreatingDatabase);
                                        x.Completed = true;
                                        x.Success = false;
                                        x.RedirectUrl = null;
                                        Logger.Error(errorCreatingDatabase);
                                    }));
                                }
                                else
                                {
                                    // Database cannot be created sometimes. Weird! Seems to be Entity Framework issue
                                    // that's just wait 3 seconds
                                    Thread.Sleep(3000);

                                    shouldDeleteDbOnFailure = true;
                                }
                            }
                        }
                        else
                        {
                            // check whether database exists
                            if (!SqlServerDatabaseExists(connectionString))
                            {
                                return(UpdateResult(x =>
                                {
                                    x.Errors.Add(_locService.GetResource("DatabaseNotExists"));
                                    x.Completed = true;
                                    x.Success = false;
                                    x.RedirectUrl = null;
                                    Logger.Error(x.Errors.Last());
                                }));
                            }
                        }
                    }
                    else
                    {
                        // SQL CE
                        string databaseFileName = "SmartStore.Db.sdf";
                        string databasePath     = @"|DataDirectory|\Tenants\{0}\{1}".FormatInvariant(DataSettings.Current.TenantName, databaseFileName);
                        connectionString = "Data Source=" + databasePath + "; Persist Security Info=False";

                        // drop database if exists
                        string databaseFullPath = HostingEnvironment.MapPath(DataSettings.Current.TenantPath.EnsureEndsWith("/")) + databaseFileName;
                        if (System.IO.File.Exists(databaseFullPath))
                        {
                            System.IO.File.Delete(databaseFullPath);
                        }

                        shouldDeleteDbOnFailure = true;
                    }

                    // save settings
                    var dataProvider = model.DataProvider;
                    var settings     = DataSettings.Current;
                    settings.AppVersion           = SmartStoreVersion.Version;
                    settings.DataProvider         = dataProvider;
                    settings.DataConnectionString = connectionString;
                    settings.Save();

                    // init data provider
                    var dataProviderInstance = scope.Resolve <IEfDataProvider>();

                    // Although obsolete we have no other chance than using this here.
                    // Delegating this to DbConfiguration is not possible during installation.
#pragma warning disable 618
                    Database.DefaultConnectionFactory = dataProviderInstance.GetConnectionFactory();
#pragma warning restore 618

                    // resolve SeedData instance from primary language
                    var lazyLanguage = _locService.GetAppLanguage(model.PrimaryLanguage);
                    if (lazyLanguage == null)
                    {
                        return(UpdateResult(x =>
                        {
                            x.Errors.Add(_locService.GetResource("Install.LanguageNotRegistered").FormatInvariant(model.PrimaryLanguage));
                            x.Completed = true;
                            x.Success = false;
                            x.RedirectUrl = null;
                            Logger.Error(x.Errors.Last());
                        }));
                    }

                    // create the DataContext
                    dbContext = new SmartObjectContext();

                    // AuditableHook must run during install
                    dbContext.DbHookHandler = new DefaultDbHookHandler(new[]
                    {
                        new Lazy <IDbHook, HookMetadata>(() => new AuditableHook(), HookMetadata.Create <AuditableHook>(typeof(IAuditable), true), false)
                    });

                    // IMPORTANT: Migration would run way too early otherwise
                    Database.SetInitializer <SmartObjectContext>(null);

                    // create Language domain object from lazyLanguage
                    var languages       = dbContext.Set <Language>();
                    var primaryLanguage = languages.Create();                     // create a proxied type, resources cannot be saved otherwise
                    primaryLanguage.Name              = lazyLanguage.Metadata.Name;
                    primaryLanguage.LanguageCulture   = lazyLanguage.Metadata.Culture;
                    primaryLanguage.UniqueSeoCode     = lazyLanguage.Metadata.UniqueSeoCode;
                    primaryLanguage.FlagImageFileName = lazyLanguage.Metadata.FlagImageFileName;

                    // Build the seed configuration model
                    var seedConfiguration = new SeedDataConfiguration
                    {
                        DefaultUserName     = model.AdminEmail,
                        DefaultUserPassword = model.AdminPassword,
                        SeedSampleData      = model.InstallSampleData,
                        Data                    = lazyLanguage.Value,
                        Language                = primaryLanguage,
                        StoreMediaInDB          = model.MediaStorage == "db",
                        ProgressMessageCallback = msg => UpdateResult(x => x.ProgressMessage = _locService.GetResource(msg))
                    };

                    var seeder = new InstallDataSeeder(seedConfiguration, Logger);
                    Database.SetInitializer(new InstallDatabaseInitializer()
                    {
                        DataSeeders = new[] { seeder }
                    });

                    UpdateResult(x =>
                    {
                        x.ProgressMessage = _locService.GetResource("Progress.BuildingDatabase");
                        Logger.Info(x.ProgressMessage);
                    });
                    // ===>>> actually performs the installation by calling "InstallDataSeeder.Seed()" internally.
                    dbContext.Database.Initialize(true);

                    // Install plugins.
                    PluginManager.MarkAllPluginsAsUninstalled();
                    var pluginFinder = scope.Resolve <IPluginFinder>();
                    var plugins      = pluginFinder.GetPlugins <IPlugin>(false)
                                       //.ToList()
                                       .OrderBy(x => x.PluginDescriptor.Group)
                                       .ThenBy(x => x.PluginDescriptor.DisplayOrder)
                                       .ToList();

                    var ignoredPluginsSetting            = CommonHelper.GetAppSetting <string>("sm:PluginsIgnoredDuringInstallation");
                    var pluginsIgnoredDuringInstallation = String.IsNullOrEmpty(ignoredPluginsSetting) ?
                                                           new List <string>() :
                                                           ignoredPluginsSetting
                                                           .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                                                           .Select(x => x.Trim())
                                                           .ToList();

                    if (pluginsIgnoredDuringInstallation.Count > 0)
                    {
                        plugins = plugins.Where(x => !pluginsIgnoredDuringInstallation.Contains(x.PluginDescriptor.SystemName, StringComparer.OrdinalIgnoreCase)).ToList();
                    }

                    var pluginsCount = plugins.Count;
                    var idx          = 0;

                    using (var dbScope = new DbContextScope(autoDetectChanges: false, hooksEnabled: false))
                    {
                        foreach (var plugin in plugins)
                        {
                            try
                            {
                                idx++;
                                UpdateResult(x =>
                                {
                                    x.ProgressMessage = _locService.GetResource("Progress.InstallingPlugins").FormatInvariant(idx, pluginsCount);
                                    Logger.InfoFormat("Installing plugin '{0}'.", plugin.PluginDescriptor.FriendlyName ?? plugin.PluginDescriptor.SystemName);
                                });
                                plugin.Install();
                                dbScope.Commit();
                            }
                            catch (Exception ex)
                            {
                                Logger.Error(ex);

                                if (plugin.PluginDescriptor.Installed)
                                {
                                    PluginManager.MarkPluginAsUninstalled(plugin.PluginDescriptor.SystemName);
                                }
                            }
                        }
                    }

                    // Detect media file tracks (must come after plugins installation)
                    UpdateResult(x =>
                    {
                        x.ProgressMessage = _locService.GetResource("Progress.ProcessingMedia");
                        Logger.Info(x.ProgressMessage);
                    });
                    var mediaTracker = scope.Resolve <IMediaTracker>();
                    foreach (var album in scope.Resolve <IAlbumRegistry>().GetAlbumNames(true))
                    {
                        mediaTracker.DetectAllTracks(album, false);
                    }

                    UpdateResult(x =>
                    {
                        x.ProgressMessage = _locService.GetResource("Progress.Finalizing");
                        Logger.Info(x.ProgressMessage);
                    });

                    // Do not ignore settings migrated by data seeder (e.g. default media storage provider).
                    scope.Resolve <ISettingService>().ClearCache();

                    // SUCCESS: Redirect to home page
                    return(UpdateResult(x =>
                    {
                        x.Completed = true;
                        x.Success = true;
                        x.RedirectUrl = Url.Action("Index", "Home");
                        Logger.Info("Installation completed successfully");
                    }));
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);

                    // Clear provider settings if something got wrong
                    DataSettings.Delete();

                    // Delete Db if it was auto generated
                    if (dbContext != null && shouldDeleteDbOnFailure)
                    {
                        try
                        {
                            Logger.Debug("Deleting database");
                            dbContext.Database.Delete();
                        }
                        catch { }
                    }

                    var msg           = ex.Message;
                    var realException = ex;
                    while (realException.InnerException != null)
                    {
                        realException = realException.InnerException;
                    }

                    if (!Object.Equals(ex, realException))
                    {
                        msg += " (" + realException.Message + ")";
                    }

                    return(UpdateResult(x =>
                    {
                        x.Errors.Add(string.Format(_locService.GetResource("SetupFailed"), msg));
                        x.Success = false;
                        x.Completed = true;
                        x.RedirectUrl = null;
                    }));
                }
                finally
                {
                    if (dbContext != null)
                    {
                        dbContext.Dispose();
                    }
                }
            }
        }
        public virtual async Task <InstallationResult> InstallAsync(InstallationModel model, ILifetimeScope scope)
        {
            // TODO: (core) CancellationToken
            Guard.NotNull(model, nameof(model));

            UpdateResult(x =>
            {
                x.ProgressMessage = GetResource("Progress.CheckingRequirements");
                x.Completed       = false;
                Logger.Info(x.ProgressMessage);
            });

            if (DataSettings.DatabaseIsInstalled())
            {
                return(UpdateResult(x =>
                {
                    x.Success = true;
                    x.RedirectUrl = _urlHelper.Action("Index", "Home");
                    Logger.Info("Application already installed");
                }));
            }

            //// set page timeout to 5 minutes
            //this.Server.ScriptTimeout = 300;

            DbFactory dbFactory = null;

            try
            {
                dbFactory = DbFactory.Load(model.DataProvider, _appContext.TypeScanner);
            }
            catch
            {
                return(UpdateResult(x =>
                {
                    x.Errors.Add(GetResource("ConnectionStringRequired")); // TODO: (core) ErrMessage
                    Logger.Error(x.Errors.Last());
                }));
            }

            model.DatabaseConnectionString = model.DatabaseConnectionString?.Trim();

            DbConnectionStringBuilder conStringBuilder = null;

            if (model.SqlConnectionInfo.EqualsNoCase("sqlconnectioninfo_raw"))
            {
                // Raw connection string
                if (string.IsNullOrEmpty(model.DatabaseConnectionString))
                {
                    return(UpdateResult(x =>
                    {
                        x.Errors.Add(GetResource("ConnectionStringRequired"));
                        Logger.Error(x.Errors.Last());
                    }));
                }

                try
                {
                    // Try to create connection string
                    conStringBuilder = dbFactory.CreateConnectionStringBuilder(model.DatabaseConnectionString);
                }
                catch (Exception ex)
                {
                    return(UpdateResult(x =>
                    {
                        x.Errors.Add(GetResource("ConnectionStringWrongFormat"));
                        Logger.Error(ex, x.Errors.Last());
                    }));
                }
            }
            else
            {
                // Structural connection string
                // TODO: (core) ...
            }

            // TODO: (core) Access rights check ...

            if (GetInstallResult().HasErrors)
            {
                return(UpdateResult(x =>
                {
                    x.Completed = true;
                    x.Success = false;
                    x.RedirectUrl = null;
                    Logger.Error("Aborting installation.");
                }));
            }

            SmartDbContext dbContext = null;
            var            shouldDeleteDbOnFailure = false;

            try
            {
                var conString = conStringBuilder.ConnectionString;
                var settings  = DataSettings.Instance;

                settings.AppVersion       = SmartstoreVersion.Version;
                settings.DbFactory        = dbFactory;
                settings.ConnectionString = conString;

                // So that DataSettings.DatabaseIsInstalled() returns false during installation.
                DataSettings.SetTestMode(true);

                // resolve SeedData instance from primary language
                var lazyLanguage = GetAppLanguage(model.PrimaryLanguage);
                if (lazyLanguage == null)
                {
                    return(UpdateResult(x =>
                    {
                        x.Errors.Add(GetResource("Install.LanguageNotRegistered").FormatInvariant(model.PrimaryLanguage));
                        x.Completed = true;
                        x.Success = false;
                        x.RedirectUrl = null;
                        Logger.Error(x.Errors.Last());
                    }));
                }

                // Create the DataContext
                dbContext = (SmartDbContext)dbFactory.CreateApplicationDbContext(
                    conString,
                    _appContext.AppConfiguration.DbMigrationCommandTimeout,
                    HistoryRepository.DefaultTableName + "_Core"); // TODO: (core) Make const for core migration table name

                // Delete only on failure if WE created the database.
                shouldDeleteDbOnFailure = !await dbContext.Database.CanConnectAsync();

                // Create Language domain object from lazyLanguage
                var languages       = dbContext.Languages;
                var primaryLanguage = new Language
                {
                    Name              = lazyLanguage.Metadata.Name,
                    LanguageCulture   = lazyLanguage.Metadata.Culture,
                    UniqueSeoCode     = lazyLanguage.Metadata.UniqueSeoCode,
                    FlagImageFileName = lazyLanguage.Metadata.FlagImageFileName
                };

                // Build the seed configuration model
                var seedConfiguration = new SeedDataConfiguration
                {
                    DefaultUserName     = model.AdminEmail,
                    DefaultUserPassword = model.AdminPassword,
                    SeedSampleData      = model.InstallSampleData,
                    Data                    = lazyLanguage.Value,
                    Language                = primaryLanguage,
                    StoreMediaInDB          = model.MediaStorage == "db",
                    ProgressMessageCallback = msg => UpdateResult(x => x.ProgressMessage = GetResource(msg))
                };

                var seeder = new InstallationDataSeeder(seedConfiguration, Logger, _httpContextAccessor);

                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.BuildingDatabase");
                    Logger.Info(x.ProgressMessage);
                });

                // ===>>> Actually performs database creation.
                await dbContext.Database.MigrateAsync();

                // ===>>> Seeds data.
                await seeder.SeedAsync(dbContext);

                // ...

                // Detect media file tracks (must come after plugins installation)
                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.ProcessingMedia");
                    Logger.Info(x.ProgressMessage);
                });

                using (var scope2 = scope.BeginLifetimeScope(c =>
                {
                    c.RegisterInstance(dbContext);
                    c.Register <IStoreContext>(cc => new StoreContext(cc.Resolve <ICacheFactory>(), null, _httpContextAccessor, cc.Resolve <IActionContextAccessor>()));
                    c.Register <ISettingFactory>(cc => new SettingFactory(cc.Resolve <ICacheManager>(), null, _httpContextAccessor));
                }))
                {
                    var mediaTracker = scope2.Resolve <IMediaTracker>();
                    foreach (var album in scope2.Resolve <IAlbumRegistry>().GetAlbumNames(true))
                    {
                        await mediaTracker.DetectAllTracksAsync(album);
                    }
                }

                UpdateResult(x =>
                {
                    x.ProgressMessage = GetResource("Progress.Finalizing");
                    Logger.Info(x.ProgressMessage);
                });

                // Now persist settings
                settings.Save();

                // SUCCESS: Redirect to home page
                return(UpdateResult(x =>
                {
                    x.Completed = true;
                    x.Success = true;
                    x.RedirectUrl = _urlHelper.Action("Index", "Home");
                    Logger.Info("Installation completed successfully");
                }));
            }
            catch (Exception ex)
            {
                Logger.Error(ex);

                // Delete Db if it was auto generated
                if (dbContext != null && shouldDeleteDbOnFailure)
                {
                    try
                    {
                        Logger.Debug("Deleting database");
                        await dbContext.Database.EnsureDeletedAsync();
                    }
                    catch { }
                }

                // Clear provider settings if something got wrong
                DataSettings.Delete();

                var msg           = ex.Message;
                var realException = ex;
                while (realException.InnerException != null)
                {
                    realException = realException.InnerException;
                }

                if (!object.Equals(ex, realException))
                {
                    msg += " (" + realException.Message + ")";
                }

                return(UpdateResult(x =>
                {
                    x.Errors.Add(string.Format(GetResource("SetupFailed"), msg));
                    x.Success = false;
                    x.Completed = true;
                    x.RedirectUrl = null;
                }));
            }
            finally
            {
                if (dbContext != null)
                {
                    dbContext.Dispose();
                }
            }
        }