Skip to content

Commit

Permalink
Merge pull request #146 from Lombiq/issue/OSOE-818
Browse files Browse the repository at this point in the history
OSOE-818: Upgrade to Orchard Core 2.0
  • Loading branch information
sarahelsaig authored Sep 25, 2024
2 parents bdf7fc7 + 51c901a commit 0684bb8
Show file tree
Hide file tree
Showing 43 changed files with 187 additions and 163 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ profiling/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea/
.build/
.testPublish/

Expand Down
10 changes: 5 additions & 5 deletions Lombiq.TrainingDemo.Tests/Services/TestedServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ public void NonExistingContentItemsShouldThrow()

mocker
.GetMock<IContentManager>()
.Verify(contentManager => contentManager.GetAsync(It.Is<string>(id => id == TestContentId)));
.Verify(contentManager => contentManager.GetAsync(It.Is<string>(id => id == TestContentId), It.IsAny<VersionOptions>()));
}

[Fact]
public async Task ContentItemsAreRetrieved()
{
// In this test we'll mock IContentManager so it actually returns something we can then verify.
// In this test we'll mock IContentManager, so it actually returns something we can then verify.

var service = CreateTestedService(out var mocker);

// Setting up an IContentManager mock that'll return a basic ContentItem placeholder.
mocker
.GetMock<IContentManager>()
.Setup(contentManager => contentManager.GetAsync(It.IsAny<string>()))
.ReturnsAsync<string, IContentManager, ContentItem>(id => new ContentItem { ContentItemId = id });
.Setup(contentManager => contentManager.GetAsync(It.IsAny<string>(), It.IsAny<VersionOptions>()))
.ReturnsAsync<string, VersionOptions, IContentManager, ContentItem>((id, _) => new ContentItem { ContentItemId = id });

var contentItem = await service.GetContentItemOrThrowAsync(TestContentId);

Expand All @@ -71,7 +71,7 @@ private static TestedService CreateTestedService(out AutoMocker mocker)
// We're using a library called AutoMocker here. It extends the Moq mocking library with the ability to
// automatically substitute injected dependencies with a mocked instance. It's a bit like a special dependency
// injection container. This way, your tested classes will get all their dependencies injected even if you don't
// explicitly register a mock or stub for the.
// explicitly register a mock or stub.

mocker = new AutoMocker();
return mocker.CreateInstance<TestedService>();
Expand Down
7 changes: 3 additions & 4 deletions Lombiq.TrainingDemo.Web/Lombiq.TrainingDemo.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="OrchardCore" Version="1.8.3" />
<PackageReference Include="OrchardCore.Logging.NLog" Version="1.8.3" />
<PackageReference Include="OrchardCore.Application.Cms.Targets" Version="1.8.3" />
<PackageReference Include="OrchardCore.Logging.NLog" Version="2.0.0" />
<PackageReference Include="OrchardCore.Application.Cms.Targets" Version="2.0.0" />
<!-- 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="1.8.3" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
.UseStaticFiles()
.UseOrchardCore();

app.Run();
await app.RunAsync();
1 change: 1 addition & 0 deletions Lombiq.TrainingDemo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
License.md = License.md
Readme.md = Readme.md
Reset-Local.ps1 = Reset-Local.ps1
NuGet.config = NuGet.config
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{C209F02F-E88D-48DC-8645-BD859AA4521A}"
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
namespace Lombiq.TrainingDemo.Controllers;

// If you have multiple admin controllers then name them whatever you want but put an [Admin] attribute on them.
public class AdminController : Controller
public sealed class AdminController : Controller
{
private readonly IContentItemDisplayManager _contentItemDisplayManager;
private readonly ISession _session;
Expand Down
7 changes: 1 addition & 6 deletions Lombiq.TrainingDemo/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ namespace Lombiq.TrainingDemo.Controllers;
// endpoints should most of the time use the "Api" authentication scheme: This is not the same that standard users are
// authenticated with (via cookies).
[Authorize(AuthenticationSchemes = "Api"), IgnoreAntiforgeryToken, AllowAnonymous]
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Major Code Smell",
"S6961:API Controllers should derive from ControllerBase instead of Controller",
Justification = "Can't be changed due to line 62. Will be applicable after an Orchard upgrade due to " +
"https://github.com/OrchardCMS/OrchardCore/issues/16186 being fixed.")]
public class ApiController : Controller
public sealed class ApiController : ControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentManager _contentManager;
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo/Controllers/AuthorizationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class AuthorizationController : Controller
public sealed class AuthorizationController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentManager _contentManager;
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo/Controllers/CacheController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class CacheController : Controller
public sealed class CacheController : Controller
{
// The actual caching is implemented in a service which we'll soon investigate.
private readonly IDateTimeCachingService _dateTimeCachingService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Lombiq.TrainingDemo.Controllers;
// This is a controller just for the sake of easy demonstration, you can do the same thing anywhere. In the Index
// action, we'll fetch content items from another tenant with the IContentManager service that you already know. This is
// just an example though, really you can access any other service as well.
public class CrossTenantServicesController : Controller
public sealed class CrossTenantServicesController : Controller
{
private readonly IShellHost _shellHost;

Expand All @@ -35,7 +35,7 @@ public class CrossTenantServicesController : Controller
// A simple route for convenience. You can access this from under /CrossTenantServices?contentItemId=ID. Here ID
// needs to be a content item ID that you can get e.g. from the URL when you open an item to edit from the admin (it
// looks something like "4da2sme18cc2k2r5d4w23d4cwj" which is NOT made by a cat walking across the keyboard!).
[Route("CrossTenantServices")]
[HttpGet, Route("CrossTenantServices")]
public async Task<string> Index(string contentItemId)
{
// If ModelState is not in a valid state we should abort the action and return null.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class DatabaseStorageController : Controller
public sealed class DatabaseStorageController : Controller
{
private readonly ISession _session;
private readonly IDisplayManager<Book> _bookDisplayManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class DisplayManagementController : Controller, IUpdateModel
public sealed class DisplayManagementController : Controller, IUpdateModel
{
// The core display management features can be used via the IDisplayManager service. The generic parameter will be
// the object that needs to be displayed on the UI somehow. Don't forget to register this generic class with the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class FileManagementController : Controller
public sealed class FileManagementController : Controller
{
// Let's have the paths here in constants to avoid repeating ourselves.
private const string TestFileRelativePath = "TrainingDemo/TestFile1.txt";
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.TrainingDemo/Controllers/PersonListController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class PersonListController : Controller
public sealed class PersonListController : Controller
{
private readonly ISession _session;
private readonly IClock _clock;
Expand Down
8 changes: 4 additions & 4 deletions Lombiq.TrainingDemo/Controllers/SiteSettingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class SiteSettingsController : Controller
public sealed class SiteSettingsController : Controller
{
private readonly ISiteService _siteService;
private readonly DemoSettings _demoSettings;
Expand All @@ -35,9 +35,9 @@ public async Task<string> SiteName() =>
// give it a value on the Dashboard if you want to see something here.
public async Task<string> DemoSettings()
{
// As mentioned the custom settings objects are serialized into the ISite object so use the .As<>() helper to
// access it as you see below.
var messageFromSiteSettings = (await _siteService.GetSiteSettingsAsync()).As<DemoSettings>().Message;
// As mentioned the custom settings objects are serialized into the ISite object. We could use the .As<>()
// helper to access it, but Orchard Core also has the built-in GetSettingsAsync<>() shortcut to do just that.
var messageFromSiteSettings = (await _siteService.GetSettingsAsync<DemoSettings>()).Message;

// But as you've seen in DemoSettings.cs our site settings are also Options so we can use it as such too:
var messageFromOptions = _demoSettings.Message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace Lombiq.TrainingDemo.Controllers;

public class YourFirstOrchardCoreController : Controller
public sealed class YourFirstOrchardCoreController : Controller
{
private readonly INotifier _notifier;
private readonly IStringLocalizer T;
Expand Down Expand Up @@ -59,7 +59,7 @@ public ActionResult Index()
}

// Let's see some custom routing here. This attribute will override the default route and use this one.
[Route("TrainingDemo/NotifyMe")]
[HttpGet, Route("TrainingDemo/NotifyMe")]
public async Task<IActionResult> NotifyMe()
{
// ILogger is an ASP.NET Core service that will write something into the specific log files. In Orchard Core
Expand Down
4 changes: 2 additions & 2 deletions Lombiq.TrainingDemo/Drivers/BookDisplayDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Lombiq.TrainingDemo.Drivers;
// drivers. Finally, you can create multiple drivers for one object and the DisplayManager will make sure that all of
// your drivers are used and their specific logic will be executed. Don't forget to register this class with the service
// provider (see: Startup.cs).
public class BookDisplayDriver : DisplayDriver<Book>
public sealed class BookDisplayDriver : DisplayDriver<Book>
{
// So we have a Book object and we want to register some display shapes. For this you need to override the Display
// or DisplayAsync methods depending on your code (only one can be used!). Ultimately, the DisplayManager will
Expand All @@ -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 sealed 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)
{
// 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, BuildEditorContext 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, Bui
}

// 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Lombiq.TrainingDemo.Drivers;
// ActivityDisplayDriver is specifically for implementing workflow tasks. It performs a simple mapping of a
// ManagePersonsPermissionCheckerTask to a ManagePersonsPermissionCheckerTaskViewModel and vice versa. Don't forget to
// register this class with the service provider (see: Startup.cs).
public class ManagePersonsPermissionCheckerTaskDisplayDriver :
public sealed class ManagePersonsPermissionCheckerTaskDisplayDriver :
ActivityDisplayDriver<ManagePersonsPermissionCheckerTask,
ManagePersonsPermissionCheckerTaskViewModel>
{
Expand Down
Loading

0 comments on commit 0684bb8

Please sign in to comment.