From a289ea8accf0098a26c7537470b562ddf70172f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Thu, 29 Jun 2023 13:36:37 +0200 Subject: [PATCH 1/8] fix tag helper by adding missing requirement --- .../TagHelpers/EditorFieldSetTagHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs index 8d79c8bb..1271c972 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs @@ -11,7 +11,7 @@ namespace Lombiq.HelpfulLibraries.OrchardCore.TagHelpers; -[HtmlTargetElement("fieldset", Attributes = "asp-for")] +[HtmlTargetElement("fieldset", Attributes = "asp-for,label")] public class EditorFieldSetTagHelper : TagHelper { private const string Class = "class"; From cadf4989e4c08b3e63bab790397864f2d51d64bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Thu, 29 Jun 2023 13:37:06 +0200 Subject: [PATCH 2/8] Add readonly option to the tag helper --- .../TagHelpers/EditorFieldSetTagHelper.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs index 1271c972..3905a3a6 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs @@ -37,6 +37,9 @@ public class EditorFieldSetTagHelper : TagHelper [HtmlAttributeName("required")] public bool IsRequired { get; set; } + [HtmlAttributeName("readonly")] + public bool IsReadOnly { get; set; } + [HtmlAttributeName("options")] public IEnumerable Options { get; set; } @@ -83,8 +86,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) Label.Html().Trim() + (isRequired ? " *" : string.Empty), htmlAttributes: null); + var attributes = new Dictionary(); + if (IsReadOnly) attributes["readonly"] = "readonly"; + if (isRequired) attributes["required"] = "required"; + if (InputType.EqualsOrdinalIgnoreCase("checkbox")) { + attributes["class"] = "custom-control-input"; var checkbox = _htmlGenerator.GenerateCheckBox( ViewContext, For.ModelExplorer, @@ -95,9 +103,7 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) bool value => value, _ => bool.TryParse(For.Model.ToString(), out var parsedValue) ? parsedValue : null, }, - new { @class = "custom-control-input" }); - - if (isRequired) MakeRequired(checkbox); + attributes); label.Attributes[Class] = "custom-control-label"; @@ -113,6 +119,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) var inputType = InputType; if (Options != null) inputType = "select"; + attributes["class"] = "form-select"; + if (inputType != "select") + { + attributes["class"] = "form-control"; + attributes["type"] = InputType; + } + var input = inputType == "select" ? _htmlGenerator.GenerateSelect( ViewContext, @@ -121,23 +134,14 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) For.Name, Options, allowMultiple: false, - new - { - @class = "form-select", - }) + attributes) : _htmlGenerator.GenerateTextBox( ViewContext, For.ModelExplorer, For.Name, For.Model, For.ModelExplorer.Metadata.EditFormatString, - new - { - @class = "form-control", - type = InputType, - }); - - if (isRequired) MakeRequired(input); + attributes); AppendContent(output, label); AppendContent(output, input); @@ -155,6 +159,4 @@ private static bool HasRequiredAttribute(ModelExpression modelExpression) => .GetProperty(modelExpression.Name)? .GetCustomAttributes(typeof(RequiredAttribute), inherit: false) .FirstOrDefault() is RequiredAttribute; - - private static void MakeRequired(TagBuilder tagBuilder) => tagBuilder.Attributes.Add("required", "required"); } From 03350f5eeba33c1bc8ca5c1a94387e8c87efa921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Wed, 5 Jul 2023 11:20:43 +0200 Subject: [PATCH 3/8] Add new dictionary AddRange method --- .../Extensions/DictionaryExtensions.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs index a3f081e4..5bb09616 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs @@ -141,6 +141,25 @@ public static void AddRange( } } + /// + /// Adds a collection of to the dictionary by generating a key for each item using the + /// . If the selector's result in that value is not added. + /// + public static void AddRange( + this IDictionary dictionary, + IEnumerable values, + Func keySelector) + { + if (values == null) return; + foreach (var value in values) + { + if (keySelector(value) is { } key) + { + dictionary[key] = value; + } + } + } + /// /// Adds a new item to the list identified by a key in the dictionary. If the item is already part of the list /// then it won't add it again. From 9f0badfa77d03b3819cb5d1d66fba48a96ed0f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Wed, 5 Jul 2023 12:08:23 +0200 Subject: [PATCH 4/8] Simplify and DRY GetItemEditUrlAsync and GetItemDisplayUrlAsync --- .../ContentOrchardHelperExtensions.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs index 35b150af..ee12dca7 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement; -using OrchardCore.DisplayManagement; using System; using System.Linq.Expressions; using System.Threading.Tasks; @@ -19,13 +19,8 @@ public static async Task GetItemEditUrlAsync( this IOrchardHelper orchardHelper, ContentItem contentItem) { - var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService(); - var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService(); - var contentManager = orchardHelper.HttpContext.RequestServices.GetService(); - - var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext); - var metadata = await contentManager.PopulateAspectAsync(contentItem); - return urlHelper.Action(metadata.EditorRouteValues["action"].ToString(), metadata.EditorRouteValues); + var urlHelper = orchardHelper.GetUrlHelper(); + return urlHelper.EditContentItem(contentItem.ContentItemId); } /// @@ -35,13 +30,8 @@ public static async Task GetItemDisplayUrlAsync( this IOrchardHelper orchardHelper, ContentItem contentItem) { - var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService(); - var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService(); - var contentManager = orchardHelper.HttpContext.RequestServices.GetService(); - - var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext); - var metadata = await contentManager.PopulateAspectAsync(contentItem); - return urlHelper.Action(metadata.DisplayRouteValues["action"].ToString(), metadata.DisplayRouteValues); + var urlHelper = orchardHelper.GetUrlHelper(); + return urlHelper.DisplayContentItem(contentItem); } /// @@ -83,4 +73,13 @@ public static string Action( params (string Key, object Value)[] additionalArguments) where TController : ControllerBase => orchardHelper.HttpContext.Action(taskActionExpression.StripResult(), additionalArguments); + + private static IUrlHelper GetUrlHelper(this IOrchardHelper orchardHelper) + { + var serviceProvider = orchardHelper.HttpContext.RequestServices; + var urlHelperFactory = serviceProvider.GetService(); + var actionContext = serviceProvider.GetService()?.ActionContext; + + return urlHelperFactory.GetUrlHelper(actionContext); + } } From 57907f88fd1b8b4f0ef41e463122dfd44319f4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Fri, 7 Jul 2023 11:10:41 +0200 Subject: [PATCH 5/8] GetItemDisplayUrl --- .../ContentOrchardHelperExtensions.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs index ee12dca7..72370f06 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement; using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; @@ -15,9 +16,15 @@ public static class ContentOrchardHelperExtensions /// /// Gets the given content item's edit URL. /// - public static async Task GetItemEditUrlAsync( - this IOrchardHelper orchardHelper, - ContentItem contentItem) + [Obsolete($"Use {nameof(GetItemEditUrl)} instead as this method does not need to be async.")] + public static Task GetItemEditUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) => + Task.FromResult(orchardHelper.GetItemEditUrl(contentItem)); + + /// + /// Gets the given content item's edit URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) { var urlHelper = orchardHelper.GetUrlHelper(); return urlHelper.EditContentItem(contentItem.ContentItemId); @@ -26,9 +33,15 @@ public static async Task GetItemEditUrlAsync( /// /// Gets the given content item's display URL. /// - public static async Task GetItemDisplayUrlAsync( - this IOrchardHelper orchardHelper, - ContentItem contentItem) + [Obsolete($"Use {nameof(GetItemDisplayUrl)} instead as this method does not need to be async.")] + public static Task GetItemDisplayUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) => + Task.FromResult(orchardHelper.GetItemDisplayUrl(contentItem)); + + /// + /// Gets the given content item's display URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) { var urlHelper = orchardHelper.GetUrlHelper(); return urlHelper.DisplayContentItem(contentItem); From 0edbbc6670f7add9ab69a6df701304db7f1bd0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Fri, 7 Jul 2023 20:23:50 +0200 Subject: [PATCH 6/8] Code cleanup. --- .../TagHelpers/EditorFieldSetTagHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs index 3905a3a6..5c676065 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs @@ -92,7 +92,7 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) if (InputType.EqualsOrdinalIgnoreCase("checkbox")) { - attributes["class"] = "custom-control-input"; + attributes[Class] = "custom-control-input"; var checkbox = _htmlGenerator.GenerateCheckBox( ViewContext, For.ModelExplorer, @@ -119,10 +119,10 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) var inputType = InputType; if (Options != null) inputType = "select"; - attributes["class"] = "form-select"; + attributes[Class] = "form-select"; if (inputType != "select") { - attributes["class"] = "form-control"; + attributes[Class] = "form-control"; attributes["type"] = InputType; } From e04b8738e03d890b43af323dde222ba9bbbf4347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Fri, 7 Jul 2023 23:59:12 +0200 Subject: [PATCH 7/8] let GetItemDisplayUrl and GetItemEditUrl accept ContentItemId directly. --- .../ContentOrchardHelperExtensions.cs | 22 +++++++++++++++---- .../Mvc/OrchardControllerExtensions.cs | 4 ++-- .../Mvc/UrlHelperExtensions.cs | 8 ++++++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs index 72370f06..05a935da 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs @@ -24,10 +24,17 @@ public static Task GetItemEditUrlAsync(this IOrchardHelper orchardHelper /// Gets the given content item's edit URL. /// [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] - public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) + public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) => + orchardHelper.GetItemEditUrl(contentItem.ContentItemId); + + /// + /// Gets the given content item's edit URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemEditUrl(this IOrchardHelper orchardHelper, string contentItemId) { var urlHelper = orchardHelper.GetUrlHelper(); - return urlHelper.EditContentItem(contentItem.ContentItemId); + return urlHelper.EditContentItem(contentItemId); } /// @@ -41,10 +48,17 @@ public static Task GetItemDisplayUrlAsync(this IOrchardHelper orchardHel /// Gets the given content item's display URL. /// [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] - public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) + public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) => + orchardHelper.GetItemDisplayUrl(contentItem.ContentItemId); + + /// + /// Gets the given content item's display URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, string contentItemId) { var urlHelper = orchardHelper.GetUrlHelper(); - return urlHelper.DisplayContentItem(contentItem); + return urlHelper.DisplayContentItem(contentItemId); } /// diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs index 4e6c26eb..fd32683d 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.Mvc; public static class OrchardControllerExtensions { /// - /// Uses extension method to redirect to this 's display page. + /// Uses + /// extension method to redirect to this 's display page. /// public static RedirectResult RedirectToContentDisplay(this Controller controller, IContent content) => controller.Redirect(controller.Url.DisplayContentItem(content)); diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs index c0bfe9d7..1d46837a 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs @@ -47,12 +47,18 @@ public static string EditContentItemWithTab(this IUrlHelper helper, string tabId /// Returns a relative URL for the display page for the given . /// public static string DisplayContentItem(this IUrlHelper helper, IContent content) => + helper.DisplayContentItem(content.ContentItem.ContentItemId); + + /// + /// Returns a relative URL for the display page for the given . + /// + public static string DisplayContentItem(this IUrlHelper helper, string contentItemId) => helper.Action( "Display", "Item", new { area = OrchardCoreContentsArea, - content.ContentItem.ContentItemId, + contentItemId, }); } From 718d0368f7408b7e5d6ed31b81f43494ddb03994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20El-Saig?= Date: Sun, 9 Jul 2023 22:49:11 +0200 Subject: [PATCH 8/8] Code cleanup. --- .../TagHelpers/EditorFieldSetTagHelper.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs index e2d78831..191a4a88 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs @@ -85,8 +85,8 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) htmlAttributes: null); var attributes = new Dictionary(); - if (IsReadOnly) attributes["readonly"] = "readonly"; - if (isRequired) attributes["required"] = "required"; + AddBoolAttribute(attributes, IsReadOnly, "readonly"); + AddBoolAttribute(attributes, isRequired, "required"); if (InputType.EqualsOrdinalIgnoreCase("checkbox")) { @@ -168,5 +168,11 @@ private static bool HasRequiredAttribute(ModelExpression modelExpression) => .GetCustomAttributes(typeof(RequiredAttribute), inherit: false) .FirstOrDefault() is RequiredAttribute; - private static void MakeRequired(TagBuilder tagBuilder) => tagBuilder.Attributes.Add("required", "required"); + private static void AddBoolAttribute(IDictionary attributes, bool value, string attributeName) + { + if (value) + { + attributes[attributeName] = attributeName; + } + } }