Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
LinkTagHelper & ScriptTagHelper now log view path:
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianEdwards committed Feb 23, 2015
1 parent f6e701b commit c4b4f81
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 30 deletions.
13 changes: 9 additions & 4 deletions src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,31 @@ public static class AttributeMatcher
/// Determines whether a <see cref="ITagHelper" />'s required attributes are present, non null, non empty, and
/// non whitepsace.
/// </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">
/// The attributes the <see cref="ITagHelper" /> requires in order to run.
/// </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>
public static bool AllRequiredAttributesArePresent(
[NotNull] TagHelperContext context,
[NotNull] TagHelperContext tagHelperContext,
[NotNull] ViewContext viewContext,
[NotNull] IEnumerable<string> requiredAttributes,
ILogger logger)
{
var attributes = GetPresentMissingAttributes(context, requiredAttributes);
var attributes = GetPresentMissingAttributes(tagHelperContext, requiredAttributes);

if (attributes.Missing.Any())
{
if (attributes.Present.Any() && logger != null && logger.IsEnabled(LogLevel.Warning))
{
// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,27 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public class MissingAttributeLoggerStructure : ILoggerStructure
{
private readonly string _uniqueId;
private readonly string _viewPath;
private readonly IEnumerable<KeyValuePair<string, object>> _values;

// Internal for unit testing
internal IEnumerable<string> MissingAttributes { get; }

/// <summary>
/// Creates a new <see cref="MissingAttributeLoggerStructure"/>.
/// </summary>
/// <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>
public MissingAttributeLoggerStructure(string uniqueId, IEnumerable<string> missingAttributes)
public MissingAttributeLoggerStructure(string uniqueId, string viewPath, IEnumerable<string> missingAttributes)
{
_uniqueId = uniqueId;
_viewPath = viewPath;
MissingAttributes = missingAttributes;
_values = new Dictionary<string, object>
{
["UniqueId"] = _uniqueId,
["ViewPath"] = _viewPath,
["MissingAttributes"] = MissingAttributes
};
}
Expand Down Expand Up @@ -61,8 +65,9 @@ public string Message
/// <returns>The message.</returns>
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));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ public class ModeMatchResult<TMode>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="tagHelper">The <see cref="ITagHelper"/>.</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
{
if (logger.IsEnabled(LogLevel.Warning) && PartiallyMatchedAttributes.Any())
Expand All @@ -50,7 +55,7 @@ public void LogDetails<TTagHelper>([NotNull] ILogger logger, [NotNull] TTagHelpe
attribute => PartiallyMatchedAttributes.Contains(
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,28 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public class PartialModeMatchLoggerStructure<TMode> : ILoggerStructure
{
private readonly string _uniqueId;
private readonly string _viewPath;
private readonly IEnumerable<ModeMatchAttributes<TMode>> _partialMatches;
private readonly IEnumerable<KeyValuePair<string, object>> _values;

/// <summary>
/// Creates a new <see cref="PartialModeMatchLoggerStructure{TMode}"/>.
/// </summary>
/// <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>
public PartialModeMatchLoggerStructure(
string uniqueId,
string viewPath,
[NotNull] IEnumerable<ModeMatchAttributes<TMode>> partialMatches)
{
_uniqueId = uniqueId;
_viewPath = viewPath;
_partialMatches = partialMatches;
_values = new Dictionary<string, object>
{
["UniqueId"] = _uniqueId,
["ViewPath"] = _viewPath,
["PartialMatches"] = partialMatches
};
}
Expand Down Expand Up @@ -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)))));
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down
11 changes: 9 additions & 2 deletions src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ public class ScriptTagHelper : TagHelper
[Activate]
protected internal ILogger<ScriptTagHelper> Logger { get; set; }

[Activate]
protected internal ViewContext ViewContext { get; set; }

/// <inheritdoc />
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ public void LogDetails_LogsVerboseWhenNoFullMatchesFound()
var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>();
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(
Expand All @@ -44,9 +45,10 @@ public void LogDetails_DoesNotLogWhenPartialMatchFoundButNoPartiallyMatchedAttri
var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>();
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(
Expand Down Expand Up @@ -74,9 +76,10 @@ public void LogDetails_LogsWhenPartiallyMatchedAttributesFound()
var logger = MakeLogger(LogLevel.Verbose);
var tagHelper = new Mock<ITagHelper>();
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(
Expand Down Expand Up @@ -104,9 +107,10 @@ public void LogDetails_DoesNotLogWhenLoggingLevelIsSetAboveWarning()
var logger = MakeLogger(LogLevel.Critical);
var tagHelper = new Mock<ITagHelper>();
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(
Expand Down
53 changes: 41 additions & 12 deletions test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,20 +37,22 @@ 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();

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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -203,7 +219,7 @@ public async Task LogsWhenAllRequiredAttributesAreMissing()
public async Task PreservesOrderOfSourceAttributesWhenRun()
{
// Arrange
var context = MakeTagHelperContext(
var tagHelperContext = MakeTagHelperContext(
attributes: new Dictionary<string, object>
{
["data-extra"] = "something",
Expand All @@ -213,6 +229,8 @@ public async Task PreservesOrderOfSourceAttributesWhenRun()
["asp-fallback-test"] = "isavailable()",
});

var viewContext = MakeViewContext();

var output = MakeTagHelperOutput("link",
attributes: new Dictionary<string, string>
{
Expand All @@ -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("<script data-extra=\"something\" src=\"/blank.js\" data-more=\"else\"", output.Content);
Expand All @@ -251,6 +270,16 @@ public async Task PreservesOrderOfSourceAttributesWhenRun()
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)
{
attributes = attributes ?? new Dictionary<string, string>();
Expand Down

0 comments on commit c4b4f81

Please sign in to comment.