Ejemplo n.º 1
0
        /// <summary>
        /// Starts the compile themes thread.
        /// </summary>
        private static void StartCompileThemesThread()
        {
            // compile less files
            CompileThemesThread = new Thread(() =>
            {
                /* Set to background thread so that this thread doesn't prevent Rock from shutting down. */
                var stopwatchCompileLess = Stopwatch.StartNew();

                Thread.CurrentThread.IsBackground = true;
                string messages = string.Empty;

                // Pass in a CancellationToken so we can stop compiling if Rock shuts down before it is done
                RockTheme.CompileAll(out messages, _threadCancellationTokenSource.Token);
                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    if (messages.IsNullOrWhiteSpace())
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} seconds] Less files compiled successfully. ", +stopwatchCompileLess.Elapsed.TotalSeconds));
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("RockTheme.CompileAll messages: " + messages);
                    }
                }
            });

            CompileThemesThread.Start();
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            DirectoryInfo themeDirectory = new DirectoryInfo(HttpRuntime.AppDomainAppPath + "Themes");

            var themes = RockTheme.GetThemes();

            var sortProperty = gThemes.SortProperty;

            if (sortProperty != null)
            {
                switch (sortProperty.Property)
                {
                case "Name":
                {
                    if (sortProperty.Direction == SortDirection.Ascending)
                    {
                        themes = themes.OrderBy(t => t.Name).ToList();
                    }
                    else
                    {
                        themes = themes.OrderByDescending(t => t.Name).ToList();
                    }
                    break;
                }
                }
            }

            gThemes.DataSource = themes.ToList();
            gThemes.DataBind();
        }
Ejemplo n.º 3
0
 public void OnStartup()
 {
     foreach (var theme in RockTheme.GetThemes())
     {
         string themeMessage = string.Empty;
         bool   themeSuccess = theme.CompileSass(out themeMessage);
     }
 }
Ejemplo n.º 4
0
        protected void btnSave_Click(object sender, EventArgs e)
        {
            string variableFile         = string.Format(@"{0}Themes/{1}/Styles/_variables.less", Request.PhysicalApplicationPath, _themeName);
            string variableOverrideFile = string.Format(@"{0}Themes/{1}/Styles/_variable-overrides.less", Request.PhysicalApplicationPath, _themeName);
            string cssOverrideFile      = string.Format(@"{0}Themes/{1}/Styles/_css-overrides.less", Request.PhysicalApplicationPath, _themeName);


            if (File.Exists(cssOverrideFile))
            {
                File.WriteAllText(cssOverrideFile, ceOverrides.Text);
            }

            // get list of original values
            Dictionary <string, string> originalValues = GetVariables(variableFile);

            StringBuilder overrideFile = new StringBuilder();

            foreach (var control in phThemeControls.Controls)
            {
                if (control is TextBox)
                {
                    var    textBoxControl = (TextBox)control;
                    string variableName   = textBoxControl.ID.Replace(" ", "-").ToLower();

                    // find original value
                    if (originalValues.ContainsKey(variableName))
                    {
                        string originalValue = originalValues[variableName];

                        // color picker will convert #fff to #ffffff so take that into account
                        string secondaryValue = string.Empty;
                        if (originalValue.Length == 4 && originalValue[0] == '#')
                        {
                            secondaryValue = originalValue + originalValue.Substring(1, 3);
                        }

                        if (originalValue != textBoxControl.Text && secondaryValue != textBoxControl.Text)
                        {
                            overrideFile.Append(string.Format("@{0}: {1};{2}", variableName, textBoxControl.Text, Environment.NewLine));
                        }
                    }
                }
            }

            System.IO.StreamWriter file = new System.IO.StreamWriter(variableOverrideFile);
            file.WriteLine(overrideFile);
            file.Dispose();

            // compile theme
            string messages = string.Empty;
            var    theme    = new RockTheme(_themeName);

            theme.Compile();

            NavigateToParentPage();
        }
