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:
N. Taylor Mullen 2015-07-16 12:39:33 -07:00
parent 6d228a62dc
commit 0ef68eefc8
23 changed files with 831 additions and 81 deletions

View File

@ -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);
}

View File

@ -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"
};

View File

@ -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);

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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
}
}
}

View File

@ -1,4 +1,3 @@
<input checked="checked" />
<input checked="checked" />
<input />

View File

@ -9,7 +9,7 @@
<h2>Image Tag Helper Test</h2>
<!-- Plain image tag -->
<img src="/images/red.png" alt="Red block" title="&lt;the title>">
<img src="/images/red.png" alt="Red block" title="&lt;the title>" />
<!-- Plain image tag with file version -->
<img alt="Red versioned" title="Red versioned" src="/images/red.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk" />

View File

@ -5,7 +5,7 @@
<title>Link</title>
<!-- Plain link tag -->
<link href="HtmlEncode[[/site.css]]" rel="stylesheet" title="&lt;the title>" />
<link href="HtmlEncode[[/]]site.css" rel="stylesheet" title="&lt;the title>" />
<!-- Globbed link tag with existing file -->
<link rel="stylesheet" title="&lt;the title>" href="HtmlEncode[[/site.css]]" />

View File

@ -6,7 +6,7 @@
</head>
<body>
<h2>Script tag helper test</h2>
<script src="HtmlEncode[[/site.js]]" data-foo="foo-data1" title="&lt;the title>">
<script src="HtmlEncode[[/]]site.js" data-foo="foo-data1" title="&lt;the title>">
// Regular script with comment in body, and extra properties.
</script>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -11,7 +11,7 @@
<h2>Image Tag Helper Test</h2>
<!-- Plain image tag -->
<img src="~/images/red.png" alt="Red block" title="&lt;the title>">
<img src="~/images/red.png" alt="Red block" title="&lt;the title>" />
<!-- Plain image tag with file version -->
<img src="~/images/red.png" alt="Red versioned" title="Red versioned" asp-append-version="true" />

View File

@ -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);
}
}
}

View File

@ -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>