/// <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(); }
/// <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(); }
/// <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(); }