// determines whether the step requires execution internal bool StepRequiresExecution(InstallSetupStep step, object instruction) { if (step == null) { throw new ArgumentNullException(nameof(step)); } var modelAttempt = instruction.TryConvertTo(step.StepType); if (!modelAttempt.Success) { throw new InvalidCastException( $"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); } var model = modelAttempt.Result; var genericStepType = typeof(InstallSetupStep <>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution"); return((bool)method.Invoke(step, new[] { model })); } catch (Exception ex) { _logger.LogError(ex, "Checking if step requires execution ({Step}) failed.", step.Name); throw; } }
// executes the step internal async Task <InstallSetupResult> ExecuteStepAsync(InstallSetupStep step, object instruction) { using (_proflog.TraceDuration <InstallApiController>($"Executing installation step: '{step.Name}'.", "Step completed")) { var modelAttempt = instruction.TryConvertTo(step.StepType); if (!modelAttempt.Success) { throw new InvalidCastException( $"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); } var model = modelAttempt.Result; var genericStepType = typeof(InstallSetupStep <>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "ExecuteAsync"); var task = (Task <InstallSetupResult>)method.Invoke(step, new[] { model }); return(await task); } catch (Exception ex) { _logger.LogError(ex, "Installation step {Step} failed.", step.Name); throw; } } }
private static object GetInstruction(InstallInstructions installModel, InstallTrackingItem item, InstallSetupStep step) { installModel.Instructions.TryGetValue(item.Name, out var instruction); // else null if (instruction is JObject jObject) { instruction = jObject?.ToObject(step.StepType); } return(instruction); }
// determines whether the step requires execution internal bool StepRequiresExecution(InstallSetupStep step, JToken instruction) { var model = instruction?.ToObject(step.StepType); var genericStepType = typeof(InstallSetupStep <>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution"); return((bool)method.Invoke(step, new[] { model })); } catch (Exception ex) { _logger.Error <InstallApiController>(ex, "Checking if step requires execution ({Step}) failed.", step.Name); throw; } }
/// <summary> /// We'll peek ahead and check if it's RequiresExecution is returning true. If it /// is not, we'll dequeue that step and peek ahead again (recurse) /// </summary> /// <param name="current"></param> /// <param name="queue"></param> /// <param name="installId"></param> /// <param name="installModel"></param> /// <returns></returns> private string IterateNextRequiredStep(InstallSetupStep current, Queue <InstallTrackingItem> queue, Guid installId, InstallInstructions installModel) { if (queue.Count > 0) { var next = queue.Peek(); var step = InstallHelper.GetAllSteps().Single(x => x.Name == next.Name); //If the current step restarts the app pool then we must simply return the next one in the queue, // we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution // will rely on that too. if (current.PerformsAppRestart) { return(step.Name); } JToken instruction = null; //If this step has any instructions then extract them if (installModel.Instructions.Any(x => x.Key == step.Name)) { instruction = installModel.Instructions[step.Name]; } //if the step requires execution then return it's name if (StepRequiresExecution(step, instruction)) { return(step.Name); } //this step no longer requires execution, this could be due to a new config change during installation, // so we'll dequeue this one from the queue and recurse queue.Dequeue(); //set this as complete InstallStatusTracker.SetComplete(installId, step.Name, null); //recurse return(IterateNextRequiredStep(step, queue, installId, installModel)); } //there is no more steps return(string.Empty); }
internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction) { using (ApplicationContext.ProfilingLogger.TraceDuration <InstallApiController>("Executing installation step: " + step.Name, "Step completed")) { var model = instruction == null ? null : instruction.ToObject(step.StepType); var genericStepType = typeof(InstallSetupStep <>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "Execute"); return((InstallSetupResult)method.Invoke(step, new object[] { model })); } catch (Exception ex) { LogHelper.Error <InstallApiController>("Installation step " + step.Name + " failed.", ex); throw; } } }
// executes the step internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction) { using (_proflog.TraceDuration <InstallApiController>($"Executing installation step: '{step.Name}'.", "Step completed")) { var model = instruction?.ToObject(step.StepType); var genericStepType = typeof(InstallSetupStep <>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "Execute"); return((InstallSetupResult)method.Invoke(step, new[] { model })); } catch (Exception ex) { _logger.Error <InstallApiController>(ex, "Installation step {Step} failed.", step.Name); throw; } } }
/// <summary> /// We'll peek ahead and check if it's RequiresExecution is returning true. If it /// is not, we'll dequeue that step and peek ahead again (recurse) /// </summary> /// <param name="current"></param> /// <param name="queue"></param> /// <param name="installId"></param> /// <param name="installModel"></param> /// <returns></returns> private string IterateSteps(InstallSetupStep current, Queue <InstallTrackingItem> queue, Guid installId, InstallInstructions installModel) { while (queue.Count > 0) { var item = queue.Peek(); // if the current step restarts the app pool then we must simply return the next one in the queue, // we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution // will rely on that too. if (current.PerformsAppRestart) { return(item.Name); } var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name); // if this step has any instructions then extract them var instruction = GetInstruction(installModel, item, step); // if the step requires execution then return its name if (StepRequiresExecution(step, instruction)) { return(step.Name); } // no longer requires execution, could be due to a new config change during installation // dequeue queue.Dequeue(); // complete _installStatusTracker.SetComplete(installId, step.Name); // and continue current = step; } return(string.Empty); }
/// <summary> /// Installs. /// </summary> public async Task <ActionResult <InstallProgressResultModel> > PostPerformInstall(InstallInstructions installModel) { if (installModel == null) { throw new ArgumentNullException(nameof(installModel)); } InstallTrackingItem[] status = InstallStatusTracker.GetStatus().ToArray(); //there won't be any statuses returned if the app pool has restarted so we need to re-read from file. if (status.Any() == false) { status = _installStatusTracker.InitializeFromFile(installModel.InstallId).ToArray(); } //create a new queue of the non-finished ones var queue = new Queue <InstallTrackingItem>(status.Where(x => x.IsComplete == false)); while (queue.Count > 0) { InstallTrackingItem item = queue.Dequeue(); InstallSetupStep step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name); // if this step has any instructions then extract them var instruction = GetInstruction(installModel, item, step); // if this step doesn't require execution then continue to the next one, this is just a fail-safe check. if (StepRequiresExecution(step, instruction) == false) { // set this as complete and continue _installStatusTracker.SetComplete(installModel.InstallId, item.Name); continue; } try { InstallSetupResult?setupData = await ExecuteStepAsync(step, instruction); // update the status _installStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData?.SavedStepData); // determine's the next step in the queue and dequeue's any items that don't need to execute var nextStep = IterateSteps(step, queue, installModel.InstallId, installModel); // check if there's a custom view to return for this step if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) { return(new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel)); } return(new InstallProgressResultModel(false, step.Name, nextStep)); } catch (Exception ex) { _logger.LogError(ex, "An error occurred during installation step {Step}", step.Name); if (ex is TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (ex is InstallException installException) { return(new ValidationErrorResult(new { view = installException.View, model = installException.ViewModel, message = installException.Message })); } return(new ValidationErrorResult(new { step = step.Name, view = "error", message = ex.Message })); } } _installStatusTracker.Reset(); return(new InstallProgressResultModel(true, string.Empty, string.Empty)); }