Added default `UrlResolutionTagHelper` to resolve app relative URLs.
- Razor removed the ability to automatically resolve URLs prefixed with `~/`; therefore `ScriptTagHelper`, `LinkTagHelper` and `ImageTagHelper` have changed to take in `IUrlHelper`s and auto-resolve their URL based properties if they start with `~/`. - Added a catch-all `~/` resolver for non `TagHelper` based HTML elements. Razor used to resolve any attribute value that started with `~/` now the behavior is restricted to attributes that can contain URLs. - Updated `IUrlHelper` to accept `null` values. - Added functional tests to validate that URLs resolve correctly. - Updated `TagHelper` tests to ensure that URLs are resolved via an `IUrlHelper`. #2807
This commit is contained in:
parent
6d228a62dc
commit
0ef68eefc8
|
|
@ -120,8 +120,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Content([NotNull] string contentPath)
|
||||
public virtual string Content(string contentPath)
|
||||
{
|
||||
if (contentPath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GenerateClientUrl(_httpContext.Request.PathBase, contentPath);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,12 +31,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
"Microsoft.AspNet.Mvc",
|
||||
"Microsoft.AspNet.Mvc.Rendering",
|
||||
};
|
||||
private static readonly Chunk[] _defaultInheritedChunks = new[]
|
||||
private static readonly Chunk[] _defaultInheritedChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", HtmlHelperPropertyName),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IJsonHelper", "Json"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
|
||||
new AddTagHelperChunk
|
||||
{
|
||||
LookupText = "Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNet.Mvc.Razor"
|
||||
},
|
||||
};
|
||||
|
||||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||
|
|
@ -99,7 +103,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
|
||||
})
|
||||
{
|
||||
ResolveUrlMethodName = "Href",
|
||||
BeginContextMethodName = "BeginContext",
|
||||
EndContextMethodName = "EndContext"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -426,6 +426,26 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_InvalidTagHelperIndexerAssignment"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting:
|
||||
///
|
||||
/// @{3} "{4}, {5}"
|
||||
/// </summary>
|
||||
internal static string CouldNotResolveApplicationRelativeUrl_TagHelper
|
||||
{
|
||||
get { return GetString("CouldNotResolveApplicationRelativeUrl_TagHelper"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting:
|
||||
///
|
||||
/// @{3} "{4}, {5}"
|
||||
/// </summary>
|
||||
internal static string FormatCouldNotResolveApplicationRelativeUrl_TagHelper(object p0, object p1, object p2, object p3, object p4, object p5)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("CouldNotResolveApplicationRelativeUrl_TagHelper"), p0, p1, p2, p3, p4, p5);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -195,4 +195,9 @@
|
|||
<data name="RazorPage_InvalidTagHelperIndexerAssignment" xml:space="preserve">
|
||||
<value>Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null.</value>
|
||||
</data>
|
||||
<data name="CouldNotResolveApplicationRelativeUrl_TagHelper" xml:space="preserve">
|
||||
<value>Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting:
|
||||
|
||||
@{3} "{4}, {5}"</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting elements containing attributes with URL expected values.
|
||||
/// </summary>
|
||||
/// <remarks>Resolves URLs starting with '~/' (relative to the application's 'webroot' setting) that are not
|
||||
/// targeted by other <see cref="ITagHelper"/>s. Runs prior to other <see cref="ITagHelper"/>s to ensure
|
||||
/// application-relative URLs are resolved.</remarks>
|
||||
[TargetElement("*", Attributes = "itemid")]
|
||||
[TargetElement("a", Attributes = "href")]
|
||||
[TargetElement("applet", Attributes = "archive")]
|
||||
[TargetElement("area", Attributes = "href")]
|
||||
[TargetElement("audio", Attributes = "src")]
|
||||
[TargetElement("base", Attributes = "href")]
|
||||
[TargetElement("blockquote", Attributes = "cite")]
|
||||
[TargetElement("button", Attributes = "formaction")]
|
||||
[TargetElement("del", Attributes = "cite")]
|
||||
[TargetElement("embed", Attributes = "src")]
|
||||
[TargetElement("form", Attributes = "action")]
|
||||
[TargetElement("html", Attributes = "manifest")]
|
||||
[TargetElement("iframe", Attributes = "src")]
|
||||
[TargetElement("img", Attributes = "src")]
|
||||
[TargetElement("input", Attributes = "src")]
|
||||
[TargetElement("input", Attributes = "formaction")]
|
||||
[TargetElement("ins", Attributes = "cite")]
|
||||
[TargetElement("link", Attributes = "href")]
|
||||
[TargetElement("menuitem", Attributes = "icon")]
|
||||
[TargetElement("object", Attributes = "archive")]
|
||||
[TargetElement("object", Attributes = "data")]
|
||||
[TargetElement("q", Attributes = "cite")]
|
||||
[TargetElement("script", Attributes = "src")]
|
||||
[TargetElement("source", Attributes = "src")]
|
||||
[TargetElement("track", Attributes = "src")]
|
||||
[TargetElement("video", Attributes = "src")]
|
||||
[TargetElement("video", Attributes = "poster")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class UrlResolutionTagHelper : TagHelper
|
||||
{
|
||||
// Valid whitespace characters defined by the HTML5 spec.
|
||||
private static readonly char[] ValidAttributeWhitespaceChars =
|
||||
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
||||
private static readonly IReadOnlyDictionary<string, IEnumerable<string>> ElementAttributeLookups =
|
||||
new Dictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "a", new[] { "href" } },
|
||||
{ "applet", new[] { "archive" } },
|
||||
{ "area", new[] { "href" } },
|
||||
{ "audio", new[] { "src" } },
|
||||
{ "base", new[] { "href" } },
|
||||
{ "blockquote", new[] { "cite" } },
|
||||
{ "button", new[] { "formaction" } },
|
||||
{ "del", new[] { "cite" } },
|
||||
{ "embed", new[] { "src" } },
|
||||
{ "form", new[] { "action" } },
|
||||
{ "html", new[] { "manifest" } },
|
||||
{ "iframe", new[] { "src" } },
|
||||
{ "img", new[] { "src" } },
|
||||
{ "input", new[] { "src", "formaction" } },
|
||||
{ "ins", new[] { "cite" } },
|
||||
{ "link", new[] { "href" } },
|
||||
{ "menuitem", new[] { "icon" } },
|
||||
{ "object", new[] { "archive", "data" } },
|
||||
{ "q", new[] { "cite" } },
|
||||
{ "script", new[] { "src" } },
|
||||
{ "source", new[] { "src" } },
|
||||
{ "track", new[] { "src" } },
|
||||
{ "video", new[] { "poster", "src" } },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UrlResolutionTagHelper"/>.
|
||||
/// </summary>
|
||||
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
|
||||
/// <param name="htmlEncoder">The <see cref="IHtmlEncoder"/>.</param>
|
||||
public UrlResolutionTagHelper(IUrlHelper urlHelper, IHtmlEncoder htmlEncoder)
|
||||
{
|
||||
UrlHelper = urlHelper;
|
||||
HtmlEncoder = htmlEncoder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultOrder.DefaultFrameworkSortOrder - 999;
|
||||
}
|
||||
}
|
||||
|
||||
protected IUrlHelper UrlHelper { get; }
|
||||
|
||||
protected IHtmlEncoder HtmlEncoder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
IEnumerable<string> attributeNames;
|
||||
if (ElementAttributeLookups.TryGetValue(output.TagName, out attributeNames))
|
||||
{
|
||||
foreach (var attributeName in attributeNames)
|
||||
{
|
||||
ProcessUrlAttribute(attributeName, output);
|
||||
}
|
||||
}
|
||||
|
||||
// itemid can be present on any HTML element.
|
||||
ProcessUrlAttribute("itemid", output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves and updates URL values starting with '~/' (relative to the application's 'webroot' setting) for
|
||||
/// <paramref name="output"/>'s <see cref="TagHelperOutput.Attributes"/> whose
|
||||
/// <see cref="TagHelperAttribute.Name"/> is <paramref name="attributeName"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">The attribute name used to lookup values to resolve.</param>
|
||||
/// <param name="output">The <see cref="TagHelperOutput"/>.</param>
|
||||
protected void ProcessUrlAttribute(string attributeName, TagHelperOutput output)
|
||||
{
|
||||
IEnumerable<TagHelperAttribute> attributes;
|
||||
if (output.Attributes.TryGetAttributes(attributeName, out attributes))
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
string resolvedUrl;
|
||||
|
||||
var stringValue = attribute.Value as string;
|
||||
if (stringValue != null)
|
||||
{
|
||||
if (TryResolveUrl(stringValue, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
attribute.Value = resolvedUrl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var htmlStringValue = attribute.Value as HtmlString;
|
||||
if (htmlStringValue != null &&
|
||||
TryResolveUrl(
|
||||
htmlStringValue.ToString(),
|
||||
encodeWebRoot: true,
|
||||
resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
attribute.Value = new HtmlString(resolvedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to resolve the given <paramref name="url"/> value relative to the application's 'webroot' setting.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to resolve.</param>
|
||||
/// <param name="encodeWebRoot">If <c>true</c>, will HTML encode the expansion of '~/'.</param>
|
||||
/// <param name="resolvedUrl">Absolute URL beginning with the application's virtual root. <c>null</c> if
|
||||
/// <paramref name="url"/> could not be resolved.</param>
|
||||
/// <returns><c>true</c> if the <paramref name="url"/> could be resolved; <c>false</c> otherwise.</returns>
|
||||
protected bool TryResolveUrl(string url, bool encodeWebRoot, out string resolvedUrl)
|
||||
{
|
||||
resolvedUrl = null;
|
||||
|
||||
if (url == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var trimmedUrl = url.Trim(ValidAttributeWhitespaceChars);
|
||||
|
||||
// Before doing more work, ensure that the URL we're looking at is app relative.
|
||||
if (trimmedUrl.Length >= 2 && trimmedUrl[0] == '~' && trimmedUrl[1] == '/')
|
||||
{
|
||||
var appRelativeUrl = UrlHelper.Content(trimmedUrl);
|
||||
|
||||
if (encodeWebRoot)
|
||||
{
|
||||
var postTildeSlashUrlValue = trimmedUrl.Substring(2);
|
||||
|
||||
if (!appRelativeUrl.EndsWith(postTildeSlashUrlValue, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatCouldNotResolveApplicationRelativeUrl_TagHelper(
|
||||
url,
|
||||
nameof(IUrlHelper),
|
||||
nameof(IUrlHelper.Content),
|
||||
"removeTagHelper",
|
||||
typeof(UrlResolutionTagHelper).FullName,
|
||||
typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly.GetName().Name));
|
||||
}
|
||||
|
||||
var applicationPath = appRelativeUrl.Substring(0, appRelativeUrl.Length - postTildeSlashUrlValue.Length);
|
||||
var encodedApplicationPath = HtmlEncoder.HtmlEncode(applicationPath);
|
||||
|
||||
resolvedUrl = string.Concat(encodedApplicationPath, postTildeSlashUrlValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedUrl = appRelativeUrl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// The tag helper won't process for cases with just the 'src' attribute.
|
||||
/// </remarks>
|
||||
[TargetElement("img", Attributes = AppendVersionAttributeName + "," + SrcAttributeName)]
|
||||
public class ImageTagHelper : TagHelper
|
||||
public class ImageTagHelper : UrlResolutionTagHelper
|
||||
{
|
||||
private static readonly string Namespace = typeof(ImageTagHelper).Namespace;
|
||||
|
||||
|
|
@ -30,7 +31,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// </summary>
|
||||
/// <param name="hostingEnvironment">The <see cref="IHostingEnvironment"/>.</param>
|
||||
/// <param name="cache">The <see cref="IMemoryCache"/>.</param>
|
||||
public ImageTagHelper(IHostingEnvironment hostingEnvironment, IMemoryCache cache)
|
||||
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
|
||||
public ImageTagHelper(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IMemoryCache cache,
|
||||
IHtmlEncoder htmlEncoder,
|
||||
IUrlHelper urlHelper)
|
||||
: base(urlHelper, htmlEncoder)
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment;
|
||||
Cache = cache;
|
||||
|
|
@ -68,12 +75,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
if (AppendVersion)
|
||||
{
|
||||
EnsureFileVersionProvider();
|
||||
|
||||
string resolvedUrl;
|
||||
if (TryResolveUrl(Src, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
Src = resolvedUrl;
|
||||
}
|
||||
output.Attributes[SrcAttributeName] = _fileVersionProvider.AddFileVersionToPath(Src);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass through attribute that is also a well-known HTML attribute.
|
||||
output.CopyHtmlAttribute(SrcAttributeName, context);
|
||||
ProcessUrlAttribute(SrcAttributeName, output);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[TargetElement("link", Attributes = FallbackTestPropertyAttributeName)]
|
||||
[TargetElement("link", Attributes = FallbackTestValueAttributeName)]
|
||||
[TargetElement("link", Attributes = AppendVersionAttributeName)]
|
||||
public class LinkTagHelper : TagHelper
|
||||
public class LinkTagHelper : UrlResolutionTagHelper
|
||||
{
|
||||
private static readonly string Namespace = typeof(LinkTagHelper).Namespace;
|
||||
|
||||
|
|
@ -91,17 +91,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <param name="cache">The <see cref="IMemoryCache"/>.</param>
|
||||
/// <param name="htmlEncoder">The <see cref="IHtmlEncoder"/>.</param>
|
||||
/// <param name="javaScriptEncoder">The <see cref="IJavaScriptStringEncoder"/>.</param>
|
||||
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
|
||||
public LinkTagHelper(
|
||||
ILogger<LinkTagHelper> logger,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IMemoryCache cache,
|
||||
IHtmlEncoder htmlEncoder,
|
||||
IJavaScriptStringEncoder javaScriptEncoder)
|
||||
IJavaScriptStringEncoder javaScriptEncoder,
|
||||
IUrlHelper urlHelper)
|
||||
: base(urlHelper, htmlEncoder)
|
||||
{
|
||||
Logger = logger;
|
||||
HostingEnvironment = hostingEnvironment;
|
||||
Cache = cache;
|
||||
HtmlEncoder = htmlEncoder;
|
||||
JavaScriptEncoder = javaScriptEncoder;
|
||||
}
|
||||
|
||||
|
|
@ -195,8 +197,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
protected IMemoryCache Cache { get; }
|
||||
|
||||
protected IHtmlEncoder HtmlEncoder { get; }
|
||||
|
||||
protected IJavaScriptStringEncoder JavaScriptEncoder { get; }
|
||||
|
||||
// Internal for ease of use when testing.
|
||||
|
|
@ -205,10 +205,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <inheritdoc />
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
string resolvedUrl;
|
||||
|
||||
// Pass through attribute that is also a well-known HTML attribute.
|
||||
if (Href != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(HrefAttributeName, context);
|
||||
|
||||
// Resolve any application relative URLs (~/) now so they can be used in comparisons later.
|
||||
if (TryResolveUrl(Href, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
Href = resolvedUrl;
|
||||
}
|
||||
|
||||
ProcessUrlAttribute(HrefAttributeName, output);
|
||||
}
|
||||
|
||||
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
|
||||
|
|
@ -243,6 +253,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
|
||||
{
|
||||
if (TryResolveUrl(HrefInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
HrefInclude = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(HrefExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
HrefExclude = resolvedUrl;
|
||||
}
|
||||
|
||||
BuildGlobbedLinkTags(attributes, builder);
|
||||
if (string.IsNullOrEmpty(Href))
|
||||
{
|
||||
|
|
@ -254,6 +273,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
if (mode == Mode.Fallback)
|
||||
{
|
||||
if (TryResolveUrl(FallbackHref, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackHref = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(FallbackHrefInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackHrefInclude = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(FallbackHrefExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackHrefExclude = resolvedUrl;
|
||||
}
|
||||
|
||||
BuildFallbackBlock(builder);
|
||||
}
|
||||
|
||||
|
|
@ -292,7 +324,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
if (AppendVersion == true)
|
||||
{
|
||||
for (var i=0; i < fallbackHrefs.Length; i++)
|
||||
for (var i = 0; i < fallbackHrefs.Length; i++)
|
||||
{
|
||||
// fallbackHrefs come from bound attributes and globbing. Must always be non-null.
|
||||
Debug.Assert(fallbackHrefs[i] != null);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
|
|
@ -28,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[TargetElement("script", Attributes = FallbackSrcExcludeAttributeName)]
|
||||
[TargetElement("script", Attributes = FallbackTestExpressionAttributeName)]
|
||||
[TargetElement("script", Attributes = AppendVersionAttributeName)]
|
||||
public class ScriptTagHelper : TagHelper
|
||||
public class ScriptTagHelper : UrlResolutionTagHelper
|
||||
{
|
||||
private const string SrcIncludeAttributeName = "asp-src-include";
|
||||
private const string SrcExcludeAttributeName = "asp-src-exclude";
|
||||
|
|
@ -78,17 +77,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <param name="cache">The <see cref="IMemoryCache"/>.</param>
|
||||
/// <param name="htmlEncoder">The <see cref="IHtmlEncoder"/>.</param>
|
||||
/// <param name="javaScriptEncoder">The <see cref="IJavaScriptStringEncoder"/>.</param>
|
||||
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
|
||||
public ScriptTagHelper(
|
||||
ILogger<ScriptTagHelper> logger,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IMemoryCache cache,
|
||||
IHtmlEncoder htmlEncoder,
|
||||
IJavaScriptStringEncoder javaScriptEncoder)
|
||||
IJavaScriptStringEncoder javaScriptEncoder,
|
||||
IUrlHelper urlHelper)
|
||||
: base(urlHelper, htmlEncoder)
|
||||
{
|
||||
Logger = logger;
|
||||
HostingEnvironment = hostingEnvironment;
|
||||
Cache = cache;
|
||||
HtmlEncoder = htmlEncoder;
|
||||
JavaScriptEncoder = javaScriptEncoder;
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +165,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
protected IMemoryCache Cache { get; }
|
||||
|
||||
protected IHtmlEncoder HtmlEncoder { get; }
|
||||
|
||||
protected IJavaScriptStringEncoder JavaScriptEncoder { get; }
|
||||
|
||||
// Internal for ease of use when testing.
|
||||
|
|
@ -174,10 +173,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <inheritdoc />
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
string resolvedUrl;
|
||||
|
||||
// Pass through attribute that is also a well-known HTML attribute.
|
||||
if (Src != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(SrcAttributeName, context);
|
||||
|
||||
if (TryResolveUrl(Src, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
Src = resolvedUrl;
|
||||
}
|
||||
|
||||
ProcessUrlAttribute(SrcAttributeName, output);
|
||||
}
|
||||
|
||||
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
|
||||
|
|
@ -212,6 +220,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
|
||||
{
|
||||
if (TryResolveUrl(SrcInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
SrcInclude = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(SrcExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
SrcExclude = resolvedUrl;
|
||||
}
|
||||
|
||||
BuildGlobbedScriptTags(attributes, builder);
|
||||
if (string.IsNullOrEmpty(Src))
|
||||
{
|
||||
|
|
@ -223,6 +240,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
if (mode == Mode.Fallback)
|
||||
{
|
||||
if (TryResolveUrl(FallbackSrc, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackSrc = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(FallbackSrcInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackSrcInclude = resolvedUrl;
|
||||
}
|
||||
if (TryResolveUrl(FallbackSrcExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
||||
{
|
||||
FallbackSrcExclude = resolvedUrl;
|
||||
}
|
||||
|
||||
BuildFallbackBlock(attributes, builder);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
public class UrlHelperTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, null, null)]
|
||||
[InlineData("/myapproot", null, null)]
|
||||
[InlineData("", "/Home/About", "/Home/About")]
|
||||
[InlineData("/myapproot", "/test", "/test")]
|
||||
public void Content_ReturnsContentPath_WhenItDoesNotStartWithToken(string appRoot,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using RazorWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class UrlResolutionTest
|
||||
{
|
||||
private const string SiteName = nameof(RazorWebSite);
|
||||
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
|
||||
private static readonly Assembly _resourcesAssembly = typeof(UrlResolutionTest).GetTypeInfo().Assembly;
|
||||
|
||||
[Fact]
|
||||
public async Task AppRelativeUrlsAreResolvedCorrectly()
|
||||
{
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
|
||||
var client = server.CreateClient();
|
||||
var outputFile = "compiler/resources/RazorWebSite.UrlResolution.Index.html";
|
||||
var expectedContent =
|
||||
await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/UrlResolution/Index");
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
responseContent = responseContent.Trim();
|
||||
#if GENERATE_BASELINES
|
||||
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent);
|
||||
#else
|
||||
Assert.Equal(expectedContent.Trim(), responseContent, ignoreLineEndingDifferences: true);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppRelativeUrlsAreResolvedAndEncodedCorrectly()
|
||||
{
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
_configureServices(services);
|
||||
services.AddTransient<IHtmlEncoder, TestHtmlEncoder>();
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
var outputFile = "compiler/resources/RazorWebSite.UrlResolution.Index.Encoded.html";
|
||||
var expectedContent =
|
||||
await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/UrlResolution/Index");
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
responseContent = responseContent.Trim();
|
||||
#if GENERATE_BASELINES
|
||||
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent);
|
||||
#else
|
||||
Assert.Equal(expectedContent.Trim(), responseContent, ignoreLineEndingDifferences: true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<input checked="checked" />
|
||||
<input checked="checked" />
|
||||
<input />
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<h2>Image Tag Helper Test</h2>
|
||||
<!-- Plain image tag -->
|
||||
<img src="/images/red.png" alt="Red block" title="<the title>">
|
||||
<img src="/images/red.png" alt="Red block" title="<the title>" />
|
||||
|
||||
<!-- Plain image tag with file version -->
|
||||
<img alt="Red versioned" title="Red versioned" src="/images/red.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk" />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<title>Link</title>
|
||||
|
||||
<!-- Plain link tag -->
|
||||
<link href="HtmlEncode[[/site.css]]" rel="stylesheet" title="<the title>" />
|
||||
<link href="HtmlEncode[[/]]site.css" rel="stylesheet" title="<the title>" />
|
||||
|
||||
<!-- Globbed link tag with existing file -->
|
||||
<link rel="stylesheet" title="<the title>" href="HtmlEncode[[/site.css]]" />
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<h2>Script tag helper test</h2>
|
||||
<script src="HtmlEncode[[/site.js]]" data-foo="foo-data1" title="<the title>">
|
||||
<script src="HtmlEncode[[/]]site.js" data-foo="foo-data1" title="<the title>">
|
||||
// Regular script with comment in body, and extra properties.
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<html manifest="HtmlEncode[[/]]Person">
|
||||
<head>
|
||||
<base href="HtmlEncode[[/]]A+Really(Crazy),Url.Is:This/HtmlEncode[[John Doe]]/Detail" target="_blank" />
|
||||
</head>
|
||||
<body>
|
||||
<dl itemscope itemid="HtmlEncode[[/]]Url"></dl>
|
||||
<a href="HtmlEncode[[/]]Person">Person</a>
|
||||
<area href="HtmlEncode[[/]]Person/HtmlEncode[[John Doe]]" alt="Url stuff" />
|
||||
<link href="HtmlEncode[[/]]Person/HtmlEncode[[John Doe]]/CSS" rel="stylesheet" />
|
||||
<video poster="HtmlEncode[[~/SomeUrl]]" src="HtmlEncode[[~/SomeUrl]]/HtmlEncode[[video]]" />
|
||||
<audio src="HtmlEncode[[~/SomeUrl]]">
|
||||
<source src="HtmlEncode[[/]]Person" />
|
||||
<track src="HtmlEncode[[/]]emailHtmlEncode[[~/SomeUrl]]" />
|
||||
</audio>
|
||||
<embed src="HtmlEncode[[/]]email@dyanmicUrl" />
|
||||
<iframe src="HtmlEncode[[~/SomeUrl]]" />
|
||||
<img src="HtmlEncode[[/]]HtmlEncode[[~/SomeUrl]]" />
|
||||
<script src="HtmlEncode[[/]]Person/HtmlEncode[[John Doe]]/JS"></script>
|
||||
<input src="HtmlEncode[[/]]/Person" itemscope itemid="HtmlEncode[[/]]Person" formaction="HtmlEncode[[~/SomeUrl]]" />
|
||||
<button formaction="HtmlEncode[[/]]\Person" />
|
||||
<form action="HtmlEncode[[/]]~Person" />
|
||||
<blockquote cite="HtmlEncode[[/]]Person" />
|
||||
<del cite="HtmlEncode[[/]]Person" />
|
||||
<ins cite="HtmlEncode[[/]]Person" />
|
||||
<q cite="HtmlEncode[[/]]Person" />
|
||||
<menu>
|
||||
<menuitem icon="HtmlEncode[[/]]Person" />
|
||||
</menu>
|
||||
<object data="HtmlEncode[[/]]Person" archive="HtmlEncode[[/]]Person/HtmlEncode[[John Doe]]" data="HtmlEncode[[/]]Person" archive="HtmlEncode[[/]]Person/HtmlEncode[[John Doe]]" />
|
||||
<applet archive="HtmlEncode[[/]]A+Really(Crazy),Url.Is:This/HtmlEncode[[John Doe]]/Detail" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<html manifest="/Person">
|
||||
<head>
|
||||
<base href="/A+Really(Crazy),Url.Is:This/John Doe/Detail" target="_blank" />
|
||||
</head>
|
||||
<body>
|
||||
<dl itemscope itemid="/Url"></dl>
|
||||
<a href="/Person">Person</a>
|
||||
<area href="/Person/John Doe" alt="Url stuff" />
|
||||
<link href="/Person/John Doe/CSS" rel="stylesheet" />
|
||||
<video poster="/SomeUrl" src="/SomeUrl/video" />
|
||||
<audio src="/SomeUrl">
|
||||
<source src="/Person" />
|
||||
<track src="/email~/SomeUrl" />
|
||||
</audio>
|
||||
<embed src="/email@dyanmicUrl" />
|
||||
<iframe src="/SomeUrl" />
|
||||
<img src="/~/SomeUrl" />
|
||||
<script src="/Person/John Doe/JS"></script>
|
||||
<input src="//Person" itemscope itemid="/Person" formaction="/SomeUrl" />
|
||||
<button formaction="/\Person" />
|
||||
<form action="/~Person" />
|
||||
<blockquote cite="/Person" />
|
||||
<del cite="/Person" />
|
||||
<ins cite="/Person" />
|
||||
<q cite="/Person" />
|
||||
<menu>
|
||||
<menuitem icon="/Person" />
|
||||
</menu>
|
||||
<object data="/Person" archive="/Person/John Doe" data="/Person" archive="/Person/John Doe" />
|
||||
<applet archive="/A+Really(Crazy),Url.Is:This/John Doe/Detail" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||
{
|
||||
public class UrlResolutionTagHelperTest
|
||||
{
|
||||
public static TheoryData ResolvableUrlData
|
||||
{
|
||||
get
|
||||
{
|
||||
// url, expectedHref
|
||||
return new TheoryData<object, object>
|
||||
{
|
||||
{ "~/home/index.html", "/approot/home/index.html" },
|
||||
{ " ~/home/index.html", "/approot/home/index.html" },
|
||||
{ new HtmlString("~/home/index.html"), new HtmlString("HtmlEncode[[/approot/]]home/index.html") },
|
||||
{
|
||||
new HtmlString(" ~/home/index.html"),
|
||||
new HtmlString("HtmlEncode[[/approot/]]home/index.html")
|
||||
},
|
||||
{
|
||||
"~/home/index.html ~/secondValue/index.html",
|
||||
"/approot/home/index.html ~/secondValue/index.html"
|
||||
},
|
||||
{
|
||||
new HtmlString("~/home/index.html ~/secondValue/index.html"),
|
||||
new HtmlString("HtmlEncode[[/approot/]]home/index.html ~/secondValue/index.html")
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ResolvableUrlData))]
|
||||
public void Process_ResolvesTildeSlashValues(object url, object expectedHref)
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
tagName: "a",
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "href", url }
|
||||
});
|
||||
var urlHelperMock = new Mock<IUrlHelper>();
|
||||
urlHelperMock
|
||||
.Setup(urlHelper => urlHelper.Content(It.IsAny<string>()))
|
||||
.Returns(new Func<string, string>(value => "/approot" + value.Substring(1)));
|
||||
var tagHelper = new UrlResolutionTagHelper(urlHelperMock.Object, new TestHtmlEncoder());
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context: null, output: tagHelperOutput);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||
Assert.IsType(expectedHref.GetType(), url);
|
||||
Assert.Equal(expectedHref.ToString(), attribute.Value.ToString());
|
||||
Assert.False(attribute.Minimized);
|
||||
}
|
||||
|
||||
public static TheoryData UnresolvableUrlData
|
||||
{
|
||||
get
|
||||
{
|
||||
// url
|
||||
return new TheoryData<object>
|
||||
{
|
||||
{ "/home/index.html" },
|
||||
{ "~ /home/index.html" },
|
||||
{ "/home/index.html ~/second/wontresolve.html" },
|
||||
{ " ~\\home\\index.html" },
|
||||
{ "~\\/home/index.html" },
|
||||
{ new HtmlString("/home/index.html") },
|
||||
{ new HtmlString("~ /home/index.html") },
|
||||
{ new HtmlString("/home/index.html ~/second/wontresolve.html") },
|
||||
{ new HtmlString("~\\home\\index.html") },
|
||||
{ new HtmlString("~\\/home/index.html") },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(UnresolvableUrlData))]
|
||||
public void Process_DoesNotResolveNonTildeSlashValues(object url)
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
tagName: "a",
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "href", url }
|
||||
});
|
||||
var urlHelperMock = new Mock<IUrlHelper>();
|
||||
urlHelperMock
|
||||
.Setup(urlHelper => urlHelper.Content(It.IsAny<string>()))
|
||||
.Returns("approot/home/index.html");
|
||||
var tagHelper = new UrlResolutionTagHelper(urlHelperMock.Object, htmlEncoder: null);
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context: null, output: tagHelperOutput);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||
Assert.Equal(url, attribute.Value);
|
||||
Assert.False(attribute.Minimized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_IgnoresNonHtmlStringOrStringValues()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
tagName: "a",
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "href", true }
|
||||
});
|
||||
var tagHelper = new UrlResolutionTagHelper(urlHelper: null, htmlEncoder: null);
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context: null, output: tagHelperOutput);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||
Assert.Equal(true, attribute.Value);
|
||||
Assert.False(attribute.Minimized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_ThrowsWhenEncodingNeededAndIUrlHelperActsUnexpectedly()
|
||||
{
|
||||
// Arrange
|
||||
var relativeUrl = "~/home/index.html";
|
||||
var expectedExceptionMessage = Resources.FormatCouldNotResolveApplicationRelativeUrl_TagHelper(
|
||||
relativeUrl,
|
||||
nameof(IUrlHelper),
|
||||
nameof(IUrlHelper.Content),
|
||||
"removeTagHelper",
|
||||
typeof(UrlResolutionTagHelper).FullName,
|
||||
typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly.GetName().Name);
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
tagName: "a",
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "href", new HtmlString(relativeUrl) }
|
||||
});
|
||||
var urlHelperMock = new Mock<IUrlHelper>();
|
||||
urlHelperMock
|
||||
.Setup(urlHelper => urlHelper.Content(It.IsAny<string>()))
|
||||
.Returns("UnexpectedResult");
|
||||
var tagHelper = new UrlResolutionTagHelper(urlHelperMock.Object, htmlEncoder: null);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => tagHelper.Process(context: null, output: tagHelperOutput));
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ using Microsoft.Framework.Caching;
|
|||
using Microsoft.Framework.Caching.Memory;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Microsoft.Framework.WebEncoders.Testing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
|
|
@ -58,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache())
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new CommonTestEncoder(), MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Src = "testimage.png",
|
||||
|
|
@ -101,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache())
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new CommonTestEncoder(), MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Src = "/images/test-image.png",
|
||||
|
|
@ -137,7 +138,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache())
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new CommonTestEncoder(), MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Src = "/images/test-image.png",
|
||||
|
|
@ -175,7 +176,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext("/bar");
|
||||
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache())
|
||||
var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new CommonTestEncoder(), MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Src = "/bar/images/image.jpg",
|
||||
|
|
@ -271,5 +272,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Returns(new object());
|
||||
return cache.Object;
|
||||
}
|
||||
|
||||
private static IUrlHelper MakeUrlHelper()
|
||||
{
|
||||
var urlHelper = new Mock<IUrlHelper>();
|
||||
|
||||
urlHelper
|
||||
.Setup(helper => helper.Content(It.IsAny<string>()))
|
||||
.Returns(new Func<string, string>(url => url));
|
||||
|
||||
return urlHelper.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
FallbackHref = "test.css",
|
||||
|
|
@ -228,7 +229,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object
|
||||
|
|
@ -323,7 +325,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object
|
||||
|
|
@ -369,7 +372,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
FallbackHref = "test.css",
|
||||
|
|
@ -478,7 +482,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -509,7 +514,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -551,7 +557,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -595,7 +602,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -639,7 +647,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Href = "/css/site.css",
|
||||
|
|
@ -680,7 +689,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
Href = "/bar/css/site.css",
|
||||
|
|
@ -725,7 +735,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -834,5 +845,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Returns(result);
|
||||
return cache.Object;
|
||||
}
|
||||
|
||||
private static IUrlHelper MakeUrlHelper()
|
||||
{
|
||||
var urlHelper = new Mock<IUrlHelper>();
|
||||
|
||||
urlHelper
|
||||
.Setup(helper => helper.Content(It.IsAny<string>()))
|
||||
.Returns(new Func<string, string>(url => url));
|
||||
|
||||
return urlHelper.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
FallbackSrc = "~/blank.js",
|
||||
|
|
@ -220,7 +221,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object
|
||||
|
|
@ -316,7 +318,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object
|
||||
|
|
@ -411,7 +414,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -445,7 +449,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -488,7 +493,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -517,7 +523,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
|
@ -568,7 +575,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
FallbackSrc = "~/blank.js",
|
||||
|
|
@ -609,7 +617,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -649,7 +658,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
hostingEnvironment,
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -689,7 +699,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
AppendVersion = true,
|
||||
|
|
@ -727,7 +738,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
AppendVersion = true,
|
||||
|
|
@ -767,7 +779,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
ViewContext = viewContext,
|
||||
FallbackSrc = "fallback.js",
|
||||
|
|
@ -813,7 +826,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
MakeHostingEnvironment(),
|
||||
MakeCache(),
|
||||
new CommonTestEncoder(),
|
||||
new CommonTestEncoder())
|
||||
new CommonTestEncoder(),
|
||||
MakeUrlHelper())
|
||||
{
|
||||
GlobbingUrlBuilder = globbingUrlBuilder.Object,
|
||||
ViewContext = viewContext,
|
||||
|
|
@ -929,5 +943,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Returns(result);
|
||||
return cache.Object;
|
||||
}
|
||||
|
||||
private static IUrlHelper MakeUrlHelper()
|
||||
{
|
||||
var urlHelper = new Mock<IUrlHelper>();
|
||||
|
||||
urlHelper
|
||||
.Setup(helper => helper.Content(It.IsAny<string>()))
|
||||
.Returns(new Func<string, string>(url => url));
|
||||
|
||||
return urlHelper.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<h2>Image Tag Helper Test</h2>
|
||||
<!-- Plain image tag -->
|
||||
<img src="~/images/red.png" alt="Red block" title="<the title>">
|
||||
<img src="~/images/red.png" alt="Red block" title="<the title>" />
|
||||
|
||||
<!-- Plain image tag with file version -->
|
||||
<img src="~/images/red.png" alt="Red versioned" title="Red versioned" asp-append-version="true" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace RazorWebSite.Controllers
|
||||
{
|
||||
public class UrlResolutionController : Controller
|
||||
{
|
||||
public IActionResult Index()
|
||||
{
|
||||
var model = new Person
|
||||
{
|
||||
Name = "John Doe"
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
@model Person
|
||||
|
||||
@{
|
||||
var dynamicUrl = "~/SomeUrl";
|
||||
}
|
||||
|
||||
<html manifest="~/Person">
|
||||
<head>
|
||||
<base href="~/A+Really(Crazy),Url.Is:This/@Model.Name/Detail" target="_blank"/>
|
||||
</head>
|
||||
<body>
|
||||
<dl itemscope itemid="~/Url"></dl>
|
||||
<a href="~/Person">Person</a>
|
||||
<area href="~/Person/@Model.Name" alt="Url stuff" />
|
||||
<link href="~/Person/@Model.Name/CSS" rel="stylesheet" />
|
||||
<video poster=@dynamicUrl src='@dynamicUrl/@("video")' />
|
||||
<audio src="@(dynamicUrl)">
|
||||
<source src="~/Person" />
|
||||
<track src="~/email@(dynamicUrl)" />
|
||||
</audio>
|
||||
<embed src="~/email@dyanmicUrl" />
|
||||
<iframe src=@(dynamicUrl) />
|
||||
<img src="~/@dynamicUrl" />
|
||||
<script src="~/Person/@Model.Name/JS"></script>
|
||||
<input src="~//Person" itemscope itemid="~/Person" formaction=@dynamicUrl />
|
||||
<button formaction="~/\Person" />
|
||||
<form action="~/~Person" />
|
||||
<blockquote cite="~/Person" />
|
||||
<del cite="~/Person" />
|
||||
<ins cite="~/Person" />
|
||||
<q cite="~/Person" />
|
||||
<menu>
|
||||
<menuitem icon=~/Person />
|
||||
</menu>
|
||||
<object data="~/Person" archive="~/Person/@Model.Name" data="~/Person" archive="~/Person/@Model.Name" />
|
||||
<applet archive="~/A+Really(Crazy),Url.Is:This/@Model.Name/Detail" />
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue