diff --git a/Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj b/Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj index 226153af..49ece610 100644 --- a/Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj +++ b/Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj @@ -18,11 +18,11 @@ - - + + - + diff --git a/Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs b/Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs index 71745293..1791d8c2 100644 --- a/Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs +++ b/Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs @@ -20,7 +20,7 @@ public class BookDisplayDriver : DisplayDriver "StyleCop.CSharp.ReadabilityRules", "SA1114:Parameter list should follow declaration", Justification = "Necessary for comments.")] - public override IDisplayResult Display(Book model) => + public override IDisplayResult Display(Book model, BuildDisplayContext context) => // For the sake of demonstration we use Combined() here. It makes it possible to return multiple shapes from a // driver method - won't necessarily be displayed all at once! Combine( diff --git a/Lombiq.TrainingDemo/Drivers/ColorFieldDisplayDriver.cs b/Lombiq.TrainingDemo/Drivers/ColorFieldDisplayDriver.cs index 7906ef7d..2f4532ad 100644 --- a/Lombiq.TrainingDemo/Drivers/ColorFieldDisplayDriver.cs +++ b/Lombiq.TrainingDemo/Drivers/ColorFieldDisplayDriver.cs @@ -5,7 +5,6 @@ using OrchardCore.ContentManagement.Display.ContentDisplay; using OrchardCore.ContentManagement.Display.Models; using OrchardCore.ContentManagement.Metadata.Models; -using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -56,31 +55,31 @@ public override IDisplayResult Edit(ColorField field, BuildFieldEditorContext co // NEXT STATION: Settings/ColorFieldSettings - public override async Task UpdateAsync(ColorField field, IUpdateModel updater, UpdateFieldEditorContext context) + public override async Task UpdateAsync(ColorField field, UpdateFieldEditorContext context) { + var updater = context.Updater; var viewModel = new EditColorFieldViewModel(); // Using this overload of the model updater you can specifically say what properties need to be updated. This // way you make sure no other properties will be bound to the view model. Instead of this you could put // [BindNever] attributes on the properties to make the model binder to skip those, it's up to you. - if (await updater.TryUpdateModelAsync(viewModel, Prefix, f => f.Value, f => f.ColorName)) + await updater.TryUpdateModelAsync(viewModel, Prefix, viewModel => viewModel.Value, viewModel => viewModel.ColorName); + + // Get the ColorFieldSettings to use it when validating the view model. + var settings = context.PartFieldDefinition.GetSettings(); + if (settings.Required && string.IsNullOrWhiteSpace(viewModel.Value)) { - // Get the ColorFieldSettings to use it when validating the view model. - var settings = context.PartFieldDefinition.GetSettings(); - if (settings.Required && string.IsNullOrWhiteSpace(viewModel.Value)) - { - updater.ModelState.AddModelError(Prefix, T["A value is required for {0}.", context.PartFieldDefinition.DisplayName()]); - } + updater.ModelState.AddModelError(Prefix, T["A value is required for {0}.", context.PartFieldDefinition.DisplayName()]); + } - // Also some custom validation for our ColorField hex value. - var isInvalidHexColor = !string.IsNullOrWhiteSpace(viewModel.Value) && - !RegexExpression().IsMatch(viewModel.Value); + // Also some custom validation for our ColorField hex value. + var isInvalidHexColor = !string.IsNullOrWhiteSpace(viewModel.Value) && + !RegexExpression().IsMatch(viewModel.Value); - if (isInvalidHexColor) updater.ModelState.AddModelError(Prefix, T["The given color is invalid."]); + if (isInvalidHexColor) updater.ModelState.AddModelError(Prefix, T["The given color is invalid."]); - field.ColorName = viewModel.ColorName; - field.Value = viewModel.Value; - } + field.ColorName = viewModel.ColorName; + field.Value = viewModel.Value; return await EditAsync(field, context); } diff --git a/Lombiq.TrainingDemo/Drivers/DemoSettingsDisplayDriver.cs b/Lombiq.TrainingDemo/Drivers/DemoSettingsDisplayDriver.cs index b9eb9c4f..e978970c 100644 --- a/Lombiq.TrainingDemo/Drivers/DemoSettingsDisplayDriver.cs +++ b/Lombiq.TrainingDemo/Drivers/DemoSettingsDisplayDriver.cs @@ -14,13 +14,17 @@ namespace Lombiq.TrainingDemo.Drivers; // Now this display driver abstraction is different from the one you've seen before. In Orchard Core you can connect // different objects to a master object that will be connected in the database when storing them. Site settings are // handled this way. -public class DemoSettingsDisplayDriver : SectionDisplayDriver +public class DemoSettingsDisplayDriver : SiteDisplayDriver { // Since technically we have only one SiteSettings we have separate the editors using editor groups. It's a good // idea to store the editor group ID in a publicly accessibly constant (would be much better to store it in a static // class placed in a Constants folder). public const string EditorGroupId = "Demo"; + // This abstract property of SiteDisplayDriver has to be overridden. It is used to filter the editor by group name + // under the hood, so you don't have to check it manually. + protected override string SettingsGroupId => EditorGroupId; + private readonly IAuthorizationService _authorizationService; private readonly IHttpContextAccessor _hca; @@ -32,10 +36,10 @@ public DemoSettingsDisplayDriver(IAuthorizationService authorizationService, IHt // Here's the EditAsync override to display editor for our site settings on the Dashboard. Note that it has a sync // version too. - public override async Task EditAsync(DemoSettings section, BuildEditorContext context) + public override async Task EditAsync(ISite model, DemoSettings section, BuildEditorContext context) { - // What you really don't want to is to let unauthorized users update site-level settings of your site so it's - // really advisable to create a separate permission for managing the settings or the feature related to this + // What you really don't want to is to let unauthorized users update site-level settings of your site. So it's + // really advisable to create a separate permission for managing the settings or the feature related to these // settings and use it here. We've created one that you can see in the Permissions/DemoSettingsPermissions.cs // file. if (!await IsAuthorizedToManageDemoSettingsAsync()) @@ -54,7 +58,7 @@ public override async Task EditAsync(DemoSettings section, Build .OnGroup(EditorGroupId); } - public override async Task UpdateAsync(DemoSettings section, UpdateEditorContext context) + public override async Task UpdateAsync(ISite model, DemoSettings section, UpdateEditorContext context) { // Since this DisplayDriver is for the ISite object this UpdateAsync will be called every time if a site // settings editor is being updated. To make sure that this is for our editor group check it here. @@ -67,14 +71,11 @@ public override async Task UpdateAsync(DemoSettings section, Upd } // Update the view model and the settings model as usual. - var viewModel = new DemoSettingsViewModel(); - - await context.Updater.TryUpdateModelAsync(viewModel, Prefix); - + var viewModel = await context.CreateModelAsync(Prefix); section.Message = viewModel.Message; } - return await EditAsync(section, context); + return await EditAsync(model, section, context); } private async Task IsAuthorizedToManageDemoSettingsAsync() diff --git a/Lombiq.TrainingDemo/Drivers/PersonPartDisplayDriver.cs b/Lombiq.TrainingDemo/Drivers/PersonPartDisplayDriver.cs index e41aa70b..28b6885a 100644 --- a/Lombiq.TrainingDemo/Drivers/PersonPartDisplayDriver.cs +++ b/Lombiq.TrainingDemo/Drivers/PersonPartDisplayDriver.cs @@ -2,7 +2,7 @@ using Lombiq.TrainingDemo.ViewModels; using OrchardCore.ContentManagement.Display.ContentDisplay; using OrchardCore.ContentManagement.Display.Models; -using OrchardCore.DisplayManagement.ModelBinding; +using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Views; using System.Threading.Tasks; @@ -27,7 +27,7 @@ public class PersonPartDisplayDriver : ContentPartDisplayDriver public override IDisplayResult Display(PersonPart part, BuildPartDisplayContext context) => // Here you have a shape helper with a shape name possibly and a factory. The Initialize method will instantiate // a view model from a type given as a generic parameter. It's recommended to use view models for the views like - // we're doing it here (sometimes you'd want a separate view model for the Display() and Edit(). There are + // we're doing it here (sometimes you'd want a separate view model for the Display() and Edit()). There are // helper methods to generate the shape type. GetDisplayShapeType() in this case will generate "PersonPart" by // default but this can be overridden form the part's settings under the content type's settings on the admin. // In the factory we map the content part properties to the view model; if there is any heavy lifting needed to @@ -73,16 +73,14 @@ public override IDisplayResult Edit(PersonPart part, BuildPartEditorContext cont // So we had an Edit (or EditAsync) method that generates the editor shape. Now it's time to do the content // part-specific model binding and validation. - public override async Task UpdateAsync(PersonPart part, IUpdateModel updater, UpdatePartEditorContext context) + public override async Task UpdateAsync(PersonPart part, UpdatePartEditorContext context) { - var viewModel = new PersonPartViewModel(); - // Via the IUpdateModel you will be able to use the current controller's model binding helpers here in the // driver. The prefix property will be used to distinguish between similarly named input fields when building // the editor form (so e.g. two content parts composing a content item can have an input field called "Name"). - // By default Orchard Core will use the content part name but if you have multiple drivers with editors for a + // By default, Orchard Core will use the content part name but if you have multiple drivers with editors for a // content part you need to override it in the driver. - await updater.TryUpdateModelAsync(viewModel, Prefix); + var viewModel = await context.CreateModelAsync(Prefix); // Now you can do some validation if needed. One way to do it you can simply write your own validation here or // you can do it in the view model class. @@ -90,7 +88,7 @@ public override async Task UpdateAsync(PersonPart part, IUpdateM // Go and check the ViewModels/PersonPartViewModel to see how to do it and then come back here. // Finally map the view model to the content part. By default, these changes won't be persisted if there was a - // validation error. Otherwise these will be automatically stored in the database. + // validation error. Otherwise, these will be automatically stored in the database. part.BirthDateUtc = viewModel.BirthDateUtc; part.Name = viewModel.Name; part.Handedness = viewModel.Handedness; diff --git a/Lombiq.TrainingDemo/Lombiq.TrainingDemo.csproj b/Lombiq.TrainingDemo/Lombiq.TrainingDemo.csproj index ade1d604..1e043ca8 100644 --- a/Lombiq.TrainingDemo/Lombiq.TrainingDemo.csproj +++ b/Lombiq.TrainingDemo/Lombiq.TrainingDemo.csproj @@ -22,24 +22,24 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - + +