Enhanced RecipeExecutionSteps to provide and handle UI on the import screen.

This commit is contained in:
Sipke Schoorstra
2015-07-16 14:24:55 +01:00
parent cc59a0ed74
commit 262454322c
22 changed files with 220 additions and 26 deletions

View File

@@ -69,6 +69,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.WebPages.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
@@ -80,6 +84,7 @@
<Compile Include="Permissions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ExportActions\BuildRecipeAction.cs" />
<Compile Include="ViewModels\RecipeExecutionStepViewModel.cs" />
<Compile Include="Services\ISetupService.cs" />
<Compile Include="Services\SetupContext.cs" />
<Compile Include="Services\SetupService.cs" />

View File

@@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.Environment.Configuration;
using Orchard.Environment.Features;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Mvc;
using Orchard.Recipes.Services;
namespace Orchard.ImportExport.Providers.ImportActions {
public class UploadRecipeAction : ImportAction {
@@ -17,24 +21,27 @@ namespace Orchard.ImportExport.Providers.ImportActions {
private readonly ISetupService _setupService;
private readonly ShellSettings _shellSettings;
private readonly IFeatureManager _featureManager;
private readonly IEnumerable<IRecipeExecutionStep> _recipeExecutionSteps;
public UploadRecipeAction(
IOrchardServices orchardServices,
IImportExportService importExportService,
ISetupService setupService,
ShellSettings shellSettings,
IFeatureManager featureManager) {
IFeatureManager featureManager,
IEnumerable<IRecipeExecutionStep> recipeExecutionSteps) {
_orchardServices = orchardServices;
_importExportService = importExportService;
_setupService = setupService;
_shellSettings = shellSettings;
_featureManager = featureManager;
_recipeExecutionSteps = recipeExecutionSteps;
}
public override string Name { get { return "UploadRecipe"; } }
public HttpPostedFileBase File { get; set; }
public XDocument RecipeDocument { get; set; }
public bool ResetSite { get; set; }
public string SuperUserPassword { get; set; }
@@ -44,26 +51,63 @@ namespace Orchard.ImportExport.Providers.ImportActions {
public override dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater) {
var viewModel = new UploadRecipeViewModel {
SuperUserName = _orchardServices.WorkContext.CurrentSite.SuperUser
SuperUserName = _orchardServices.WorkContext.CurrentSite.SuperUser,
RecipeExecutionSteps = _recipeExecutionSteps.Select(x => new RecipeExecutionStepViewModel {
Name = x.Name,
DisplayName = x.DisplayName,
Description = x.Description,
Editor = x.BuildEditor(shapeFactory)
}).Where(x => x.Editor != null).ToList()
};
if (updater != null) {
if (updater.TryUpdateModel(viewModel, Prefix, null, null)) {
// Validate and read uploaded recipe file.
var request = _orchardServices.WorkContext.HttpContext.Request;
var file = request.Files["RecipeFile"];
var isInValid = false;
File = request.Files["RecipeFile"];
ResetSite = viewModel.ResetSite;
SuperUserPassword = viewModel.SuperUserPassword;
if (File == null || File.ContentLength == 0)
if (file == null || file.ContentLength == 0) {
updater.AddModelError("RecipeFile", T("No recipe file selected."));
isInValid = true;
}
if (ResetSite) {
if(String.IsNullOrWhiteSpace(viewModel.SuperUserPassword))
if (String.IsNullOrWhiteSpace(viewModel.SuperUserPassword)) {
updater.AddModelError("SuperUserPassword", T("Please specify a new password for the super user."));
else if(!String.Equals(viewModel.SuperUserPassword, viewModel.SuperUserPasswordConfirmation))
isInValid = true;
}
else if (!String.Equals(viewModel.SuperUserPassword, viewModel.SuperUserPasswordConfirmation)) {
updater.AddModelError("SuperUserPassword", T("The passwords do not match."));
isInValid = true;
}
}
if (!isInValid) {
// Read recipe file.
RecipeDocument = XDocument.Parse(new StreamReader(file.InputStream).ReadToEnd());
var orchardElement = RecipeDocument.Element("Orchard");
// Update execution steps.
var executionStepNames = viewModel.RecipeExecutionSteps.Where(x => x.IsSelected).Select(x => x.Name);
var executionStepsQuery =
from name in executionStepNames
where orchardElement.Element(name) != null
let provider = _recipeExecutionSteps.SingleOrDefault(x => x.Name == name)
where provider != null
select provider;
var executionSteps = executionStepsQuery.ToArray();
var stepUpdater = new Updater(updater, secondHalf => String.Format("{0}.{1}", Prefix, secondHalf));
foreach (var executionStep in executionSteps) {
var context = new UpdateRecipeExecutionStepContext {
RecipeDocument = RecipeDocument,
Step = orchardElement.Element(executionStep.Name)
};
executionStep.UpdateEditor(shapeFactory, stepUpdater, context);
}
}
}
}
@@ -72,7 +116,7 @@ namespace Orchard.ImportExport.Providers.ImportActions {
}
public override void Execute(ImportActionContext context) {
if (File == null || File.ContentLength == 0)
if (RecipeDocument == null)
return;
var executionId = ResetSite ? Setup() : ExecuteRecipe();
@@ -82,7 +126,7 @@ namespace Orchard.ImportExport.Providers.ImportActions {
private string Setup() {
var setupContext = new SetupContext {
DropExistingTables = true,
RecipeText = ReadRecipeFile(),
RecipeText = RecipeDocument.ToString(SaveOptions.DisableFormatting),
AdminPassword = SuperUserPassword,
AdminUsername = _orchardServices.WorkContext.CurrentSite.SuperUser,
DatabaseConnectionString = _shellSettings.DataConnectionString,
@@ -95,11 +139,7 @@ namespace Orchard.ImportExport.Providers.ImportActions {
}
private string ExecuteRecipe() {
return _importExportService.Import(ReadRecipeFile());
}
private string ReadRecipeFile() {
return new StreamReader(File.InputStream).ReadToEnd();
return _importExportService.Import(RecipeDocument.ToString(SaveOptions.DisableFormatting));
}
}
}

View File

@@ -0,0 +1,11 @@
using Orchard.Localization;
namespace Orchard.ImportExport.ViewModels {
public class RecipeExecutionStepViewModel {
public string Name { get; set; }
public LocalizedString DisplayName { get; set; }
public LocalizedString Description { get; set; }
public bool IsSelected { get; set; }
public dynamic Editor { get; set; }
}
}

View File

@@ -1,8 +1,15 @@
namespace Orchard.ImportExport.ViewModels {
using System.Collections.Generic;
namespace Orchard.ImportExport.ViewModels {
public class UploadRecipeViewModel {
public UploadRecipeViewModel() {
RecipeExecutionSteps = new List<RecipeExecutionStepViewModel>();
}
public bool ResetSite { get; set; }
public string SuperUserName { get; set; }
public string SuperUserPassword { get; set; }
public string SuperUserPasswordConfirmation { get; set; }
public IList<RecipeExecutionStepViewModel> RecipeExecutionSteps { get; set; }
}
}

View File

@@ -1,5 +1,8 @@
@using Orchard.Utility.Extensions
@model Orchard.ImportExport.ViewModels.RecipeBuilderViewModel
@{
Script.Require("ShapesBase");
}
@{
var exportStepIndex = 0;
foreach (var exportStep in Model.Steps) {

View File

@@ -1,4 +1,8 @@
@model Orchard.ImportExport.ViewModels.UploadRecipeViewModel
@using Orchard.Utility.Extensions
@model Orchard.ImportExport.ViewModels.UploadRecipeViewModel
@{
Script.Require("ShapesBase");
}
<p>
@T("Choose a recipe file to import. Please consider {0} or backing up your data first.", @Html.Link(T("exporting").Text, Url.Action("Export", "Admin", new { area = "Orchard.ImportExport" })))
</p>
@@ -9,7 +13,7 @@
<fieldset>
<div>
@Html.CheckBoxFor(m => m.ResetSite)
@Html.LabelFor(m => m.ResetSite, T("Reset Site").ToString(), new { @class = "forcheckbox" })
@Html.LabelFor(m => m.ResetSite, T("Reset Site").ToString(), new {@class = "forcheckbox"})
@Html.Hint(T("Check this option to reset your site before executing the uploaded recipe."))
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.ResetSite)">
@@ -20,9 +24,32 @@
</div>
<div>
@Html.LabelFor(m => m.SuperUserPasswordConfirmation, T("Confirm Super User Password"))
@Html.PasswordFor(m => m.SuperUserPasswordConfirmation, new { @class = "text medium" })
@Html.PasswordFor(m => m.SuperUserPasswordConfirmation, new {@class = "text medium"})
@Html.Hint(T("Repeat the password to make sure you didn't mistype anything."))
</div>
<div class="message message-Warning">@T("This will delete your database tables. Please consider creating a backup first.")</div>
</div>
</fieldset>
@{
var stepIndex = 0;
foreach (var step in Model.RecipeExecutionSteps) {
var stepName = Html.NameFor(m => m.RecipeExecutionSteps[stepIndex].IsSelected).ToString();
var stepId = stepName.HtmlClassify();
<fieldset class="recipe-builder-step recipe-builder-step-@step.Name.HtmlClassify()">
<legend>
<input type="hidden" name="@Html.NameFor(m => m.RecipeExecutionSteps[stepIndex].Name)" value="@Model.RecipeExecutionSteps[stepIndex].Name" />
<input type="checkbox" id="@stepId" name="@stepName" value="true" />
<label for="@stepId" class="forcheckbox">@step.DisplayName</label>
</legend>
@Html.Hint(step.Description)
<div data-controllerid="@stepId">
@Display(step.Editor)
</div>
</fieldset>
stepIndex++;
if (stepIndex < Model.RecipeExecutionSteps.Count) {
<hr />
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Features;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Modules.Recipes.Executors {
@@ -13,7 +14,10 @@ namespace Orchard.Modules.Recipes.Executors {
_featureManager = featureManager;
}
public override string Name { get { return "Feature"; } }
public override string Name
{
get { return "Feature"; }
}
// <Feature enable="f1,f2,f3" disable="f4" />
// Enable/Disable features.

View File

@@ -86,6 +86,7 @@
<Compile Include="Providers\Builders\RecipeMetadataStep.cs" />
<Compile Include="Providers\Builders\SettingsStep.cs" />
<Compile Include="Providers\Executors\CommandStep.cs" />
<Compile Include="ViewModels\ContentExecutionStepViewModel.cs" />
<Compile Include="Providers\Executors\ContentStep.cs" />
<Compile Include="Providers\Executors\ContentSchemaStep.cs" />
<Compile Include="Providers\Executors\MigrationStep.cs" />
@@ -142,6 +143,9 @@
<ItemGroup>
<Content Include="Views\EditorTemplates\ExportSteps\Settings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\ExecutionSteps\Content.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using Orchard.Commands;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {

View File

@@ -3,6 +3,7 @@ using System.Xml;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentTypes.Events;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {

View File

@@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Recipes.ViewModels;
namespace Orchard.Recipes.Providers.Executors {
public class ContentStep : RecipeExecutionStep {
@@ -16,7 +19,35 @@ namespace Orchard.Recipes.Providers.Executors {
_transactionManager = transactionManager;
}
public override string Name { get { return "Content"; } }
public override string Name
{
get { return "Content"; }
}
public override LocalizedString DisplayName
{
get { return T("Content"); }
}
public override LocalizedString Description {
get { return T("Provides additional coniguration for the Content recipe step."); }
}
public override dynamic BuildEditor(dynamic shapeFactory) {
return UpdateEditor(shapeFactory, null, null);
}
public override dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater, UpdateRecipeExecutionStepContext context) {
var viewModel = new ContentExecutionStepViewModel();
if (updater != null) {
if (updater.TryUpdateModel(viewModel, Prefix, null, null)) {
SetBatchSizeForDataStep(context.Step, viewModel.BatchSize);
}
}
return shapeFactory.EditorTemplate(TemplateName: "ExecutionSteps/Content", Model: viewModel, Prefix: Prefix);
}
// <Data />
// Import Data.
@@ -93,6 +124,10 @@ namespace Orchard.Recipes.Providers.Executors {
return elementDictionary;
}
private void SetBatchSizeForDataStep(XElement step, int? batchSize) {
step.SetAttributeValue("BatchSize", batchSize);
}
private int GetBatchSizeForDataStep(XElement step) {
int batchSize;
if (step.Attribute("BatchSize") == null ||

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Orchard.Data.Migration;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {

View File

@@ -6,6 +6,7 @@ using Orchard.Environment.Extensions.Models;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {

View File

@@ -4,6 +4,7 @@ using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Settings;

View File

@@ -0,0 +1,5 @@
namespace Orchard.Recipes.ViewModels {
public class ContentExecutionStepViewModel {
public int? BatchSize { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
@model Orchard.Recipes.ViewModels.ContentExecutionStepViewModel
<div>
@Html.LabelFor(m => m.BatchSize, T("Batch Size"))
@Html.TextBoxFor(m => m.BatchSize, new { @class = "text small" })
@Html.Hint(T("The batch size to use when importing the data. Leave empty to disable batched imports."))
</div>

View File

@@ -6,6 +6,7 @@ using Orchard.Environment.Extensions.Models;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Themes.Services;

View File

@@ -164,6 +164,7 @@
<Compile Include="Recipes\Models\RecipeExecutionContext.cs" />
<Compile Include="Recipes\Services\RecipeExecutionStep.cs" />
<Compile Include="Recipes\Services\RecipeExecutor.cs" />
<Compile Include="Recipes\Services\UpdateRecipeExecutionStepContext.cs" />
<Compile Include="Reports\Report.cs" />
<Compile Include="Reports\ReportEntry.cs" />
<Compile Include="Reports\ReportExtentions.cs" />

View File

@@ -1,6 +1,4 @@
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
namespace Orchard.Recipes.Models {
public class RecipeExecutionContext {
public string ExecutionId { get; set; }
public RecipeStep RecipeStep { get; set; }

View File

@@ -1,6 +1,15 @@
namespace Orchard.Recipes.Services {
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public interface IRecipeExecutionStep : IDependency {
string Name { get; }
LocalizedString DisplayName { get; }
LocalizedString Description { get; }
dynamic BuildEditor(dynamic shapeFactory);
dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater, UpdateRecipeExecutionStepContext context);
void Execute(RecipeExecutionContext context);
}
}

View File

@@ -1,6 +1,31 @@
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public abstract class RecipeExecutionStep : Component, IRecipeExecutionStep {
public abstract string Name { get; }
public virtual LocalizedString DisplayName {
get { return T(Name); }
}
public virtual LocalizedString Description {
get { return DisplayName; }
}
protected virtual string Prefix {
get { return GetType().Name; }
}
public virtual dynamic BuildEditor(dynamic shapeFactory) {
return null;
}
public virtual dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater, UpdateRecipeExecutionStepContext context) {
return null;
}
public abstract void Execute(RecipeExecutionContext context);
}
}

View File

@@ -0,0 +1,8 @@
using System.Xml.Linq;
namespace Orchard.Recipes.Services {
public class UpdateRecipeExecutionStepContext {
public XDocument RecipeDocument { get; set; }
public XElement Step { get; set; }
}
}