LinkTagHelper & ScriptTagHelper now log view path:

- #1949
This commit is contained in:
damianedwards 2015-02-23 21:51:45 -08:00
parent 9d5364cf9b
commit eef8884d0f
8 changed files with 89 additions and 29 deletions

View File

@ -19,18 +19,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// Determines whether a <see cref="ITagHelper" />'s required attributes are present, non null, non empty, and /// Determines whether a <see cref="ITagHelper" />'s required attributes are present, non null, non empty, and
/// non whitepsace. /// non whitepsace.
/// </summary> /// </summary>
/// <param name="context">The <see cref="TagHelperContext"/>.</param> /// <param name="tagHelperContext">The <see cref="TagHelperContext"/>.</param>
/// <param name="viewContext">The <see cref="ViewContext"/>.</param>
/// <param name="requiredAttributes"> /// <param name="requiredAttributes">
/// The attributes the <see cref="ITagHelper" /> requires in order to run. /// The attributes the <see cref="ITagHelper" /> requires in order to run.
/// </param> /// </param>
/// <param name="logger">An optional <see cref="ILogger"/> to log warning details to.</param> /// <param name="logger">An optional <see cref="ILogger"/> to log warning details to.</param>
/// <returns>A <see cref="bool"/> indicating whether the <see cref="ITagHelper" /> should run.</returns> /// <returns>A <see cref="bool"/> indicating whether the <see cref="ITagHelper" /> should run.</returns>
public static bool AllRequiredAttributesArePresent( public static bool AllRequiredAttributesArePresent(
[NotNull] TagHelperContext context, [NotNull] TagHelperContext tagHelperContext,
[NotNull] ViewContext viewContext,
[NotNull] IEnumerable<string> requiredAttributes, [NotNull] IEnumerable<string> requiredAttributes,
ILogger logger) ILogger logger)
{ {
var attributes = GetPresentMissingAttributes(context, requiredAttributes); var attributes = GetPresentMissingAttributes(tagHelperContext, requiredAttributes);
if (attributes.Missing.Any()) if (attributes.Missing.Any())
{ {
@ -38,7 +40,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{ {
// At least 1 attribute was present indicating the user intended to use the tag helper, // 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. // 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; return false;

View File

@ -14,6 +14,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public class MissingAttributeLoggerStructure : ILoggerStructure public class MissingAttributeLoggerStructure : ILoggerStructure
{ {
private readonly string _uniqueId; private readonly string _uniqueId;
private readonly string _viewPath;
private readonly IEnumerable<KeyValuePair<string, object>> _values; private readonly IEnumerable<KeyValuePair<string, object>> _values;
// Internal for unit testing // Internal for unit testing
@ -23,14 +24,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// Creates a new <see cref="MissingAttributeLoggerStructure"/>. /// Creates a new <see cref="MissingAttributeLoggerStructure"/>.
/// </summary> /// </summary>
/// <param name="uniqueId">The unique ID of the HTML element this message applies to.</param> /// <param name="uniqueId">The unique ID of the HTML element this message applies to.</param>
/// <param name="viewPath">The path to the view.</param>
/// <param name="missingAttributes">The missing required attributes.</param> /// <param name="missingAttributes">The missing required attributes.</param>
public MissingAttributeLoggerStructure(string uniqueId, IEnumerable<string> missingAttributes) public MissingAttributeLoggerStructure(string uniqueId, string viewPath, IEnumerable<string> missingAttributes)
{ {
_uniqueId = uniqueId; _uniqueId = uniqueId;
_viewPath = viewPath;
MissingAttributes = missingAttributes; MissingAttributes = missingAttributes;
_values = new Dictionary<string, object> _values = new Dictionary<string, object>
{ {
["UniqueId"] = _uniqueId, ["UniqueId"] = _uniqueId,
["ViewPath"] = _viewPath,
["MissingAttributes"] = MissingAttributes ["MissingAttributes"] = MissingAttributes
}; };
} }
@ -61,8 +65,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// <returns>The message.</returns> /// <returns>The message.</returns>
public string Format() 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, _uniqueId,
_viewPath,
string.Join(",", MissingAttributes)); string.Join(",", MissingAttributes));
} }
} }

View File

@ -39,7 +39,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// <param name="logger">The <see cref="ILogger"/>.</param> /// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="tagHelper">The <see cref="ITagHelper"/>.</param> /// <param name="tagHelper">The <see cref="ITagHelper"/>.</param>
/// <param name="uniqueId">The value of <see cref="TagHelperContext.UniqueId"/>.</param> /// <param name="uniqueId">The value of <see cref="TagHelperContext.UniqueId"/>.</param>
public void LogDetails<TTagHelper>([NotNull] ILogger logger, [NotNull] TTagHelper tagHelper, string uniqueId) /// <param name="viewPath">The path to the view the <see cref="ITagHelper"/> is on.</param>
public void LogDetails<TTagHelper>(
[NotNull] ILogger logger,
[NotNull] TTagHelper tagHelper,
string uniqueId,
string viewPath)
where TTagHelper : ITagHelper where TTagHelper : ITagHelper
{ {
if (logger.IsEnabled(LogLevel.Warning) && PartiallyMatchedAttributes.Any()) if (logger.IsEnabled(LogLevel.Warning) && PartiallyMatchedAttributes.Any())
@ -50,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
attribute => PartiallyMatchedAttributes.Contains( attribute => PartiallyMatchedAttributes.Contains(
attribute, StringComparer.OrdinalIgnoreCase))); attribute, StringComparer.OrdinalIgnoreCase)));
logger.WriteWarning(new PartialModeMatchLoggerStructure<TMode>(uniqueId, partialOnlyMatches)); logger.WriteWarning(new PartialModeMatchLoggerStructure<TMode>(uniqueId, viewPath, partialOnlyMatches));
} }
if (logger.IsEnabled(LogLevel.Verbose) && !FullMatches.Any()) if (logger.IsEnabled(LogLevel.Verbose) && !FullMatches.Any())

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public class PartialModeMatchLoggerStructure<TMode> : ILoggerStructure public class PartialModeMatchLoggerStructure<TMode> : ILoggerStructure
{ {
private readonly string _uniqueId; private readonly string _uniqueId;
private readonly string _viewPath;
private readonly IEnumerable<ModeMatchAttributes<TMode>> _partialMatches; private readonly IEnumerable<ModeMatchAttributes<TMode>> _partialMatches;
private readonly IEnumerable<KeyValuePair<string, object>> _values; private readonly IEnumerable<KeyValuePair<string, object>> _values;
@ -24,16 +25,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// Creates a new <see cref="PartialModeMatchLoggerStructure{TMode}"/>. /// Creates a new <see cref="PartialModeMatchLoggerStructure{TMode}"/>.
/// </summary> /// </summary>
/// <param name="uniqueId">The unique ID of the HTML element this message applies to.</param> /// <param name="uniqueId">The unique ID of the HTML element this message applies to.</param>
/// <param name="viewPath">The path to the view.</param>
/// <param name="partialMatches">The set of modes with partial required attributes.</param> /// <param name="partialMatches">The set of modes with partial required attributes.</param>
public PartialModeMatchLoggerStructure( public PartialModeMatchLoggerStructure(
string uniqueId, string uniqueId,
string viewPath,
[NotNull] IEnumerable<ModeMatchAttributes<TMode>> partialMatches) [NotNull] IEnumerable<ModeMatchAttributes<TMode>> partialMatches)
{ {
_uniqueId = uniqueId; _uniqueId = uniqueId;
_viewPath = viewPath;
_partialMatches = partialMatches; _partialMatches = partialMatches;
_values = new Dictionary<string, object> _values = new Dictionary<string, object>
{ {
["UniqueId"] = _uniqueId, ["UniqueId"] = _uniqueId,
["ViewPath"] = _viewPath,
["PartialMatches"] = partialMatches ["PartialMatches"] = partialMatches
}; };
} }
@ -65,8 +70,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public string Format() public string Format()
{ {
var newLine = Environment.NewLine; var newLine = Environment.NewLine;
return return string.Format(
string.Format($"Tag Helper {_uniqueId} had partial matches while determining mode:{newLine}\t{{0}}", $"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.Join($"{newLine}\t", _partialMatches.Select(partial =>
string.Format($"Mode '{partial.Mode}' missing attributes:{newLine}\t\t{{0}} ", string.Format($"Mode '{partial.Mode}' missing attributes:{newLine}\t\t{{0}} ",
string.Join($"{newLine}\t\t", partial.MissingAttributes))))); string.Join($"{newLine}\t\t", partial.MissingAttributes)))));

View File

@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Debug.Assert(modeResult.FullMatches.Select(match => match.Mode).Distinct().Count() <= 1, Debug.Assert(modeResult.FullMatches.Select(match => match.Mode).Distinct().Count() <= 1,
$"There should only be one mode match, check the {nameof(ModeDetails)}"); $"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()) if (!modeResult.FullMatches.Any())
{ {

View File

@ -50,14 +50,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[Activate] [Activate]
protected internal ILogger<ScriptTagHelper> Logger { get; set; } protected internal ILogger<ScriptTagHelper> Logger { get; set; }
[Activate]
protected internal ViewContext ViewContext { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 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)) 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; return;

View File

@ -19,9 +19,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var logger = MakeLogger(LogLevel.Verbose); var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>(); var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id"; var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act // Act
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
// Assert // Assert
Mock.Get(logger).Verify(l => l.Write( Mock.Get(logger).Verify(l => l.Write(
@ -44,9 +45,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var logger = MakeLogger(LogLevel.Verbose); var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>(); var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id"; var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act // Act
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
// Assert // Assert
Mock.Get(logger).Verify(l => l.Write( Mock.Get(logger).Verify(l => l.Write(
@ -74,9 +76,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var logger = MakeLogger(LogLevel.Verbose); var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>(); var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id"; var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act // Act
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
// Assert // Assert
Mock.Get(logger).Verify(l => l.Write( Mock.Get(logger).Verify(l => l.Write(
@ -104,9 +107,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var logger = MakeLogger(LogLevel.Critical); var logger = MakeLogger(LogLevel.Critical);
var tagHelper = new Mock<ITagHelper>(); var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id"; var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act // Act
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId); modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
// Assert // Assert
Mock.Get(logger).Verify(l => l.Write( Mock.Get(logger).Verify(l => l.Write(

View File

@ -3,11 +3,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; 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.Mvc.TagHelpers.Internal;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
@ -31,7 +37,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attributes.Add("src", srcValue); attributes.Add("src", srcValue);
} }
var context = MakeTagHelperContext(attributes); var tagHelperContext = MakeTagHelperContext(attributes);
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script"); var output = MakeTagHelperOutput("script");
var logger = CreateLogger(); var logger = CreateLogger();
@ -39,12 +46,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var helper = new ScriptTagHelper() var helper = new ScriptTagHelper()
{ {
Logger = logger, Logger = logger,
ViewContext = viewContext,
FallbackSrc = "http://www.example.com/blank.js", FallbackSrc = "http://www.example.com/blank.js",
FallbackTestExpression = "isavailable()", FallbackTestExpression = "isavailable()",
}; };
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Null(output.TagName); Assert.Null(output.TagName);
@ -96,15 +104,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange // Arrange
Assert.Single(attributes); Assert.Single(attributes);
var context = MakeTagHelperContext(attributes); var tagHelperContext = MakeTagHelperContext(attributes);
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script"); var output = MakeTagHelperOutput("script");
var logger = CreateLogger(); var logger = CreateLogger();
helper.Logger = logger; helper.Logger = logger;
helper.ViewContext = viewContext;
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Equal("script", output.TagName); Assert.Equal("script", output.TagName);
@ -115,17 +125,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task DoesNotRunWhenAllRequiredAttributesAreMissing() public async Task DoesNotRunWhenAllRequiredAttributesAreMissing()
{ {
// Arrange // Arrange
var context = MakeTagHelperContext(); var tagHelperContext = MakeTagHelperContext();
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script"); var output = MakeTagHelperOutput("script");
var logger = CreateLogger(); var logger = CreateLogger();
var helper = new ScriptTagHelper var helper = new ScriptTagHelper
{ {
Logger = logger, Logger = logger,
ViewContext = viewContext
}; };
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Equal("script", output.TagName); Assert.Equal("script", output.TagName);
@ -142,15 +154,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange // Arrange
Assert.Single(attributes); Assert.Single(attributes);
var context = MakeTagHelperContext(attributes); var tagHelperContext = MakeTagHelperContext(attributes);
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script"); var output = MakeTagHelperOutput("script");
var logger = CreateLogger(); var logger = CreateLogger();
helper.Logger = logger; helper.Logger = logger;
helper.ViewContext = viewContext;
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Equal("script", output.TagName); Assert.Equal("script", output.TagName);
@ -175,17 +189,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task LogsWhenAllRequiredAttributesAreMissing() public async Task LogsWhenAllRequiredAttributesAreMissing()
{ {
// Arrange // Arrange
var context = MakeTagHelperContext(); var tagHelperContext = MakeTagHelperContext();
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script"); var output = MakeTagHelperOutput("script");
var logger = CreateLogger(); var logger = CreateLogger();
var helper = new ScriptTagHelper var helper = new ScriptTagHelper
{ {
Logger = logger, Logger = logger,
ViewContext = viewContext
}; };
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Equal("script", output.TagName); Assert.Equal("script", output.TagName);
@ -203,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task PreservesOrderOfSourceAttributesWhenRun() public async Task PreservesOrderOfSourceAttributesWhenRun()
{ {
// Arrange // Arrange
var context = MakeTagHelperContext( var tagHelperContext = MakeTagHelperContext(
attributes: new Dictionary<string, object> attributes: new Dictionary<string, object>
{ {
["data-extra"] = "something", ["data-extra"] = "something",
@ -213,6 +229,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-fallback-test"] = "isavailable()", ["asp-fallback-test"] = "isavailable()",
}); });
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("link", var output = MakeTagHelperOutput("link",
attributes: new Dictionary<string, string> attributes: new Dictionary<string, string>
{ {
@ -226,12 +244,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var helper = new ScriptTagHelper var helper = new ScriptTagHelper
{ {
Logger = logger, Logger = logger,
ViewContext = viewContext,
FallbackSrc = "~/blank.js", FallbackSrc = "~/blank.js",
FallbackTestExpression = "http://www.example.com/blank.js", FallbackTestExpression = "http://www.example.com/blank.js",
}; };
// Act // Act
await helper.ProcessAsync(context, output); await helper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.StartsWith("<script data-extra=\"something\" src=\"/blank.js\" data-more=\"else\"", output.Content); Assert.StartsWith("<script data-extra=\"something\" src=\"/blank.js\" data-more=\"else\"", output.Content);
@ -251,6 +270,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
getChildContentAsync: () => Task.FromResult(content)); getChildContentAsync: () => Task.FromResult(content));
} }
private static ViewContext MakeViewContext()
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var metadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(metadataProvider);
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, TextWriter.Null);
return viewContext;
}
private TagHelperOutput MakeTagHelperOutput(string tagName, IDictionary<string, string> attributes = null) private TagHelperOutput MakeTagHelperOutput(string tagName, IDictionary<string, string> attributes = null)
{ {
attributes = attributes ?? new Dictionary<string, string>(); attributes = attributes ?? new Dictionary<string, string>();