Clean up remaining `HtmlString` usage
- #3122 and #3123 (5 of 5) - avoid concatenating values and wrapping them in an `HtmlString` - switch from `string.Format()` to `AppendHtml()` in `LinkTagHelper` - simplify `RazorPage` and `TagHelperContentExtensions` e.g. don't use a `TagHelperContentWrapperTextWriter` - add some special cases in `UrlResolutionTagHelper` - add `TagBuilder` and `StringHtmlContent` special cases to avoid `StringWriter` use when value is an `HtmlString` - move `HtmlTextWriter` optimizations a bit lower and add missing checks for that type nits: - correct comments that incorrectly mention `HtmlString`s - update comments in `JavaScriptStringArrayEncoder`
This commit is contained in:
parent
dd774366f6
commit
1f76b3c7b7
|
|
@ -602,22 +602,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
if (!_tagHelperAttributeInfo.Suppressed)
|
if (!_tagHelperAttributeInfo.Suppressed)
|
||||||
{
|
{
|
||||||
HtmlString htmlString;
|
executionContext.AddHtmlAttribute(
|
||||||
|
_tagHelperAttributeInfo.Name,
|
||||||
if (_valueBuffer != null)
|
_valueBuffer?.Content ?? HtmlString.Empty);
|
||||||
{
|
_valueBuffer = null;
|
||||||
using (var stringWriter = new StringWriter())
|
|
||||||
{
|
|
||||||
_valueBuffer.Content.WriteTo(stringWriter, HtmlEncoder);
|
|
||||||
htmlString = new HtmlString(stringWriter.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
htmlString = HtmlString.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
executionContext.AddHtmlAttribute(_tagHelperAttributeInfo.Name, htmlString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -843,7 +831,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// content to the <see cref="HttpResponse.Body"/>.
|
/// content to the <see cref="HttpResponse.Body"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="Task{HtmlString}"/> that represents the asynchronous flush operation and on
|
/// <returns>A <see cref="Task{HtmlString}"/> that represents the asynchronous flush operation and on
|
||||||
/// completion returns a <see cref="HtmlString.Empty"/>.</returns>
|
/// completion returns <see cref="HtmlString.Empty"/>.</returns>
|
||||||
/// <remarks>The value returned is a token value that allows FlushAsync to work directly in an HTML
|
/// <remarks>The value returned is a token value that allows FlushAsync to work directly in an HTML
|
||||||
/// section. However the value does not represent the rendered content.
|
/// section. However the value does not represent the rendered content.
|
||||||
/// This method also writes out headers, so any modifications to headers must be done before
|
/// This method also writes out headers, so any modifications to headers must be done before
|
||||||
|
|
@ -936,7 +924,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets antiforgery cookie and X-Frame-Options header on the response.
|
/// Sets antiforgery cookie and X-Frame-Options header on the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="HtmlString"/> that returns a <see cref="HtmlString.Empty"/>.</returns>
|
/// <returns><see cref="HtmlString.Empty"/>.</returns>
|
||||||
/// <remarks> Call this method to send antiforgery cookie token and X-Frame-Options header to client
|
/// <remarks> Call this method to send antiforgery cookie token and X-Frame-Options header to client
|
||||||
/// before <see cref="FlushAsync"/> flushes the headers. </remarks>
|
/// before <see cref="FlushAsync"/> flushes the headers. </remarks>
|
||||||
public virtual HtmlString SetAntiforgeryCookieAndHeader()
|
public virtual HtmlString SetAntiforgeryCookieAndHeader()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNet.Html;
|
using Microsoft.AspNet.Html;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNet.Html;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
|
|
@ -160,26 +162,45 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
{
|
{
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
string resolvedUrl;
|
|
||||||
|
|
||||||
var stringValue = attribute.Value as string;
|
var stringValue = attribute.Value as string;
|
||||||
if (stringValue != null)
|
if (stringValue != null)
|
||||||
{
|
{
|
||||||
if (TryResolveUrl(stringValue, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
string resolvedUrl;
|
||||||
|
if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl))
|
||||||
{
|
{
|
||||||
attribute.Value = resolvedUrl;
|
attribute.Value = resolvedUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var htmlStringValue = attribute.Value as HtmlString;
|
var htmlContent = attribute.Value as IHtmlContent;
|
||||||
if (htmlStringValue != null &&
|
if (htmlContent != null)
|
||||||
TryResolveUrl(
|
|
||||||
htmlStringValue.ToString(),
|
|
||||||
encodeWebRoot: true,
|
|
||||||
resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
{
|
||||||
attribute.Value = new HtmlString(resolvedUrl);
|
var htmlString = htmlContent as HtmlString;
|
||||||
|
if (htmlString != null)
|
||||||
|
{
|
||||||
|
// No need for a StringWriter in this case.
|
||||||
|
stringValue = htmlString.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
htmlContent.WriteTo(writer, HtmlEncoder);
|
||||||
|
stringValue = writer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IHtmlContent resolvedUrl;
|
||||||
|
if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl))
|
||||||
|
{
|
||||||
|
attribute.Value = resolvedUrl;
|
||||||
|
}
|
||||||
|
else if (htmlString == null)
|
||||||
|
{
|
||||||
|
// Not a ~/ URL. Just avoid re-encoding the attribute value later.
|
||||||
|
attribute.Value = new HtmlString(stringValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,14 +211,12 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
/// Tries to resolve the given <paramref name="url"/> value relative to the application's 'webroot' setting.
|
/// Tries to resolve the given <paramref name="url"/> value relative to the application's 'webroot' setting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">The URL to resolve.</param>
|
/// <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
|
/// <param name="resolvedUrl">Absolute URL beginning with the application's virtual root. <c>null</c> if
|
||||||
/// <paramref name="url"/> could not be resolved.</param>
|
/// <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>
|
/// <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)
|
protected bool TryResolveUrl(string url, out string resolvedUrl)
|
||||||
{
|
{
|
||||||
resolvedUrl = null;
|
resolvedUrl = null;
|
||||||
|
|
||||||
if (url == null)
|
if (url == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -205,42 +224,93 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
|
|
||||||
var trimmedUrl = url.Trim(ValidAttributeWhitespaceChars);
|
var trimmedUrl = url.Trim(ValidAttributeWhitespaceChars);
|
||||||
|
|
||||||
// Before doing more work, ensure that the URL we're looking at is app relative.
|
// Before doing more work, ensure that the URL we're looking at is app-relative.
|
||||||
if (trimmedUrl.Length >= 2 && trimmedUrl[0] == '~' && trimmedUrl[1] == '/')
|
if (trimmedUrl.Length >= 2 && trimmedUrl[0] == '~' && trimmedUrl[1] == '/')
|
||||||
{
|
{
|
||||||
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
|
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
|
||||||
var appRelativeUrl = urlHelper.Content(trimmedUrl);
|
resolvedUrl = 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.Encode(applicationPath);
|
|
||||||
|
|
||||||
resolvedUrl = string.Concat(encodedApplicationPath, postTildeSlashUrlValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resolvedUrl = appRelativeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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="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, out IHtmlContent 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 urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
|
||||||
|
var appRelativeUrl = urlHelper.Content(trimmedUrl);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedUrl = new EncodeFirstSegmentContent(
|
||||||
|
appRelativeUrl,
|
||||||
|
appRelativeUrl.Length - postTildeSlashUrlValue.Length,
|
||||||
|
postTildeSlashUrlValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EncodeFirstSegmentContent : IHtmlContent
|
||||||
|
{
|
||||||
|
private readonly string _firstSegment;
|
||||||
|
private readonly int _firstSegmentLength;
|
||||||
|
private readonly string _secondSegment;
|
||||||
|
|
||||||
|
public EncodeFirstSegmentContent(string firstSegment, int firstSegmentLength, string secondSegment)
|
||||||
|
{
|
||||||
|
_firstSegment = firstSegment;
|
||||||
|
_firstSegmentLength = firstSegmentLength;
|
||||||
|
_secondSegment = secondSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
var htmlTextWriter = writer as HtmlTextWriter;
|
||||||
|
if (htmlTextWriter != null)
|
||||||
|
{
|
||||||
|
htmlTextWriter.Write(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encoder.Encode(writer, _firstSegment, 0, _firstSegmentLength);
|
||||||
|
writer.Write(_secondSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
// Load the JavaScript from embedded resource
|
// Load the JavaScript from embedded resource
|
||||||
using (var resourceStream = getManifestResourceStream(key))
|
using (var resourceStream = getManifestResourceStream(key))
|
||||||
{
|
{
|
||||||
Debug.Assert(resourceStream != null, "Embedded resource missing. Ensure 'prebuild' script has run.");
|
Debug.Assert(
|
||||||
|
resourceStream != null,
|
||||||
|
"Embedded resource missing. Ensure 'prebuild' script has run.");
|
||||||
|
|
||||||
using (var streamReader = new StreamReader(resourceStream))
|
using (var streamReader = new StreamReader(resourceStream))
|
||||||
{
|
{
|
||||||
|
|
@ -49,14 +51,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string PrepareFormatString(string input)
|
private static string PrepareFormatString(string input)
|
||||||
{
|
{
|
||||||
// Replace unescaped/escaped chars with their equivalent
|
// Remove final ");". Those characters are in the file only to allow minification.
|
||||||
return input.Replace("{", "{{")
|
return input.Substring(0, input.Length - 2);
|
||||||
.Replace("}", "}}")
|
|
||||||
.Replace("[[[", "{")
|
|
||||||
.Replace("]]]", "}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,35 +1,34 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Methods for encoding <see cref="IEnumerable{string}"/> for use as a JavaScript array literal.
|
/// Methods for encoding <c>string[]</c>s for use as a JavaScript array literal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JavaScriptStringArrayEncoder
|
public static class JavaScriptStringArrayEncoder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encodes a .NET string array for safe use as a JavaScript array literal, including inline in an HTML file.
|
/// Encodes a <c>string[]</c> for safe use as a JavaScript array literal in many contexts, including
|
||||||
|
/// inline in an HTML file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Encode(JavaScriptEncoder encoder, string[] values)
|
public static string Encode(JavaScriptEncoder encoder, string[] values)
|
||||||
{
|
{
|
||||||
var writer = new StringWriter();
|
var writer = new StringWriter();
|
||||||
|
|
||||||
var firstAdded = false;
|
|
||||||
|
|
||||||
writer.Write('[');
|
writer.Write('[');
|
||||||
|
|
||||||
// Perf: Avoid allocating enumerator
|
// Perf: Avoid allocating enumerator
|
||||||
|
var firstAdded = false;
|
||||||
for (var i = 0; i < values.Length; i++)
|
for (var i = 0; i < values.Length; i++)
|
||||||
{
|
{
|
||||||
if (firstAdded)
|
if (firstAdded)
|
||||||
{
|
{
|
||||||
writer.Write(',');
|
writer.Write(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write('"');
|
writer.Write('"');
|
||||||
encoder.Encode(writer, values[i]);
|
encoder.Encode(writer, values[i]);
|
||||||
writer.Write('"');
|
writer.Write('"');
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||||
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -287,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
if (mode == Mode.Fallback)
|
if (mode == Mode.Fallback)
|
||||||
{
|
{
|
||||||
string resolvedUrl;
|
string resolvedUrl;
|
||||||
if (TryResolveUrl(FallbackHref, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
if (TryResolveUrl(FallbackHref, resolvedUrl: out resolvedUrl))
|
||||||
{
|
{
|
||||||
FallbackHref = resolvedUrl;
|
FallbackHref = resolvedUrl;
|
||||||
}
|
}
|
||||||
|
|
@ -350,16 +349,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
// Build the <script /> tag that checks the effective style of <meta /> tag above and renders the extra
|
// Build the <script /> tag that checks the effective style of <meta /> tag above and renders the extra
|
||||||
// <link /> tag to load the fallback stylesheet if the test CSS property value is found to be false,
|
// <link /> tag to load the fallback stylesheet if the test CSS property value is found to be false,
|
||||||
// indicating that the primary stylesheet failed to load.
|
// indicating that the primary stylesheet failed to load.
|
||||||
|
// GetEmbeddedJavaScript returns JavaScript to which we add '"{0}","{1}",{2});'
|
||||||
builder
|
builder
|
||||||
.AppendHtml("<script>")
|
.AppendHtml("<script>")
|
||||||
.AppendHtml(
|
.AppendHtml(JavaScriptResources.GetEmbeddedJavaScript(FallbackJavaScriptResourceName))
|
||||||
string.Format(
|
.AppendHtml("\"")
|
||||||
CultureInfo.InvariantCulture,
|
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestProperty))
|
||||||
JavaScriptResources.GetEmbeddedJavaScript(FallbackJavaScriptResourceName),
|
.AppendHtml("\",\"")
|
||||||
JavaScriptEncoder.Encode(FallbackTestProperty),
|
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestValue))
|
||||||
JavaScriptEncoder.Encode(FallbackTestValue),
|
.AppendHtml("\",")
|
||||||
JavaScriptStringArrayEncoder.Encode(JavaScriptEncoder, fallbackHrefs)))
|
.AppendHtml(JavaScriptStringArrayEncoder.Encode(JavaScriptEncoder, fallbackHrefs))
|
||||||
.AppendHtml("</script>");
|
.AppendHtml(");</script>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,7 +410,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
builder
|
builder
|
||||||
.AppendHtml(attribute.Name)
|
.AppendHtml(attribute.Name)
|
||||||
.AppendHtml("=\"")
|
.AppendHtml("=\"")
|
||||||
.Append(HtmlEncoder, ViewContext.Writer.Encoding, attributeValue)
|
.Append(HtmlEncoder, attributeValue)
|
||||||
.AppendHtml("\" ");
|
.AppendHtml("\" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
if (mode == Mode.Fallback)
|
if (mode == Mode.Fallback)
|
||||||
{
|
{
|
||||||
string resolvedUrl;
|
string resolvedUrl;
|
||||||
if (TryResolveUrl(FallbackSrc, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
if (TryResolveUrl(FallbackSrc, resolvedUrl: out resolvedUrl))
|
||||||
{
|
{
|
||||||
FallbackSrc = resolvedUrl;
|
FallbackSrc = resolvedUrl;
|
||||||
}
|
}
|
||||||
|
|
@ -418,10 +418,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// HTML-encoded the given value if necessary.
|
// HTML-encode the given value if necessary.
|
||||||
content
|
content
|
||||||
.AppendHtml("=\"")
|
.AppendHtml("=\"")
|
||||||
.Append(HtmlEncoder, ViewContext.Writer.Encoding, value)
|
.Append(HtmlEncoder, value)
|
||||||
.AppendHtml("\"");
|
.AppendHtml("\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNet.Mvc.Razor;
|
using Microsoft.AspNet.Mvc.Razor;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">The <see cref="TagHelperContent"/> to write to.</param>
|
/// <param name="content">The <see cref="TagHelperContent"/> to write to.</param>
|
||||||
/// <param name="encoder">The <see cref="HtmlEncoder"/> to use when encoding <paramref name="value"/>.</param>
|
/// <param name="encoder">The <see cref="HtmlEncoder"/> to use when encoding <paramref name="value"/>.</param>
|
||||||
/// <param name="encoding">The character encoding in which the <paramref name="value"/> is written.</param>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
/// <returns><paramref name="content"/> after the write operation has completed.</returns>
|
/// <returns><paramref name="content"/> after the write operation has completed.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
@ -29,11 +28,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
/// For all other types, the encoded result of <see cref="object.ToString"/>
|
/// For all other types, the encoded result of <see cref="object.ToString"/>
|
||||||
/// is written to the <paramref name="content"/>.
|
/// is written to the <paramref name="content"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static TagHelperContent Append(
|
public static TagHelperContent Append(this TagHelperContent content, HtmlEncoder encoder, object value)
|
||||||
this TagHelperContent content,
|
|
||||||
HtmlEncoder encoder,
|
|
||||||
Encoding encoding,
|
|
||||||
object value)
|
|
||||||
{
|
{
|
||||||
if (content == null)
|
if (content == null)
|
||||||
{
|
{
|
||||||
|
|
@ -45,25 +40,34 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
throw new ArgumentNullException(nameof(encoder));
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoding == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(encoding));
|
// No real action but touch content to ensure IsModified is true.
|
||||||
|
content.Append((string)null);
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var writer = new TagHelperContentWrapperTextWriter(encoding, content))
|
string stringValue;
|
||||||
|
var htmlString = value as HtmlString;
|
||||||
|
if (htmlString != null)
|
||||||
|
{
|
||||||
|
// No need for a StringWriter in this case.
|
||||||
|
stringValue = htmlString.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
using (var stringWriter = new StringWriter())
|
using (var stringWriter = new StringWriter())
|
||||||
{
|
{
|
||||||
RazorPage.WriteTo(stringWriter, encoder, value);
|
RazorPage.WriteTo(stringWriter, encoder, value);
|
||||||
|
stringValue = stringWriter.ToString();
|
||||||
// In this case the text likely came directly from the Razor source. Since the original string is
|
|
||||||
// an attribute value that may have been quoted with single quotes, must handle any double quotes
|
|
||||||
// in the value. Writing the value out surrounded by double quotes.
|
|
||||||
var stringValue = stringWriter.ToString().Replace("\"", """);
|
|
||||||
writer.Write(stringValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In this case the text likely came directly from the Razor source. Since the original string is
|
||||||
|
// an attribute value that may have been quoted with single quotes, must handle any double quotes
|
||||||
|
// in the value. Writing the value out surrounded by double quotes.
|
||||||
|
content.AppendHtml(stringValue.Replace("\"", """));
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,4 @@
|
||||||
doc.write('<link rel="stylesheet" href="' + fallbackHref[i] + '"/>');
|
doc.write('<link rel="stylesheet" href="' + fallbackHref[i] + '"/>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})("[[[0]]]", "[[[1]]]", [[[2]]]);
|
})();
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
|
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
|
/// A <see cref="Task"/> that on completion returns a new <see cref="IHtmlContent"/> instance containing
|
||||||
/// the created HTML.
|
/// the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static Task<IHtmlContent> PartialAsync(
|
public static Task<IHtmlContent> PartialAsync(
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
|
/// A <see cref="Task"/> that on completion returns a new <see cref="IHtmlContent"/> instance containing
|
||||||
/// the created HTML.
|
/// the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static Task<IHtmlContent> PartialAsync(
|
public static Task<IHtmlContent> PartialAsync(
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="model">A model to pass into the partial view.</param>
|
/// <param name="model">A model to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
|
/// A <see cref="Task"/> that on completion returns a new <see cref="IHtmlContent"/> instance containing
|
||||||
/// the created HTML.
|
/// the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static Task<IHtmlContent> PartialAsync(
|
public static Task<IHtmlContent> PartialAsync(
|
||||||
|
|
@ -109,7 +109,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
|
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Returns a new <see cref="HtmlString"/> containing the created HTML.
|
/// Returns a new <see cref="IHtmlContent"/> instance containing the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method synchronously calls and blocks on
|
/// This method synchronously calls and blocks on
|
||||||
|
|
@ -141,7 +141,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Returns a new <see cref="HtmlString"/> containing the created HTML.
|
/// Returns a new <see cref="IHtmlContent"/> instance containing the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method synchronously calls and blocks on
|
/// This method synchronously calls and blocks on
|
||||||
|
|
@ -174,7 +174,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="model">A model to pass into the partial view.</param>
|
/// <param name="model">A model to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Returns a new <see cref="HtmlString"/> containing the created HTML.
|
/// Returns a new <see cref="IHtmlContent"/> instance containing the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method synchronously calls and blocks on
|
/// This method synchronously calls and blocks on
|
||||||
|
|
@ -208,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// <param name="model">A model to pass into the partial view.</param>
|
/// <param name="model">A model to pass into the partial view.</param>
|
||||||
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Returns a new <see cref="HtmlString"/> containing the created HTML.
|
/// Returns a new <see cref="IHtmlContent"/> instance containing the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method synchronously calls and blocks on
|
/// This method synchronously calls and blocks on
|
||||||
|
|
|
||||||
|
|
@ -498,7 +498,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// <param name="model">A model to pass into the partial view.</param>
|
/// <param name="model">A model to pass into the partial view.</param>
|
||||||
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
|
/// A <see cref="Task"/> that on completion returns a new <see cref="IHtmlContent"/> instance containing
|
||||||
/// the created HTML.
|
/// the created HTML.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<IHtmlContent> PartialAsync(string partialViewName, object model, ViewDataDictionary viewData);
|
Task<IHtmlContent> PartialAsync(string partialViewName, object model, ViewDataDictionary viewData);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNet.Html;
|
using Microsoft.AspNet.Html;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Rendering
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
|
|
@ -252,6 +251,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
{
|
{
|
||||||
|
var htmlTextWriter = writer as HtmlTextWriter;
|
||||||
|
if (htmlTextWriter != null)
|
||||||
|
{
|
||||||
|
// As a perf optimization, we can buffer this output rather than writing it
|
||||||
|
// out character by character.
|
||||||
|
htmlTextWriter.Write(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (TagRenderMode)
|
switch (TagRenderMode)
|
||||||
{
|
{
|
||||||
case TagRenderMode.StartTag:
|
case TagRenderMode.StartTag:
|
||||||
|
|
|
||||||
|
|
@ -866,18 +866,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
if (tagBuilder != null)
|
if (tagBuilder != null)
|
||||||
{
|
{
|
||||||
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
|
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
|
||||||
|
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
||||||
// As a perf optimization, we can buffer this output rather than writing it
|
|
||||||
// out character by character.
|
|
||||||
var htmlWriter = ViewContext.Writer as HtmlTextWriter;
|
|
||||||
if (htmlWriter == null)
|
|
||||||
{
|
|
||||||
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
htmlWriter.Write(tagBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateForm();
|
return CreateForm();
|
||||||
|
|
@ -920,18 +909,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
if (tagBuilder != null)
|
if (tagBuilder != null)
|
||||||
{
|
{
|
||||||
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
|
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
|
||||||
|
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
||||||
// As a perf optimization, we can buffer this output rather than writing it
|
|
||||||
// out character by character.
|
|
||||||
var htmlWriter = ViewContext.Writer as HtmlTextWriter;
|
|
||||||
if (htmlWriter == null)
|
|
||||||
{
|
|
||||||
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
htmlWriter.Write(tagBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateForm();
|
return CreateForm();
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,17 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(encoder));
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder.Encode(writer, _input);
|
var htmlTextWriter = writer as HtmlTextWriter;
|
||||||
|
if (htmlTextWriter != null)
|
||||||
|
{
|
||||||
|
// As a perf optimization, we can buffer this output rather than writing it
|
||||||
|
// out character by character.
|
||||||
|
htmlTextWriter.Write(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encoder.Encode(writer, _input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DebuggerToString()
|
private string DebuggerToString()
|
||||||
|
|
|
||||||
|
|
@ -961,13 +961,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
// Assert
|
// Assert
|
||||||
var htmlAttribute = Assert.Single(executionContext.HTMLAttributes);
|
var htmlAttribute = Assert.Single(executionContext.HTMLAttributes);
|
||||||
Assert.Equal("someattr", htmlAttribute.Name, StringComparer.Ordinal);
|
Assert.Equal("someattr", htmlAttribute.Name, StringComparer.Ordinal);
|
||||||
Assert.IsType<HtmlString>(htmlAttribute.Value);
|
var htmlContent = Assert.IsAssignableFrom<IHtmlContent>(htmlAttribute.Value);
|
||||||
Assert.Equal(expectedValue, HtmlContentUtilities.HtmlContentToString((IHtmlContent)htmlAttribute.Value));
|
Assert.Equal(expectedValue, HtmlContentUtilities.HtmlContentToString(htmlContent), StringComparer.Ordinal);
|
||||||
Assert.False(htmlAttribute.Minimized);
|
Assert.False(htmlAttribute.Minimized);
|
||||||
|
|
||||||
var allAttribute = Assert.Single(executionContext.AllAttributes);
|
var allAttribute = Assert.Single(executionContext.AllAttributes);
|
||||||
Assert.Equal("someattr", allAttribute.Name, StringComparer.Ordinal);
|
Assert.Equal("someattr", allAttribute.Name, StringComparer.Ordinal);
|
||||||
Assert.IsType<HtmlString>(allAttribute.Value);
|
htmlContent = Assert.IsAssignableFrom<IHtmlContent>(allAttribute.Value);
|
||||||
Assert.Equal(expectedValue, allAttribute.Value.ToString(), StringComparer.Ordinal);
|
Assert.Equal(expectedValue, HtmlContentUtilities.HtmlContentToString(htmlContent), StringComparer.Ordinal);
|
||||||
Assert.False(allAttribute.Minimized);
|
Assert.False(allAttribute.Minimized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Html;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
using Microsoft.AspNet.Mvc.TestCommon;
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
using Microsoft.Extensions.WebEncoders.Testing;
|
using Microsoft.Extensions.WebEncoders.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -22,22 +24,13 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// url, expectedHref
|
// url, expectedHref
|
||||||
return new TheoryData<object, object>
|
return new TheoryData<string, string>
|
||||||
{
|
{
|
||||||
{ "~/home/index.html", "/approot/home/index.html" },
|
{ "~/home/index.html", "/approot/home/index.html" },
|
||||||
{ " ~/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"),
|
"~/home/index.html ~/secondValue/index.html",
|
||||||
new HtmlString("HtmlEncode[[/approot/]]home/index.html")
|
"/approot/home/index.html ~/secondValue/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")
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ResolvableUrlData))]
|
[MemberData(nameof(ResolvableUrlData))]
|
||||||
public void Process_ResolvesTildeSlashValues(object url, object expectedHref)
|
public void Process_ResolvesTildeSlashValues(string url, string expectedHref)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var tagHelperOutput = new TagHelperOutput(
|
var tagHelperOutput = new TagHelperOutput(
|
||||||
|
|
@ -77,8 +70,64 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
// Assert
|
// Assert
|
||||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||||
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||||
Assert.IsType(expectedHref.GetType(), url);
|
var attributeValue = Assert.IsType<string>(attribute.Value);
|
||||||
Assert.Equal(expectedHref.ToString(), attribute.Value.ToString());
|
Assert.Equal(expectedHref, attributeValue, StringComparer.Ordinal);
|
||||||
|
Assert.False(attribute.Minimized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData ResolvableUrlHtmlStringData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// url, expectedHref
|
||||||
|
return new TheoryData<HtmlString, string>
|
||||||
|
{
|
||||||
|
{ new HtmlString("~/home/index.html"), "HtmlEncode[[/approot/]]home/index.html" },
|
||||||
|
{ new HtmlString(" ~/home/index.html"), "HtmlEncode[[/approot/]]home/index.html" },
|
||||||
|
{
|
||||||
|
new HtmlString("~/home/index.html ~/secondValue/index.html"),
|
||||||
|
"HtmlEncode[[/approot/]]home/index.html ~/secondValue/index.html"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ResolvableUrlHtmlStringData))]
|
||||||
|
public void Process_ResolvesTildeSlashValues_InHtmlString(object url, string expectedHref)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tagHelperOutput = new TagHelperOutput(
|
||||||
|
tagName: "a",
|
||||||
|
attributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "href", url }
|
||||||
|
},
|
||||||
|
getChildContentAsync: _ => Task.FromResult<TagHelperContent>(null));
|
||||||
|
var urlHelperMock = new Mock<IUrlHelper>();
|
||||||
|
urlHelperMock
|
||||||
|
.Setup(urlHelper => urlHelper.Content(It.IsAny<string>()))
|
||||||
|
.Returns(new Func<string, string>(value => "/approot" + value.Substring(1)));
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
||||||
|
.Returns(urlHelperMock.Object);
|
||||||
|
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, new HtmlTestEncoder());
|
||||||
|
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||||
|
Enumerable.Empty<IReadOnlyTagHelperAttribute>()),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tagHelper.Process(context, tagHelperOutput);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||||
|
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||||
|
var htmlContent = Assert.IsAssignableFrom<IHtmlContent>(attribute.Value);
|
||||||
|
Assert.Equal(expectedHref, HtmlContentUtilities.HtmlContentToString(htmlContent), StringComparer.Ordinal);
|
||||||
Assert.False(attribute.Minimized);
|
Assert.False(attribute.Minimized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,25 +136,20 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// url
|
// url
|
||||||
return new TheoryData<object>
|
return new TheoryData<string>
|
||||||
{
|
{
|
||||||
{ "/home/index.html" },
|
{ "/home/index.html" },
|
||||||
{ "~ /home/index.html" },
|
{ "~ /home/index.html" },
|
||||||
{ "/home/index.html ~/second/wontresolve.html" },
|
{ "/home/index.html ~/second/wontresolve.html" },
|
||||||
{ " ~\\home\\index.html" },
|
{ " ~\\home\\index.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]
|
[Theory]
|
||||||
[MemberData(nameof(UnresolvableUrlData))]
|
[MemberData(nameof(UnresolvableUrlData))]
|
||||||
public void Process_DoesNotResolveNonTildeSlashValues(object url)
|
public void Process_DoesNotResolveNonTildeSlashValues(string url)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var tagHelperOutput = new TagHelperOutput(
|
var tagHelperOutput = new TagHelperOutput(
|
||||||
|
|
@ -123,7 +167,7 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
urlHelperFactory
|
urlHelperFactory
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
||||||
.Returns(urlHelperMock.Object);
|
.Returns(urlHelperMock.Object);
|
||||||
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, htmlEncoder: null);
|
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, new HtmlTestEncoder());
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
var context = new TagHelperContext(
|
||||||
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||||
|
|
@ -137,7 +181,63 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
// Assert
|
// Assert
|
||||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||||
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||||
Assert.Equal(url, attribute.Value);
|
var attributeValue = Assert.IsType<string>(attribute.Value);
|
||||||
|
Assert.Equal(url, attributeValue, StringComparer.Ordinal);
|
||||||
|
Assert.False(attribute.Minimized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData UnresolvableUrlHtmlStringData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// url
|
||||||
|
return new TheoryData<HtmlString>
|
||||||
|
{
|
||||||
|
{ 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(UnresolvableUrlHtmlStringData))]
|
||||||
|
public void Process_DoesNotResolveNonTildeSlashValues_InHtmlString(HtmlString url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tagHelperOutput = new TagHelperOutput(
|
||||||
|
tagName: "a",
|
||||||
|
attributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "href", url }
|
||||||
|
},
|
||||||
|
getChildContentAsync: _ => Task.FromResult<TagHelperContent>(null));
|
||||||
|
var urlHelperMock = new Mock<IUrlHelper>();
|
||||||
|
urlHelperMock
|
||||||
|
.Setup(urlHelper => urlHelper.Content(It.IsAny<string>()))
|
||||||
|
.Returns("approot/home/index.html");
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
||||||
|
.Returns(urlHelperMock.Object);
|
||||||
|
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, new HtmlTestEncoder());
|
||||||
|
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||||
|
Enumerable.Empty<IReadOnlyTagHelperAttribute>()),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tagHelper.Process(context, tagHelperOutput);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||||
|
Assert.Equal("href", attribute.Name, StringComparer.Ordinal);
|
||||||
|
var attributeValue = Assert.IsType<HtmlString>(attribute.Value);
|
||||||
|
Assert.Equal(url.ToString(), attributeValue.ToString(), StringComparer.Ordinal);
|
||||||
Assert.False(attribute.Minimized);
|
Assert.False(attribute.Minimized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +297,7 @@ namespace Microsoft.AspNet.Mvc.Razor.TagHelpers
|
||||||
urlHelperFactory
|
urlHelperFactory
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
||||||
.Returns(urlHelperMock.Object);
|
.Returns(urlHelperMock.Object);
|
||||||
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, htmlEncoder: null);
|
var tagHelper = new UrlResolutionTagHelper(urlHelperFactory.Object, new HtmlTestEncoder());
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
var context = new TagHelperContext(
|
||||||
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
allAttributes: new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var resource = "window.alert('An alert');";
|
var resource = "window.alert('An alert');";
|
||||||
|
var expected = resource.Substring(0, resource.Length - 2);
|
||||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
||||||
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
||||||
var cache = new ConcurrentDictionary<string, string>();
|
var cache = new ConcurrentDictionary<string, string>();
|
||||||
|
|
@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
var result = JavaScriptResources.GetEmbeddedJavaScript("test.js", getManifestResourceStream, cache);
|
var result = JavaScriptResources.GetEmbeddedJavaScript("test.js", getManifestResourceStream, cache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(resource, result);
|
Assert.Equal(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var resource = "window.alert('An alert');";
|
var resource = "window.alert('An alert');";
|
||||||
|
var expected = resource.Substring(0, resource.Length - 2);
|
||||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
||||||
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
||||||
var cache = new ConcurrentDictionary<string, string>();
|
var cache = new ConcurrentDictionary<string, string>();
|
||||||
|
|
@ -43,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
Assert.Collection(cache, kvp =>
|
Assert.Collection(cache, kvp =>
|
||||||
{
|
{
|
||||||
Assert.Equal("test.js", kvp.Key);
|
Assert.Equal("test.js", kvp.Key);
|
||||||
Assert.Equal(resource, kvp.Value);
|
Assert.Equal(expected, kvp.Value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,12 +72,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("window.alert(\"[[[0]]]\")", "window.alert(\"{0}\")")]
|
[InlineData("window.alert(\"[[[0]]]\")")]
|
||||||
[InlineData("var test = { a: 1 };", "var test = {{ a: 1 }};")]
|
[InlineData("var test = { a: 1 };")]
|
||||||
[InlineData("var test = { a: 1, b: \"[[[0]]]\" };", "var test = {{ a: 1, b: \"{0}\" }};")]
|
[InlineData("var test = { a: 1, b: \"[[[0]]]\" };")]
|
||||||
public void GetEmbeddedJavaScript_PreparesJavaScriptCorrectly(string resource, string expectedResult)
|
public void GetEmbeddedJavaScript_PreparesJavaScriptCorrectly(string resource)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = resource.Substring(0, resource.Length - 2);
|
||||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
var stream = new MemoryStream(Encoding.UTF8.GetBytes(resource));
|
||||||
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
var getManifestResourceStream = new Func<string, Stream>(name => stream);
|
||||||
var cache = new ConcurrentDictionary<string, string>();
|
var cache = new ConcurrentDictionary<string, string>();
|
||||||
|
|
@ -84,7 +87,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
var result = JavaScriptResources.GetEmbeddedJavaScript("test.js", getManifestResourceStream, cache);
|
var result = JavaScriptResources.GetEmbeddedJavaScript("test.js", getManifestResourceStream, cache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedResult, result);
|
Assert.Equal(expected, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue