diff --git a/src/Mvc/Mvc.TagHelpers/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.TagHelpers/src/PublicAPI.Unshipped.txt
index 7dc5c58110bf..46ac17a8733c 100644
--- a/src/Mvc/Mvc.TagHelpers/src/PublicAPI.Unshipped.txt
+++ b/src/Mvc/Mvc.TagHelpers/src/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+*REMOVED*~override Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) -> void
+~override Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper.ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) -> System.Threading.Tasks.Task
diff --git a/src/Mvc/Mvc.TagHelpers/src/ScriptTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ScriptTagHelper.cs
index 64cc1be2f694..85952ce0865a 100644
--- a/src/Mvc/Mvc.TagHelpers/src/ScriptTagHelper.cs
+++ b/src/Mvc/Mvc.TagHelpers/src/ScriptTagHelper.cs
@@ -235,15 +235,29 @@ private StringWriter StringWriter
}
///
- public override void Process(TagHelperContext context, TagHelperOutput output)
+ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(output);
if (string.Equals(Type, "importmap", StringComparison.OrdinalIgnoreCase))
{
- // This is an importmap script, we'll write out the import map and
- // stop processing.
+ // Do not update the content if another tag helper targeting this element has already done so.
+ if (output.IsContentModified)
+ {
+ return;
+ }
+
+ // This is an importmap script, check if there's existing content first.
+ var childContent = await output.GetChildContentAsync();
+ if (!childContent.IsEmptyOrWhiteSpace)
+ {
+ // User provided existing content; preserve it.
+ output.Content.SetHtmlContent(childContent);
+ return;
+ }
+
+ // No existing content, so we can apply import map logic.
var importMap = ImportMap ?? ViewContext.HttpContext.GetEndpoint()?.Metadata.GetMetadata();
if (importMap == null)
{
@@ -252,10 +266,10 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
return;
}
+ output.Content.SetHtmlContent(importMap.ToString());
output.TagName = "script";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.SetAttribute("type", "importmap");
- output.Content.SetHtmlContent(importMap.ToString());
return;
}
diff --git a/src/Mvc/Mvc.TagHelpers/test/ScriptTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/ScriptTagHelperTest.cs
index 18ffbc3cd10f..11fec3545440 100644
--- a/src/Mvc/Mvc.TagHelpers/test/ScriptTagHelperTest.cs
+++ b/src/Mvc/Mvc.TagHelpers/test/ScriptTagHelperTest.cs
@@ -30,7 +30,7 @@ public class ScriptTagHelperTest
[InlineData("abcd.js", "test.js", "test.js")]
[InlineData(null, "~/test.js", "virtualRoot/test.js")]
[InlineData("abcd.js", "~/test.js", "virtualRoot/test.js")]
- public void Process_SrcDefaultsToTagHelperOutputSrcAttributeAddedByOtherTagHelper(
+ public async Task ProcessAsync_SrcDefaultsToTagHelperOutputSrcAttributeAddedByOtherTagHelper(
string src,
string srcOutput,
string expectedSrcPrefix)
@@ -66,7 +66,7 @@ public void Process_SrcDefaultsToTagHelperOutputSrcAttributeAddedByOtherTagHelpe
helper.Src = src;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal(
@@ -77,7 +77,7 @@ public void Process_SrcDefaultsToTagHelperOutputSrcAttributeAddedByOtherTagHelpe
[Theory]
[MemberData(nameof(LinkTagHelperTest.MultiAttributeSameNameData), MemberType = typeof(LinkTagHelperTest))]
- public void HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
+ public async Task HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
{
// Arrange
var allAttributes = new TagHelperAttributeList(
@@ -107,7 +107,7 @@ public void HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList ou
expectedAttributes.Add(new TagHelperAttribute("src", "/blank.js"));
// Act
- helper.Process(tagHelperContext, output);
+ await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
@@ -268,7 +268,7 @@ public static TheoryData> RunsWh
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_Data))]
- public void RunsWhenRequiredAttributesArePresent(
+ public async Task RunsWhenRequiredAttributesArePresent(
TagHelperAttributeList attributes,
Action setProperties)
{
@@ -287,7 +287,7 @@ public void RunsWhenRequiredAttributesArePresent(
setProperties(helper);
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.NotNull(output.TagName);
@@ -355,7 +355,7 @@ public static TheoryData> RunsWh
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_NoSrc_Data))]
- public void RunsWhenRequiredAttributesArePresent_NoSrc(
+ public async Task RunsWhenRequiredAttributesArePresent_NoSrc(
TagHelperAttributeList attributes,
Action setProperties)
{
@@ -374,7 +374,7 @@ public void RunsWhenRequiredAttributesArePresent_NoSrc(
setProperties(helper);
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Null(output.TagName);
@@ -449,7 +449,7 @@ public static TheoryData> DoesNo
[Theory]
[MemberData(nameof(DoesNotRunWhenARequiredAttributeIsMissing_Data))]
- public void DoesNotRunWhenARequiredAttributeIsMissing(
+ public async Task DoesNotRunWhenARequiredAttributeIsMissing(
TagHelperAttributeList attributes,
Action setProperties)
{
@@ -462,7 +462,7 @@ public void DoesNotRunWhenARequiredAttributeIsMissing(
setProperties(helper);
// Act
- helper.Process(tagHelperContext, output);
+ await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.NotNull(output.TagName);
@@ -472,7 +472,7 @@ public void DoesNotRunWhenARequiredAttributeIsMissing(
}
[Fact]
- public void DoesNotRunWhenAllRequiredAttributesAreMissing()
+ public async Task DoesNotRunWhenAllRequiredAttributesAreMissing()
{
// Arrange
var tagHelperContext = MakeTagHelperContext();
@@ -482,7 +482,7 @@ public void DoesNotRunWhenAllRequiredAttributesAreMissing()
var helper = GetHelper();
// Act
- helper.Process(tagHelperContext, output);
+ await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -492,7 +492,7 @@ public void DoesNotRunWhenAllRequiredAttributesAreMissing()
}
[Fact]
- public void PreservesOrderOfNonSrcAttributes()
+ public async Task PreservesOrderOfNonSrcAttributes()
{
// Arrange
var tagHelperContext = MakeTagHelperContext(
@@ -518,7 +518,7 @@ public void PreservesOrderOfNonSrcAttributes()
helper.Src = "/blank.js";
// Act
- helper.Process(tagHelperContext, output);
+ await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal("data-extra", output.Attributes[0].Name);
@@ -527,7 +527,7 @@ public void PreservesOrderOfNonSrcAttributes()
}
[Fact]
- public void RendersScriptTagsForGlobbedSrcResults()
+ public async Task RendersScriptTagsForGlobbedSrcResults()
{
// Arrange
var expectedContent = "" +
@@ -552,7 +552,7 @@ public void RendersScriptTagsForGlobbedSrcResults()
helper.SrcInclude = "**/*.js";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -562,7 +562,7 @@ public void RendersScriptTagsForGlobbedSrcResults()
}
[Fact]
- public void RendersScriptTagsForGlobbedSrcResults_EncodesAsExpected()
+ public async Task RendersScriptTagsForGlobbedSrcResults_EncodesAsExpected()
{
// Arrange
var expectedContent =
@@ -605,7 +605,7 @@ public void RendersScriptTagsForGlobbedSrcResults_EncodesAsExpected()
helper.SrcInclude = "**/*.js";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -615,7 +615,7 @@ public void RendersScriptTagsForGlobbedSrcResults_EncodesAsExpected()
}
[Fact]
- public void RenderScriptTags_WithFileVersion()
+ public async Task RenderScriptTags_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@@ -631,7 +631,7 @@ public void RenderScriptTags_WithFileVersion()
helper.AppendVersion = true;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -642,7 +642,7 @@ public void RenderScriptTags_WithFileVersion()
[InlineData("~/js/site.js", "/js/site.fingerprint.js")]
[InlineData("/js/site.js", "/js/site.fingerprint.js")]
[InlineData("js/site.js", "js/site.fingerprint.js")]
- public void RenderScriptTags_WithFileVersion_UsingResourceCollection(string src, string expected)
+ public async Task RenderScriptTags_WithFileVersion_UsingResourceCollection(string src, string expected)
{
// Arrange
var context = MakeTagHelperContext(
@@ -661,7 +661,7 @@ public void RenderScriptTags_WithFileVersion_UsingResourceCollection(string src,
helper.AppendVersion = true;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -671,7 +671,7 @@ public void RenderScriptTags_WithFileVersion_UsingResourceCollection(string src,
[Theory]
[InlineData("~/js/site.js")]
[InlineData("/approot/js/site.js")]
- public void RenderScriptTags_PathBase_WithFileVersion_UsingResourceCollection(string path)
+ public async Task RenderScriptTags_PathBase_WithFileVersion_UsingResourceCollection(string path)
{
// Arrange
var context = MakeTagHelperContext(
@@ -699,7 +699,7 @@ public void RenderScriptTags_PathBase_WithFileVersion_UsingResourceCollection(st
helper.AppendVersion = true;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -707,7 +707,7 @@ public void RenderScriptTags_PathBase_WithFileVersion_UsingResourceCollection(st
}
[Fact]
- public void ScriptTagHelper_RendersProvided_ImportMap()
+ public async Task ScriptTagHelper_RendersProvided_ImportMap()
{
// Arrange
var importMap = new ImportMapDefinition(
@@ -744,7 +744,7 @@ public void ScriptTagHelper_RendersProvided_ImportMap()
helper.ImportMap = importMap;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -752,7 +752,7 @@ public void ScriptTagHelper_RendersProvided_ImportMap()
}
[Fact]
- public void ScriptTagHelper_RendersImportMap_FromEndpoint()
+ public async Task ScriptTagHelper_RendersImportMap_FromEndpoint()
{
// Arrange
var importMap = new ImportMapDefinition(
@@ -787,13 +787,66 @@ public void ScriptTagHelper_RendersImportMap_FromEndpoint()
helper.Type = "importmap";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal(importMap.ToJson(), output.Content.GetContent());
}
+ [Fact]
+ public async Task ScriptTagHelper_PreservesExplicitImportMapContent_WhenUserProvidesContent()
+ {
+ // Arrange - this simulates the user's scenario where they provide explicit importmap content
+ var context = MakeTagHelperContext(attributes: [new TagHelperAttribute("type", "importmap")]);
+
+ // Simulate user providing explicit content
+ var childContent = new DefaultTagHelperContent();
+ childContent.SetHtmlContent(@"{""imports"":{""jquery"":""https://code.jquery.com/jquery.js""}}");
+
+ var output = MakeTagHelperOutput("script", attributes: [new TagHelperAttribute("type", "importmap")], childContent: childContent);
+
+ var helper = GetHelper();
+ helper.Type = "importmap";
+
+ // No endpoint with ImportMapDefinition - this should NOT suppress the output
+ // since user provided explicit content
+
+ // Act
+ await helper.ProcessAsync(context, output);
+
+ // Assert
+ Assert.Equal("script", output.TagName); // Tag should not be suppressed
+ Assert.Equal("importmap", output.Attributes["type"].Value);
+ // The user's explicit content should be preserved
+ Assert.Equal(@"{""imports"":{""jquery"":""https://code.jquery.com/jquery.js""}}", output.Content.GetContent());
+ }
+
+ [Fact]
+ public async Task ScriptTagHelper_SuppressesOutput_WhenNoContentAndNoImportMapDefinition()
+ {
+ // Arrange - this simulates an empty importmap script with no definition
+ var context = MakeTagHelperContext(
+ attributes: new TagHelperAttributeList
+ {
+ new TagHelperAttribute("type", "importmap"),
+ });
+
+ var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
+ // No content provided
+
+ var helper = GetHelper();
+ helper.Type = "importmap";
+ // No endpoint with ImportMapDefinition and no explicit content
+ // This should suppress the output since there's nothing to render
+
+ // Act
+ await helper.ProcessAsync(context, output);
+
+ // Assert - output should be suppressed when no content and no definition
+ Assert.Null(output.TagName); // Tag should be suppressed
+ }
+
private Endpoint CreateEndpoint(ImportMapDefinition importMap = null)
{
return new Endpoint(
@@ -809,7 +862,7 @@ [new ResourceAssetCollection([
}
[Fact]
- public void RenderScriptTags_WithFileVersion_AndRequestPathBase()
+ public async Task RenderScriptTags_WithFileVersion_AndRequestPathBase()
{
// Arrange
var context = MakeTagHelperContext(
@@ -826,7 +879,7 @@ public void RenderScriptTags_WithFileVersion_AndRequestPathBase()
helper.AppendVersion = true;
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -834,7 +887,7 @@ public void RenderScriptTags_WithFileVersion_AndRequestPathBase()
}
[Fact]
- public void RenderScriptTags_FallbackSrc_WithFileVersion()
+ public async Task RenderScriptTags_FallbackSrc_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@@ -854,7 +907,7 @@ public void RenderScriptTags_FallbackSrc_WithFileVersion()
helper.Src = "/js/site.js";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -865,7 +918,7 @@ public void RenderScriptTags_FallbackSrc_WithFileVersion()
}
[Fact]
- public void RenderScriptTags_FallbackSrc_AppendVersion_WithStaticAssets()
+ public async Task RenderScriptTags_FallbackSrc_AppendVersion_WithStaticAssets()
{
// Arrange
var context = MakeTagHelperContext(
@@ -886,7 +939,7 @@ public void RenderScriptTags_FallbackSrc_AppendVersion_WithStaticAssets()
helper.Src = "/js/site.js";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -897,7 +950,7 @@ public void RenderScriptTags_FallbackSrc_AppendVersion_WithStaticAssets()
}
[Fact]
- public void RenderScriptTags_FallbackSrc_WithFileVersion_EncodesAsExpected()
+ public async Task RenderScriptTags_FallbackSrc_WithFileVersion_EncodesAsExpected()
{
// Arrange
var expectedContent =
@@ -939,7 +992,7 @@ public void RenderScriptTags_FallbackSrc_WithFileVersion_EncodesAsExpected()
helper.Src = "/js/site.js";
// Act
- helper.Process(context, output);
+ await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("script", output.TagName);
@@ -949,7 +1002,7 @@ public void RenderScriptTags_FallbackSrc_WithFileVersion_EncodesAsExpected()
}
[Fact]
- public void RenderScriptTags_GlobbedSrc_WithFileVersion()
+ public async Task RenderScriptTags_GlobbedSrc_WithFileVersion()
{
// Arrange
var expectedContent = "