Ejemplo n.º 5
0
        public static bool CompileSass(this RockTheme theme, out string messages)
        {
            messages = string.Empty;

            bool result = true;


            if (IntPtr.Size == 8)
            {
                // 64 bit machine
                SetDllDirectory(System.Web.HttpContext.Current.Server.MapPath("~/LibSass/win-x64/native"));
            }
            else if (IntPtr.Size == 4)
            {
                // 32 bit machine
                SetDllDirectory(System.Web.HttpContext.Current.Server.MapPath("~/LibSass/win-x86/native"));
            }

            try
            {
                DirectoryInfo themeDirectory = new DirectoryInfo(theme.AbsolutePath + @"\Styles");
                if (themeDirectory.Exists)
                {
                    List <FileInfo> files = GetSCSSFiles(themeDirectory);

                    if (files != null)
                    {
                        if (theme.AllowsCompile)
                        {
                            // don't compile files that start with an underscore
                            foreach (var file in files.Where(f => f.Name.EndsWith(".scss") && !f.Name.StartsWith("_")))
                            {
                                var content = File.ReadAllText(file.FullName);
                                var compact = Scss.ConvertToCss(content, new ScssOptions()
                                {
                                    InputFile  = file.FullName,
                                    OutputFile = file.Name.Replace(".scss", ".css"),   // Note: It will not generate the file,
                                                                                       // only used for exception reporting
                                                                                       // includes and source maps
                                    GenerateSourceMap = false,
                                });

                                File.WriteAllText(file.DirectoryName + @"\" + file.Name.Replace(".scss", ".css"), compact.Css);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                result   = false;
                messages = ex.Message;
            }
            return(result);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Handles the Click event of the gCompileTheme control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param>
        protected void gCompileTheme_Click(object sender, RowEventArgs e)
        {
            var    theme    = new RockTheme(e.RowKeyValue.ToString());
            string messages = string.Empty;

            bool compileSuccess = theme.Compile(out messages);

            if (compileSuccess)
            {
                mdThemeCompile.Show("Theme was successfully compiled.", ModalAlertType.Information);
            }
            else
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Danger;
                nbMessages.Text = string.Format("An error occurred while compiling the {0} them. Message: {1}", theme.Name, messages);
            }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Handles the Delete event of the gThemes control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param>
        protected void gThemes_Delete(object sender, RowEventArgs e)
        {
            string messages = string.Empty;

            bool deleteSuccess = RockTheme.DeleteTheme(e.RowKeyValue.ToString(), out messages);

            if (deleteSuccess)
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Success;
                nbMessages.Text = string.Format("The {0} theme has been successfully deleted.", e.RowKeyValue);
                BindGrid();
            }
            else
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Danger;
                nbMessages.Text = "Could not delete theme. Message: " + messages;
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Handles the Click event of the btnCompileTheme control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void btnCompileTheme_Click(object sender, EventArgs e)
        {
            var         rockContext = new RockContext();
            SiteService siteService = new SiteService(rockContext);
            Site        site        = siteService.Get(hfSiteId.Value.AsInteger());

            string messages = string.Empty;
            var    theme    = new RockTheme(site.Theme);
            bool   success  = theme.Compile(out messages);

            if (success)
            {
                mdThemeCompile.Show("Theme was successfully compiled.", ModalAlertType.Information);
            }
            else
            {
                mdThemeCompile.Show(string.Format("An error occurred compiling the theme {0}. Message: {1}.", site.Theme, messages), ModalAlertType.Warning);
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Handles the SaveClick event of the mdThemeClone control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void mdThemeClone_SaveClick(object sender, EventArgs e)
        {
            mdThemeClone.Hide();

            string resultMessages = string.Empty;


            var cloneWasSuccessful = RockTheme.CloneTheme(hfClonedThemeName.Value, tbNewThemeName.Text, out resultMessages);

            if (cloneWasSuccessful)
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Success;
                nbMessages.Text = string.Format("The {0} theme has been successfully cloned to {1}.", hfClonedThemeName.Value, tbNewThemeName.Text);
                BindGrid();
            }
            else
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Danger;
                nbMessages.Text = resultMessages;
            }
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Handles the Start event of the Application control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        protected void Application_Start(object sender, EventArgs e)
        {
            try
            {
                var stopwatch = System.Diagnostics.Stopwatch.StartNew();
                LogMessage(APP_LOG_FILENAME, "Application Starting...");

                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("Application_Start: {0}", RockDateTime.Now.ToString("hh:mm:ss.FFF")));
                }

                // Clear all cache
                RockMemoryCache.Clear();

                // If not migrating, set up view cache to speed up startup (Not supported when running migrations).
                var fileInfo = new FileInfo(Server.MapPath("~/App_Data/Run.Migration"));
                if (!fileInfo.Exists)
                {
                    RockInteractiveViews.SetViewFactory(Server.MapPath("~/App_Data/RockModelViews.xml"));
                }

                // Get a db context
                using (var rockContext = new RockContext())
                {
                    if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                    {
                        try
                        {
                            // default Initializer is CreateDatabaseIfNotExists, so set it to NULL so that nothing happens if there isn't a database yet
                            Database.SetInitializer <Rock.Data.RockContext>(null);
                            new AttributeService(rockContext).Get(0);
                            System.Diagnostics.Debug.WriteLine(string.Format("ConnectToDatabase {2}/{1} - {0} ms", stopwatch.Elapsed.TotalMilliseconds, rockContext.Database.Connection.Database, rockContext.Database.Connection.DataSource));
                        }
                        catch
                        {
                            // Intentionally Blank
                        }
                    }

                    //// Run any needed Rock and/or plugin migrations
                    //// NOTE: MigrateDatabase must be the first thing that touches the database to help prevent EF from creating empty tables for a new database
                    MigrateDatabase(rockContext);

                    // Preload the commonly used objects
                    stopwatch.Restart();
                    LoadCacheObjects(rockContext);

                    if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("LoadCacheObjects - {0} ms", stopwatch.Elapsed.TotalMilliseconds));
                    }

                    // Run any plugin migrations
                    MigratePlugins(rockContext);

                    RegisterRoutes(rockContext, RouteTable.Routes);

                    // Configure Rock Rest API
                    stopwatch.Restart();
                    GlobalConfiguration.Configure(Rock.Rest.WebApiConfig.Register);
                    if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("Configure WebApiConfig - {0} ms", stopwatch.Elapsed.TotalMilliseconds));
                        stopwatch.Restart();
                    }

                    // setup and launch the jobs infrastructure if running under IIS
                    bool runJobsInContext = Convert.ToBoolean(ConfigurationManager.AppSettings["RunJobsInIISContext"]);
                    if (runJobsInContext)
                    {
                        ISchedulerFactory sf;

                        // create scheduler
                        sf    = new StdSchedulerFactory();
                        sched = sf.GetScheduler();

                        // get list of active jobs
                        ServiceJobService jobService = new ServiceJobService(rockContext);
                        foreach (ServiceJob job in jobService.GetActiveJobs().ToList())
                        {
                            const string errorLoadingStatus = "Error Loading Job";
                            try
                            {
                                IJobDetail jobDetail  = jobService.BuildQuartzJob(job);
                                ITrigger   jobTrigger = jobService.BuildQuartzTrigger(job);

                                sched.ScheduleJob(jobDetail, jobTrigger);

                                //// if the last status was an error, but we now loaded successful, clear the error
                                // also, if the last status was 'Running', clear that status because it would have stopped if the app restarted
                                if (job.LastStatus == errorLoadingStatus || job.LastStatus == "Running")
                                {
                                    job.LastStatusMessage = string.Empty;
                                    job.LastStatus        = string.Empty;
                                    rockContext.SaveChanges();
                                }
                            }
                            catch (Exception ex)
                            {
                                // log the error
                                LogError(ex, null);

                                // create a friendly error message
                                string message = string.Format("Error loading the job: {0}.\n\n{2}", job.Name, job.Assembly, ex.Message);
                                job.LastStatusMessage = message;
                                job.LastStatus        = errorLoadingStatus;
                                rockContext.SaveChanges();
                            }
                        }

                        // set up the listener to report back from jobs as they complete
                        sched.ListenerManager.AddJobListener(new RockJobListener(), EverythingMatcher <JobKey> .AllJobs());

                        // start the scheduler
                        sched.Start();
                    }

                    // Force the static Liquid class to get instantiated so that the standard filters are loaded prior
                    // to the custom RockFilter.  This is to allow the custom 'Date' filter to replace the standard
                    // Date filter.
                    Liquid.UseRubyDateFormat = false;

                    //// NOTE: This means that template filters will also use CSharpNamingConvention
                    //// For example the dotliquid documentation says to do this for formatting dates:
                    //// {{ some_date_value | date:"MMM dd, yyyy" }}
                    //// However, if CSharpNamingConvention is enabled, it needs to be:
                    //// {{ some_date_value | Date:"MMM dd, yyyy" }}
                    Template.NamingConvention = new DotLiquid.NamingConventions.CSharpNamingConvention();
                    Template.FileSystem       = new RockWeb.LavaFileSystem();
                    Template.RegisterSafeType(typeof(Enum), o => o.ToString());
                    Template.RegisterFilter(typeof(Rock.Lava.RockFilters));

                    // add call back to keep IIS process awake at night and to provide a timer for the queued transactions
                    AddCallBack();

                    Rock.Security.Authorization.Load();
                }

                EntityTypeService.RegisterEntityTypes(Server.MapPath("~"));
                FieldTypeService.RegisterFieldTypes(Server.MapPath("~"));

                BundleConfig.RegisterBundles(BundleTable.Bundles);

                // mark any user login stored as 'IsOnline' in the database as offline
                MarkOnlineUsersOffline();

                SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~"));

                LogMessage(APP_LOG_FILENAME, "Application Started Successfully");
                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("Application_Started_Successfully: {0}", RockDateTime.Now.ToString("hh:mm:ss.FFF")));
                }
            }
            catch (Exception ex)
            {
                SetError66();
                throw (new Exception("Error occurred during application startup", ex));
            }

            // Update attributes for new workflow actions
            new Thread(() =>
            {
                Rock.Workflow.ActionContainer.Instance.UpdateAttributes();
            }).Start();

            // compile less files
            new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;
                RockTheme.CompileAll();
            }).Start();
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void btnSave_Click(object sender, EventArgs e)
        {
            string cssOverrideFile = string.Format(@"{0}Themes/{1}/Styles/_css-overrides.less", Request.PhysicalApplicationPath, _themeName);

            if (File.Exists(cssOverrideFile))
            {
                File.WriteAllText(cssOverrideFile, ceOverrides.Text);
            }

            // get list of original values
            Dictionary <string, string> originalValues = GetVariables(_variableFile);

            StringBuilder overrideFile = new StringBuilder();

            if (pnlFontAwesomeSettings.Visible)
            {
                overrideFile.AppendLine(FontAwesomeHelper.VariableOverridesTokens.StartRegion);

                var selectedPrimaryWeight = FontAwesomeHelper.FontAwesomeIconCssWeights.FirstOrDefault(a => a.WeightName == ddlFontAwesomeIconWeight.SelectedValue);

                overrideFile.AppendLine(string.Format("{0} {1};", FontAwesomeHelper.VariableOverridesTokens.FontWeightValueLineStart, selectedPrimaryWeight.WeightValue));
                overrideFile.AppendLine(string.Format("{0} '{1}';", FontAwesomeHelper.VariableOverridesTokens.FontWeightNameLineStart, selectedPrimaryWeight.WeightName));

                if (FontAwesomeHelper.HasFontAwesomeProKey())
                {
                    overrideFile.AppendLine("@fa-edition: 'pro';");
                }

                overrideFile.AppendLine();

                if (!selectedPrimaryWeight.IncludedInFree)
                {
                    overrideFile.AppendLine(string.Format("{0} {1};", FontAwesomeHelper.VariableOverridesTokens.FontEditionLineStart, FontAwesomeHelper.VariableOverridesTokens.FontEditionPro));
                }

                overrideFile.AppendLine("@import \"../../../Styles/FontAwesome/_rock-fa-mixins.less\";");

                foreach (var alternateFontWeightName in cblFontAwesomeAlternateFonts.Items.OfType <ListItem>().Where(a => a.Selected).Select(a => a.Value).ToList())
                {
                    var alternateFont = FontAwesomeHelper.FontAwesomeIconCssWeights.Where(a => a.WeightName == alternateFontWeightName).FirstOrDefault();
                    if (alternateFont != null)
                    {
                        string suffixParam = string.Empty;
                        overrideFile.AppendLine(
                            string.Format("{0} '{1}', 'pro' );",
                                          FontAwesomeHelper.VariableOverridesTokens.FontFaceLineStart,
                                          alternateFont.WeightName
                                          ));
                    }
                }

                overrideFile.AppendLine(FontAwesomeHelper.VariableOverridesTokens.EndRegion);
            }

            foreach (var control in phThemeControls.Controls)
            {
                if (control is TextBox)
                {
                    var    textBoxControl = ( TextBox )control;
                    string variableName   = textBoxControl.ID.Replace(" ", "-").ToLower();

                    // find original value
                    if (originalValues.ContainsKey(variableName))
                    {
                        string originalValue = originalValues[variableName];

                        // color picker will convert #fff to #ffffff so take that into account
                        string secondaryValue = string.Empty;
                        if (originalValue.Length == 4 && originalValue[0] == '#')
                        {
                            secondaryValue = originalValue + originalValue.Substring(1, 3);
                        }

                        if (originalValue.ToLower() != textBoxControl.Text.ToLower() && secondaryValue.ToLower() != textBoxControl.Text.ToLower())
                        {
                            overrideFile.Append(string.Format("@{0}: {1};{2}", variableName, textBoxControl.Text, Environment.NewLine));
                        }
                    }
                }
            }

            File.WriteAllText(_variableOverrideFile, overrideFile.ToString());

            // compile theme
            string messages = string.Empty;
            var    theme    = new RockTheme(_themeName);

            if (!theme.Compile(out messages))
            {
                nbMessages.NotificationBoxType = NotificationBoxType.Danger;
                nbMessages.Text    = string.Format("An error occurred while compiling the {0} theme.\nMessage: <pre>{1}</pre>", theme.Name, messages);;
                nbMessages.Visible = true;
            }
            else
            {
                NavigateToParentPage();
            }
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Handles the Start event of the Application control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        protected void Application_Start(object sender, EventArgs e)
        {
            RockApplicationStartupHelper.ShowDebugTimingMessage("Application Start");

            QueueInUse = false;

            /* 2020-05-20 MDP
             * Prior to Application_Start, Rock.WebStartup has an AssemblyInitializer class that runs as a PreApplicationStartMethod.
             *
             * This will call RockApplicationStartupHelper which will take care of the following
             *   -- EF Migrations
             *   -- Rock Plugin Migrations (except for ones that are in App_Code)
             *   -- Sending Version update notifications to Spark
             *   -- Pre-loading EntityTypeCache, FieldTypeCache, and AttributeCache
             *   -- Loading any attributes defined in web.config
             *   -- Registering HttpModules
             *   -- Initializing Lava
             *   -- Starting the Job Scheduler (if configured to run)
             */

            /* 2020-05-20 MDP
             * The remaining items need to be run here in Application_Start since they depend on things that don't happen until now
             * like Routes, plugin stuff in App_Code
             */

            try
            {
                // AssemblyInitializer will catch any exception that it gets and sets AssemblyInitializerException.
                // Doing this lets us do any error handling (now that RockWeb has started)
                if (AssemblyInitializer.AssemblyInitializerException != null)
                {
                    throw AssemblyInitializer.AssemblyInitializerException;
                }

                // register the App_Code assembly in the Rock.Reflection helper so that Reflection methods can search for types in it
                var appCodeAssembly = typeof(Global).Assembly;
                Rock.Reflection.SetAppCodeAssembly(appCodeAssembly);

                // Probably won't be any, but run any migrations that might be in App_Code. (Any that are dll's get run in RockApplicationStartupHelper.RunApplicationStartup())
                RockApplicationStartupHelper.RunPluginMigrations(appCodeAssembly);

                // Register Routes
                RouteTable.Routes.Clear();
                Rock.Web.RockRouteHandler.RegisterRoutes();

                // Configure Rock Rest API
                GlobalConfiguration.Configure(Rock.Rest.WebApiConfig.Register);

                // set the encryption protocols that are permissible for external SSL connections
                System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;

                RockApplicationStartupHelper.ShowDebugTimingMessage("Register Routes");

                // Perform any Rock startups
                RunStartups();

                // add call back to keep IIS process awake at night and to provide a timer for the queued transactions
                AddCallBack();

                // register any EntityTypes or FieldTypes that are discovered in Rock or any plugins (including ones in ~/App_Code)
                EntityTypeService.RegisterEntityTypes();
                FieldTypeService.RegisterFieldTypes();

                BundleConfig.RegisterBundles(BundleTable.Bundles);

                // mark any user login stored as 'IsOnline' in the database as offline
                MarkOnlineUsersOffline();

                SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~"));

                RockApplicationStartupHelper.ShowDebugTimingMessage("Register Types");

                RockApplicationStartupHelper.LogStartupMessage("Application Started Successfully");
                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} ms] Total Startup Time", (RockDateTime.Now - RockApplicationStartupHelper.StartDateTime).TotalMilliseconds));
                }

                ExceptionLogService.AlwaysLogToFile = false;
            }
            catch (Exception ex)
            {
                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("##Startup Exception##: {0}\n{1}", ex.Message, ex.StackTrace));
                }

                SetError66();
                var startupException = new RockStartupException("Error occurred during application startup", ex);
                LogError(startupException, null);
                throw startupException;
            }

            // Update attributes for new workflow actions, instead of doing them on demand
            // Not sure why we did this but this is the commit c23a4021d2ce7be96a30bae8c431c113f942f26f
            new Thread(() =>
            {
                Rock.Workflow.ActionContainer.Instance.UpdateAttributes();
            }).Start();

            // compile less files
            new Thread(() =>
            {
                var stopwatchCompileLess          = Stopwatch.StartNew();
                Thread.CurrentThread.IsBackground = true;
                string messages = string.Empty;
                RockTheme.CompileAll(out messages);
                if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment)
                {
                    if (messages.IsNullOrWhiteSpace())
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} seconds] Less files compiled successfully. ", +stopwatchCompileLess.Elapsed.TotalSeconds));
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("RockTheme.CompileAll messages: " + messages);
                    }
                }
            }).Start();
        }