diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs index 9f181d1ab9..7608782758 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs @@ -19,18 +19,20 @@ public static class AttributeMatcher /// Determines whether a 's required attributes are present, non null, non empty, and /// non whitepsace. /// - /// The . + /// The . + /// The . /// /// The attributes the requires in order to run. /// /// An optional to log warning details to. /// A indicating whether the should run. public static bool AllRequiredAttributesArePresent( - [NotNull] TagHelperContext context, + [NotNull] TagHelperContext tagHelperContext, + [NotNull] ViewContext viewContext, [NotNull] IEnumerable requiredAttributes, ILogger logger) { - var attributes = GetPresentMissingAttributes(context, requiredAttributes); + var attributes = GetPresentMissingAttributes(tagHelperContext, requiredAttributes); if (attributes.Missing.Any()) { @@ -38,7 +40,10 @@ public static class AttributeMatcher { // At least 1 attribute was present indicating the user intended to use the tag helper, // but at least 1 was missing too, so log a warning with the details. - logger.WriteWarning(new MissingAttributeLoggerStructure(context.UniqueId, attributes.Missing)); + logger.WriteWarning(new MissingAttributeLoggerStructure( + tagHelperContext.UniqueId, + viewContext.View.Path, + attributes.Missing)); } return false; diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/MissingAttributeLoggerStructure.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/MissingAttributeLoggerStructure.cs index b5818101c6..44f7dda5dc 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/MissingAttributeLoggerStructure.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/MissingAttributeLoggerStructure.cs @@ -14,8 +14,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal public class MissingAttributeLoggerStructure : ILoggerStructure { private readonly string _uniqueId; + private readonly string _viewPath; private readonly IEnumerable> _values; - + // Internal for unit testing internal IEnumerable MissingAttributes { get; } @@ -23,14 +24,17 @@ public class MissingAttributeLoggerStructure : ILoggerStructure /// Creates a new . /// /// The unique ID of the HTML element this message applies to. + /// The path to the view. /// The missing required attributes. - public MissingAttributeLoggerStructure(string uniqueId, IEnumerable missingAttributes) + public MissingAttributeLoggerStructure(string uniqueId, string viewPath, IEnumerable missingAttributes) { _uniqueId = uniqueId; + _viewPath = viewPath; MissingAttributes = missingAttributes; _values = new Dictionary { ["UniqueId"] = _uniqueId, + ["ViewPath"] = _viewPath, ["MissingAttributes"] = MissingAttributes }; } @@ -61,8 +65,9 @@ public string Message /// The message. public string Format() { - return string.Format("Tag Helper unique ID: {0}, Missing attributes: {1}", + return string.Format("Tag Helper with ID {0} in view '{1}' is missing attributes: {2}", _uniqueId, + _viewPath, string.Join(",", MissingAttributes)); } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs index bc6dc275bf..d7c1735d6c 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs @@ -39,7 +39,12 @@ public class ModeMatchResult /// The . /// The . /// The value of . - public void LogDetails([NotNull] ILogger logger, [NotNull] TTagHelper tagHelper, string uniqueId) + /// The path to the view the is on. + public void LogDetails( + [NotNull] ILogger logger, + [NotNull] TTagHelper tagHelper, + string uniqueId, + string viewPath) where TTagHelper : ITagHelper { if (logger.IsEnabled(LogLevel.Warning) && PartiallyMatchedAttributes.Any()) @@ -50,7 +55,7 @@ public void LogDetails([NotNull] ILogger logger, [NotNull] TTagHelpe attribute => PartiallyMatchedAttributes.Contains( attribute, StringComparer.OrdinalIgnoreCase))); - logger.WriteWarning(new PartialModeMatchLoggerStructure(uniqueId, partialOnlyMatches)); + logger.WriteWarning(new PartialModeMatchLoggerStructure(uniqueId, viewPath, partialOnlyMatches)); } if (logger.IsEnabled(LogLevel.Verbose) && !FullMatches.Any()) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/PartialAttributeLoggerStructure.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/PartialAttributeLoggerStructure.cs index 395294d108..12f1cd8f05 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/PartialAttributeLoggerStructure.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/PartialAttributeLoggerStructure.cs @@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal public class PartialModeMatchLoggerStructure : ILoggerStructure { private readonly string _uniqueId; + private readonly string _viewPath; private readonly IEnumerable> _partialMatches; private readonly IEnumerable> _values; @@ -24,16 +25,20 @@ public class PartialModeMatchLoggerStructure : ILoggerStructure /// Creates a new . /// /// The unique ID of the HTML element this message applies to. + /// The path to the view. /// The set of modes with partial required attributes. public PartialModeMatchLoggerStructure( string uniqueId, + string viewPath, [NotNull] IEnumerable> partialMatches) { _uniqueId = uniqueId; + _viewPath = viewPath; _partialMatches = partialMatches; _values = new Dictionary { ["UniqueId"] = _uniqueId, + ["ViewPath"] = _viewPath, ["PartialMatches"] = partialMatches }; } @@ -65,8 +70,8 @@ public string Message public string Format() { var newLine = Environment.NewLine; - return - string.Format($"Tag Helper {_uniqueId} had partial matches while determining mode:{newLine}\t{{0}}", + return string.Format( + $"Tag Helper with ID {_uniqueId} in view '{_viewPath}' had partial matches while determining mode:{newLine}\t{{0}}", string.Join($"{newLine}\t", _partialMatches.Select(partial => string.Format($"Mode '{partial.Mode}' missing attributes:{newLine}\t\t{{0}} ", string.Join($"{newLine}\t\t", partial.MissingAttributes))))); diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs index 8b46283489..91cc7707cb 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs @@ -164,7 +164,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output) Debug.Assert(modeResult.FullMatches.Select(match => match.Mode).Distinct().Count() <= 1, $"There should only be one mode match, check the {nameof(ModeDetails)}"); - modeResult.LogDetails(Logger, this, context.UniqueId); + modeResult.LogDetails(Logger, this, context.UniqueId, ViewContext.View.Path); if (!modeResult.FullMatches.Any()) { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs index 1afc889517..975209caba 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs @@ -50,14 +50,21 @@ public class ScriptTagHelper : TagHelper [Activate] protected internal ILogger Logger { get; set; } + [Activate] + protected internal ViewContext ViewContext { get; set; } + /// public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { - if (!AttributeMatcher.AllRequiredAttributesArePresent(context, RequiredAttributes, Logger)) + if (!AttributeMatcher.AllRequiredAttributesArePresent(context, ViewContext, RequiredAttributes, Logger)) { if (Logger.IsEnabled(LogLevel.Verbose)) { - Logger.WriteVerbose("Skipping processing for {0} {1}", nameof(ScriptTagHelper), context.UniqueId); + Logger.WriteVerbose( + "Skipping processing for {0} with ID {1} on view '{2}'", + nameof(ScriptTagHelper), + context.UniqueId, + ViewContext.View.Path); } return; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/ModeMatchResultTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/ModeMatchResultTest.cs index 042a555429..0cbb16c5bb 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/ModeMatchResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/ModeMatchResultTest.cs @@ -19,9 +19,10 @@ public void LogDetails_LogsVerboseWhenNoFullMatchesFound() var logger = MakeLogger(LogLevel.Verbose); var tagHelper = new Mock(); var uniqueId = "id"; + var viewPath = "Views/Home/Index.cshtml"; // Act - modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); + modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath); // Assert Mock.Get(logger).Verify(l => l.Write( @@ -44,9 +45,10 @@ public void LogDetails_DoesNotLogWhenPartialMatchFoundButNoPartiallyMatchedAttri var logger = MakeLogger(LogLevel.Verbose); var tagHelper = new Mock(); var uniqueId = "id"; + var viewPath = "Views/Home/Index.cshtml"; // Act - modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); + modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath); // Assert Mock.Get(logger).Verify(l => l.Write( @@ -74,9 +76,10 @@ public void LogDetails_LogsWhenPartiallyMatchedAttributesFound() var logger = MakeLogger(LogLevel.Verbose); var tagHelper = new Mock(); var uniqueId = "id"; + var viewPath = "Views/Home/Index.cshtml"; // Act - modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); + modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath); // Assert Mock.Get(logger).Verify(l => l.Write( @@ -104,9 +107,10 @@ public void LogDetails_DoesNotLogWhenLoggingLevelIsSetAboveWarning() var logger = MakeLogger(LogLevel.Critical); var tagHelper = new Mock(); var uniqueId = "id"; + var viewPath = "Views/Home/Index.cshtml"; // Act - modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); + modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath); // Assert Mock.Get(logger).Verify(l => l.Write( diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index c8f4b5e0d5..ce0d41f4e0 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -3,11 +3,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.TagHelpers.Internal; using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.AspNet.Routing; using Microsoft.Framework.Logging; +using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.TagHelpers @@ -31,7 +37,8 @@ public async Task RunsWhenRequiredAttributesArePresent(string srcValue) attributes.Add("src", srcValue); } - var context = MakeTagHelperContext(attributes); + var tagHelperContext = MakeTagHelperContext(attributes); + var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); var logger = CreateLogger(); @@ -39,12 +46,13 @@ public async Task RunsWhenRequiredAttributesArePresent(string srcValue) var helper = new ScriptTagHelper() { Logger = logger, + ViewContext = viewContext, FallbackSrc = "http://www.example.com/blank.js", FallbackTestExpression = "isavailable()", }; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.Null(output.TagName); @@ -96,15 +104,17 @@ public static TheoryData MissingAttributeDataSet // Arrange Assert.Single(attributes); - var context = MakeTagHelperContext(attributes); + var tagHelperContext = MakeTagHelperContext(attributes); + var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); var logger = CreateLogger(); helper.Logger = logger; + helper.ViewContext = viewContext; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.Equal("script", output.TagName); @@ -115,17 +125,19 @@ public static TheoryData MissingAttributeDataSet public async Task DoesNotRunWhenAllRequiredAttributesAreMissing() { // Arrange - var context = MakeTagHelperContext(); + var tagHelperContext = MakeTagHelperContext(); + var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); var logger = CreateLogger(); var helper = new ScriptTagHelper { Logger = logger, + ViewContext = viewContext }; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.Equal("script", output.TagName); @@ -142,15 +154,17 @@ public async Task DoesNotRunWhenAllRequiredAttributesAreMissing() // Arrange Assert.Single(attributes); - var context = MakeTagHelperContext(attributes); + var tagHelperContext = MakeTagHelperContext(attributes); + var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); var logger = CreateLogger(); helper.Logger = logger; + helper.ViewContext = viewContext; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.Equal("script", output.TagName); @@ -175,17 +189,19 @@ public async Task DoesNotRunWhenAllRequiredAttributesAreMissing() public async Task LogsWhenAllRequiredAttributesAreMissing() { // Arrange - var context = MakeTagHelperContext(); + var tagHelperContext = MakeTagHelperContext(); + var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); var logger = CreateLogger(); var helper = new ScriptTagHelper { Logger = logger, + ViewContext = viewContext }; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.Equal("script", output.TagName); @@ -203,7 +219,7 @@ public async Task LogsWhenAllRequiredAttributesAreMissing() public async Task PreservesOrderOfSourceAttributesWhenRun() { // Arrange - var context = MakeTagHelperContext( + var tagHelperContext = MakeTagHelperContext( attributes: new Dictionary { ["data-extra"] = "something", @@ -213,6 +229,8 @@ public async Task PreservesOrderOfSourceAttributesWhenRun() ["asp-fallback-test"] = "isavailable()", }); + var viewContext = MakeViewContext(); + var output = MakeTagHelperOutput("link", attributes: new Dictionary { @@ -226,12 +244,13 @@ public async Task PreservesOrderOfSourceAttributesWhenRun() var helper = new ScriptTagHelper { Logger = logger, + ViewContext = viewContext, FallbackSrc = "~/blank.js", FallbackTestExpression = "http://www.example.com/blank.js", }; // Act - await helper.ProcessAsync(context, output); + await helper.ProcessAsync(tagHelperContext, output); // Assert Assert.StartsWith("