Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSOE-893: Fix breaking changes and use CreateModelAsync #145

Merged
merged 7 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="OrchardCore.Logging.NLog" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Application.Cms.Targets" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Logging.NLog" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Application.Cms.Targets" Version="2.0.0-preview-18300" />
<!-- This reference is not strictly needed here but included so if the web and the module projects' Orchard Core
versions go out of sync, the CI build's package consolidation validation will fail. -->
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18300" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class BookDisplayDriver : DisplayDriver<Book>
"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(
Expand Down
31 changes: 15 additions & 16 deletions Lombiq.TrainingDemo/Drivers/ColorFieldDisplayDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,31 +55,31 @@ public override IDisplayResult Edit(ColorField field, BuildFieldEditorContext co

// NEXT STATION: Settings/ColorFieldSettings

public override async Task<IDisplayResult> UpdateAsync(ColorField field, IUpdateModel updater, UpdateFieldEditorContext context)
public override async Task<IDisplayResult> 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<ColorFieldSettings>();
if (settings.Required && string.IsNullOrWhiteSpace(viewModel.Value))
{
// Get the ColorFieldSettings to use it when validating the view model.
var settings = context.PartFieldDefinition.GetSettings<ColorFieldSettings>();
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);
}
Expand Down
21 changes: 11 additions & 10 deletions Lombiq.TrainingDemo/Drivers/DemoSettingsDisplayDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ISite, DemoSettings>
public class DemoSettingsDisplayDriver : SiteDisplayDriver<DemoSettings>
{
// 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;

Expand All @@ -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<IDisplayResult> EditAsync(DemoSettings section, BuildEditorContext context)
public override async Task<IDisplayResult> EditAsync(ISite model, DemoSettings section, BuildEditorContext context)
Piedone marked this conversation as resolved.
Show resolved Hide resolved
{
// 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())
Expand All @@ -54,7 +58,7 @@ public override async Task<IDisplayResult> EditAsync(DemoSettings section, Build
.OnGroup(EditorGroupId);
}

public override async Task<IDisplayResult> UpdateAsync(DemoSettings section, UpdateEditorContext context)
public override async Task<IDisplayResult> 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.
Expand All @@ -67,14 +71,11 @@ public override async Task<IDisplayResult> 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<DemoSettingsViewModel>(Prefix);
section.Message = viewModel.Message;
}

return await EditAsync(section, context);
return await EditAsync(model, section, context);
}

private async Task<bool> IsAuthorizedToManageDemoSettingsAsync()
Expand Down
14 changes: 6 additions & 8 deletions Lombiq.TrainingDemo/Drivers/PersonPartDisplayDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,7 +27,7 @@ public class PersonPartDisplayDriver : ContentPartDisplayDriver<PersonPart>
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
Expand Down Expand Up @@ -73,24 +73,22 @@ 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<IDisplayResult> UpdateAsync(PersonPart part, IUpdateModel updater, UpdatePartEditorContext context)
public override async Task<IDisplayResult> 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<PersonPartViewModel>(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.

// 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;
Expand Down
30 changes: 15 additions & 15 deletions Lombiq.TrainingDemo/Lombiq.TrainingDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="OrchardCore.Users.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Workflows.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.BackgroundTasks" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.ContentTypes.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Data.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.DynamicCache" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Indexing.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Media.Abstractions" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Module.Targets" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.Users.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Workflows.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.BackgroundTasks" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.ContentTypes.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Data.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.DynamicCache" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Indexing.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Media.Abstractions" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.Module.Targets" Version="2.0.0-preview-18300" />
<!-- Note that you need to add a reference to OrchardCore.ResourceManagement for the basic tag helpers like <style>
and <script> to properly work when the app is built for production (and thus Razor Runtime Compilation is turned
off. OrchardCore.DisplayManagement is needed for a variety of things but also for the <shape> tag helper for the
same reason. -->
<PackageReference Include="OrchardCore.ResourceManagement" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18296" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="2.0.0-preview-18300" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18300" />
</ItemGroup>

<!-- This module requires Lombiq.HelpfulLibraries.OrchardCore, and it can be included two ways. The PackageReference
Expand All @@ -51,7 +51,7 @@
</ItemGroup>

<ItemGroup Condition="!Exists('..\..\..\Libraries\Lombiq.HelpfulLibraries\Lombiq.HelpfulLibraries.OrchardCore\Lombiq.HelpfulLibraries.OrchardCore.csproj')">
<PackageReference Include="Lombiq.HelpfulLibraries.OrchardCore" Version="10.0.1-alpha.2.occ-245" />
<PackageReference Include="Lombiq.HelpfulLibraries.OrchardCore" Version="10.0.1-alpha.4.osoe-893" />
</ItemGroup>

</Project>
11 changes: 5 additions & 6 deletions Lombiq.TrainingDemo/Settings/ColorFieldSettingsDriver.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
using Lombiq.TrainingDemo.Fields;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.ContentTypes.Editors;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace Lombiq.TrainingDemo.Settings;

// It's in the Settings folder by convention but it's the same DisplayDriver as the others; except, it also has a
// It's in the Settings folder by convention, but it's the same DisplayDriver as the others; except, it also has a
// specific base class. Don't forget to register this class with the service provider (see: Startup.cs).
public class ColorFieldSettingsDriver : ContentPartFieldDefinitionDisplayDriver<ColorField>
{
// This won't have a Display override since it wouldn't make too much sense, settings are just edited.
public override IDisplayResult Edit(ContentPartFieldDefinition model) =>
public override IDisplayResult Edit(ContentPartFieldDefinition model, BuildEditorContext context) =>
// Same old Initialize shape helper.
Initialize<ColorFieldSettings>(
$"{nameof(ColorFieldSettings)}_Edit",
Expand All @@ -25,15 +26,13 @@ public override async Task<IDisplayResult> UpdateAsync(
ContentPartFieldDefinition model,
UpdatePartFieldEditorContext context)
{
var settings = new ColorFieldSettings();

await context.Updater.TryUpdateModelAsync(settings, Prefix);
var settings = await context.CreateModelAsync<ColorFieldSettings>(Prefix);

// A content field or a content part can have multiple settings. These settings are stored in a single JSON
// object. This helper will merge our settings into this JSON object so these will be stored.
context.Builder.WithSettings(settings);

return Edit(model);
return await EditAsync(model, context);
}
}

Expand Down