parent
49294b7d28
commit
3d33418f31
|
|
@ -29,4 +29,7 @@ nuget.exe
|
|||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
*.sln.ide
|
||||
.settings
|
||||
*.sln.ide
|
||||
node_modules
|
||||
**/[Cc]ompiler/[Rr]esources/**/*.js
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22522.0
|
||||
VisualStudioVersion = 14.0.22526.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
|
|
|||
|
|
@ -1,26 +1,39 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Builder;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Console;
|
||||
using TagHelperSample.Web.Services;
|
||||
|
||||
namespace TagHelperSample.Web
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc();
|
||||
|
||||
|
||||
// Setup services with a test AssemblyProvider so that only the sample's assemblies are loaded. This
|
||||
// prevents loading controllers from other assemblies when the sample is used in functional tests.
|
||||
services.AddTransient<IAssemblyProvider, TestAssemblyProvider<Startup>>();
|
||||
services.AddSingleton<MoviesService>();
|
||||
});
|
||||
|
||||
loggerFactory.AddConsole((name, logLevel) =>
|
||||
name.StartsWith("Microsoft.AspNet.Mvc.TagHelpers", StringComparison.OrdinalIgnoreCase)
|
||||
|| (name.StartsWith("Microsoft.Net.Http.Server.WebListener", StringComparison.OrdinalIgnoreCase)
|
||||
&& logLevel >= LogLevel.Information));
|
||||
|
||||
app.UseErrorPage();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="~/blank.css" rel="stylesheet" />
|
||||
<link href="~/site.min.css" rel="stylesheet"
|
||||
asp-fallback-href="~/site.css"
|
||||
asp-fallback-test-class="fallback-test"
|
||||
asp-fallback-test-property="visibility"
|
||||
asp-fallback-test-value="hidden" />
|
||||
</head>
|
||||
<body>
|
||||
@RenderBody()
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +1,5 @@
|
|||
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
|
||||
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Console": "1.0.0-*",
|
||||
"Microsoft.AspNet.Diagnostics": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
meta.fallback-test {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
body::after {
|
||||
display: block;
|
||||
color: #0fa912;
|
||||
font-size: 0.9em;
|
||||
margin-top: 2.4em;
|
||||
content: "Stylesheet 'site.css' loaded successfully!";
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.initConfig({
|
||||
jshint: {
|
||||
scripts: [ "js/**/*.js" ]
|
||||
},
|
||||
uglify: {
|
||||
scripts: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: "js",
|
||||
src: "**/*.js",
|
||||
dest: "Compiler/Resources"
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks("grunt-contrib-jshint");
|
||||
grunt.loadNpmTasks("grunt-contrib-uglify");
|
||||
|
||||
grunt.registerTask("default", [ "jshint", "uglify" ]);
|
||||
};
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for dealing with JavaScript.
|
||||
/// </summary>
|
||||
public static class JavaScriptUtility
|
||||
{
|
||||
private static readonly Assembly ResourcesAssembly = typeof(JavaScriptUtility).GetTypeInfo().Assembly;
|
||||
|
||||
private static readonly ConcurrentDictionary<string, string> Cache =
|
||||
new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
private static readonly IDictionary<char, string> EncodingMap = new Dictionary<char, string>
|
||||
{
|
||||
{ '<', @"\u003c" }, // opening angle-bracket
|
||||
{ '>', @"\u003e" }, // closing angle-bracket
|
||||
{ '\'', @"\u0027" }, // single quote
|
||||
{ '"', @"\u0022" }, // double quote
|
||||
{ '\\', @"\\" }, // back slash
|
||||
{ '\r', "\\r" }, // carriage return
|
||||
{ '\n', "\\n" }, // new line
|
||||
{ '\u0085', @"\u0085" }, // next line
|
||||
{ '&', @"\u0026" }, // ampersand
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets an embedded JavaScript file resource and decodes it for use as a .NET format string.
|
||||
/// </summary>
|
||||
public static string GetEmbeddedJavaScript(string resourceName)
|
||||
{
|
||||
return Cache.GetOrAdd(resourceName, key =>
|
||||
{
|
||||
// Load the JavaScript from embedded resource
|
||||
using (var resourceStream = ResourcesAssembly.GetManifestResourceStream(key))
|
||||
{
|
||||
Debug.Assert(resourceStream != null, "Embedded resource missing. Ensure 'prebuild' script has run.");
|
||||
|
||||
using (var streamReader = new StreamReader(resourceStream))
|
||||
{
|
||||
var script = streamReader.ReadToEnd();
|
||||
|
||||
// Replace unescaped/escaped chars with their equivalent
|
||||
return PrepareFormatString(script);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Internal so we can test this separately
|
||||
internal static string PrepareFormatString(string input)
|
||||
{
|
||||
return input.Replace("{", "{{")
|
||||
.Replace("}", "}}")
|
||||
.Replace("[[[", "{")
|
||||
.Replace("]]]", "}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a .NET string for safe use as a JavaScript string literal, including inline in an HTML file.
|
||||
/// </summary>
|
||||
internal static string JavaScriptStringEncode(string value)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
|
||||
foreach (var character in value)
|
||||
{
|
||||
if (CharRequiresJavaScriptEncoding(character))
|
||||
{
|
||||
EncodeAndAppendChar(result, character);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(character);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static bool CharRequiresJavaScriptEncoding(char character)
|
||||
{
|
||||
return character < 0x20 // Control chars
|
||||
|| EncodingMap.ContainsKey(character);
|
||||
}
|
||||
|
||||
private static void EncodeAndAppendChar(StringBuilder builder, char character)
|
||||
{
|
||||
string mapped;
|
||||
|
||||
if (!EncodingMap.TryGetValue(character, out mapped))
|
||||
{
|
||||
mapped = "\\u" + ((int)character).ToString("x4", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
builder.Append(mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <link> elements that supports fallback href paths.
|
||||
/// </summary>
|
||||
public class LinkTagHelper : TagHelper
|
||||
{
|
||||
private const string FallbackHrefAttributeName = "asp-fallback-href";
|
||||
private const string FallbackTestClassAttributeName = "asp-fallback-test-class";
|
||||
private const string FallbackTestPropertyAttributeName = "asp-fallback-test-property";
|
||||
private const string FallbackTestValueAttributeName = "asp-fallback-test-value";
|
||||
private const string FallbackTestMetaTemplate = "<meta name=\"x-stylesheet-fallback-test\" class=\"{0}\" />";
|
||||
private const string FallbackJavaScriptResourceName = "compiler/resources/LinkTagHelper_FallbackJavaScript.js";
|
||||
|
||||
// NOTE: All attributes are required for the LinkTagHelper to process.
|
||||
private static readonly string[] RequiredAttributes = new[]
|
||||
{
|
||||
FallbackHrefAttributeName,
|
||||
FallbackTestClassAttributeName,
|
||||
FallbackTestPropertyAttributeName,
|
||||
FallbackTestValueAttributeName
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The URL of a CSS stylesheet to fallback to in the case the primary one fails (as specified in the href
|
||||
/// attribute).
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FallbackHrefAttributeName)]
|
||||
public string FallbackHref { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The class name defined in the stylesheet to use for the fallback test.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FallbackTestClassAttributeName)]
|
||||
public string FallbackTestClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CSS property name to use for the fallback test.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FallbackTestPropertyAttributeName)]
|
||||
public string FallbackTestProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CSS property value to use for the fallback test.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FallbackTestValueAttributeName)]
|
||||
public string FallbackTestValue { get; set; }
|
||||
|
||||
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
|
||||
[Activate]
|
||||
protected internal ILoggerFactory LoggerFactory { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
var logger = LoggerFactory.Create<LinkTagHelper>();
|
||||
|
||||
if (!context.AllRequiredAttributesArePresent(RequiredAttributes, logger))
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Verbose))
|
||||
{
|
||||
logger.WriteVerbose("Skipping processing for {0} {1}", nameof(LinkTagHelper), context.UniqueId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var content = new StringBuilder();
|
||||
|
||||
// NOTE: Values in TagHelperOutput.Attributes are already HtmlEncoded
|
||||
|
||||
// We've taken over rendering here so prevent the element rendering the outer tag
|
||||
output.TagName = null;
|
||||
|
||||
// Rebuild the <link /> tag that loads the primary stylesheet
|
||||
content.Append("<link ");
|
||||
foreach (var attribute in output.Attributes)
|
||||
{
|
||||
content.AppendFormat(CultureInfo.InvariantCulture, "{0}=\"{1}\" ", attribute.Key, attribute.Value);
|
||||
}
|
||||
content.AppendLine("/>");
|
||||
|
||||
// Build the <meta /> tag that's used to test for the presence of the stylesheet
|
||||
content.AppendLine(string.Format(CultureInfo.InvariantCulture, FallbackTestMetaTemplate, FallbackTestClass));
|
||||
|
||||
// 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,
|
||||
// indicating that the primary stylesheet failed to load.
|
||||
content.Append("<script>");
|
||||
content.AppendFormat(CultureInfo.InvariantCulture,
|
||||
JavaScriptUtility.GetEmbeddedJavaScript(FallbackJavaScriptResourceName),
|
||||
JavaScriptUtility.JavaScriptStringEncode(FallbackTestProperty),
|
||||
JavaScriptUtility.JavaScriptStringEncode(FallbackTestValue),
|
||||
JavaScriptUtility.JavaScriptStringEncode(FallbackHref));
|
||||
content.Append("</script>");
|
||||
|
||||
output.Content = content.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ILoggerStructure"/> for log messages regarding <see cref="ITagHelper"/> instances that opt out of
|
||||
/// processing due to missing required attributes.
|
||||
/// </summary>
|
||||
public class MissingAttributeLoggerStructure : ILoggerStructure
|
||||
{
|
||||
private readonly string _uniqueId;
|
||||
private readonly IEnumerable<string> _missingAttributes;
|
||||
private readonly IEnumerable<KeyValuePair<string, object>> _values;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MissingAttributeLoggerStructure"/>.
|
||||
/// </summary>
|
||||
/// <param name="uniqueId">The unique ID of the HTML element this message applies to.</param>
|
||||
/// <param name="missingAttributes">The missing required attributes.</param>
|
||||
public MissingAttributeLoggerStructure(string uniqueId, IEnumerable<string> missingAttributes)
|
||||
{
|
||||
_uniqueId = uniqueId;
|
||||
_missingAttributes = missingAttributes;
|
||||
_values = new Dictionary<string, object>
|
||||
{
|
||||
{ "UniqueId", _uniqueId },
|
||||
{ "MissingAttributes", _missingAttributes }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The log message.
|
||||
/// </summary>
|
||||
public string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Tag Helper skipped due to missing required attributes.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values associated with this structured log message.
|
||||
/// </summary>
|
||||
/// <returns>The values.</returns>
|
||||
public IEnumerable<KeyValuePair<string, object>> GetValues()
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a human readable string for this structured log message.
|
||||
/// </summary>
|
||||
/// <returns>The message.</returns>
|
||||
public string Format()
|
||||
{
|
||||
return string.Format("Tag Helper unique ID: {0}, Missing attributes: {1}",
|
||||
_uniqueId,
|
||||
string.Join(",", _missingAttributes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility related extensions for <see cref="TagHelperContext"/>.
|
||||
/// </summary>
|
||||
public static class TagHelperContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="ITagHelper" />'s required attributes are present, non null, non empty, and
|
||||
/// non whitepsace.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="TagHelperContext"/>.</param>
|
||||
/// <param name="requiredAttributes">The attributes the <see cref="ITagHelper" /> requires in order to run.</param>
|
||||
/// <param name="logger">An optional <see cref="ILogger"/> to log warning details to.</param>
|
||||
/// <returns>A <see cref="bool"/> indicating whether the <see cref="ITagHelper" /> should run.</returns>
|
||||
public static bool AllRequiredAttributesArePresent(
|
||||
[NotNull]this TagHelperContext context,
|
||||
[NotNull]IEnumerable<string> requiredAttributes,
|
||||
ILogger logger = null)
|
||||
{
|
||||
// Check for all attribute values & log a warning if any required are missing
|
||||
var atLeastOnePresent = false;
|
||||
var missingAttrNames = new List<string>();
|
||||
|
||||
foreach (var attr in requiredAttributes)
|
||||
{
|
||||
if (!context.AllAttributes.ContainsKey(attr)
|
||||
|| context.AllAttributes[attr] == null
|
||||
|| string.IsNullOrWhiteSpace(context.AllAttributes[attr] as string))
|
||||
{
|
||||
// Missing attribute!
|
||||
missingAttrNames.Add(attr);
|
||||
}
|
||||
else
|
||||
{
|
||||
atLeastOnePresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingAttrNames.Any())
|
||||
{
|
||||
if (atLeastOnePresent && logger != null && logger.IsEnabled(LogLevel.Warning))
|
||||
{
|
||||
// At least 1 attribute was present indicating the user intended to use the tag helper,
|
||||
// but at least 1 was missing too, so log a warning with the details.
|
||||
logger.WriteWarning(new MissingAttributeLoggerStructure(context.UniqueId, missingAttrNames));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// All required attributes present
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
(function (cssTestPropertyName, cssTestPropertyValue, fallbackHref) {
|
||||
// This function finds the previous element (assumed to be meta) and tests its current CSS style using the passed
|
||||
// values, to determine if a stylesheet was loaded. If not, this function loads the fallback stylesheet via
|
||||
// document.write.
|
||||
var doc = document,
|
||||
scriptElements = doc.getElementsByTagName("SCRIPT"),
|
||||
meta = scriptElements[scriptElements.length - 1].previousElementSibling;
|
||||
|
||||
if (doc.defaultView.getComputedStyle(meta)[cssTestPropertyName] !== cssTestPropertyValue) {
|
||||
doc.write('<link rel="stylesheet" href="' + fallbackHref + '"/>');
|
||||
}
|
||||
})("[[[0]]]", "[[[1]]]", "[[[2]]]");
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"name": "Microsoft.AspNet.Mvc.TagHelpers",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-contrib-jshint": "~0.11.0",
|
||||
"grunt-contrib-uglify": "~0.7.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
{
|
||||
"description": "Contains a default set of Tag Helpers for building ASP.NET MVC applications.",
|
||||
"version": "6.0.0-*",
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
|
||||
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {},
|
||||
"aspnetcore50": {}
|
||||
}
|
||||
"description": "Contains a default set of Tag Helpers for building ASP.NET MVC applications.",
|
||||
"version": "6.0.0-*",
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
|
||||
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Interfaces": { "version": "1.0.0-*", "type": "build" },
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": [ "npm install", "grunt" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Link</title>
|
||||
<link href="/blank.css" rel="stylesheet"></link>
|
||||
<link rel="stylesheet" href="/link-test.min.css" data-extra="test" />
|
||||
<meta name="x-stylesheet-fallback-test" class="hidden" />
|
||||
<script>!function(a,b,c){var d=document,e=d.getElementsByTagName("SCRIPT"),f=e[e.length-1].previousElementSibling;d.defaultView.getComputedStyle(f)[a]!==b&&d.write('<link rel="stylesheet" href="'+c+'"/>')}("visibility","hidden","/link-test.css");</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Link Tag Helper Test</h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -39,6 +39,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
[InlineData("EditWarehouse", null)]
|
||||
// Testing the EnvironmentTagHelper
|
||||
[InlineData("Environment", null)]
|
||||
// Testing the LinkTagHelper
|
||||
[InlineData("Link", null)]
|
||||
public async Task MvcTagHelpers_GeneratesExpectedResults(string action, string antiForgeryPath)
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Net;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
|
|
@ -31,10 +32,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
"/Home/Index",
|
||||
};
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory = new TestLoggerFactory();
|
||||
// Path relative to Mvc\\test\Microsoft.AspNet.Mvc.FunctionalTests
|
||||
private readonly IServiceProvider _services =
|
||||
TestHelper.CreateServices("TagHelperSample.Web", Path.Combine("..", "..", "samples"));
|
||||
private readonly Action<IApplicationBuilder> _app = new TagHelperSample.Web.Startup().Configure;
|
||||
private readonly Action<IApplicationBuilder, ILoggerFactory> _app = new TagHelperSample.Web.Startup().Configure;
|
||||
|
||||
[Fact]
|
||||
public async Task Home_Pages_ReturnSuccess()
|
||||
|
|
@ -42,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
using (TestHelper.ReplaceCallContextServiceLocationService(_services))
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var server = TestServer.Create(_services, app => _app(app, _loggerFactory));
|
||||
var client = server.CreateClient();
|
||||
|
||||
for (var index = 0; index < Paths.Count; index++)
|
||||
|
|
@ -57,5 +59,44 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLoggerFactory : ILoggerFactory
|
||||
{
|
||||
public void AddProvider(ILoggerProvider provider)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ILogger Create(string name)
|
||||
{
|
||||
return new TestLogger();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLogger : ILogger
|
||||
{
|
||||
public bool IsEnabled(LogLevel level)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope(object scope)
|
||||
{
|
||||
return new TestDisposable();
|
||||
}
|
||||
|
||||
public void Write(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDisposable : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Test
|
||||
{
|
||||
public class JavaScriptUtilityTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Hello World", "Hello World")]
|
||||
[InlineData("Hello & World", "Hello \\u0026 World")]
|
||||
[InlineData("Hello \r World", "Hello \\r World")]
|
||||
[InlineData("Hello \n World", "Hello \\n World")]
|
||||
[InlineData("Hello < World", "Hello \\u003c World")]
|
||||
[InlineData("Hello > World", "Hello \\u003e World")]
|
||||
[InlineData("Hello ' World", "Hello \\u0027 World")]
|
||||
[InlineData("Hello \" World", "Hello \\u0022 World")]
|
||||
[InlineData("Hello \\ World", "Hello \\\\ World")]
|
||||
[InlineData("Hello \u0005 \u001f World", "Hello \\u0005 \\u001f World")]
|
||||
[InlineData("Hello \r\n <ah /> 'eep' & \"hey\" World", "Hello \\r\\n \\u003cah /\\u003e \\u0027eep\\u0027 \\u0026 \\u0022hey\\u0022 World")]
|
||||
public void JavaScriptEncode_EncodesCorrectly(string input, string expectedOutput)
|
||||
{
|
||||
// Act
|
||||
var result = JavaScriptUtility.JavaScriptStringEncode(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedOutput, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("window.alert(\"[[[0]]]\")", "window.alert(\"{0}\")")]
|
||||
[InlineData("var test = { a: 1 };", "var test = {{ a: 1 }};")]
|
||||
[InlineData("var test = { a: 1, b: \"[[[0]]]\" };", "var test = {{ a: 1, b: \"{0}\" }};")]
|
||||
public void PrepareFormatString_PreparesJavaScriptCorrectly(string input, string expectedOutput)
|
||||
{
|
||||
// Act
|
||||
var result = JavaScriptUtility.PrepareFormatString(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedOutput, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Test
|
||||
{
|
||||
public class LinkTagHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public void RunsWhenRequiredAttributesArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new Dictionary<string, object>
|
||||
{
|
||||
{ "asp-fallback-href", "test.css" },
|
||||
{ "asp-fallback-test-class", "hidden" },
|
||||
{ "asp-fallback-test-property", "visible" },
|
||||
{ "asp-fallback-test-value", "hidden" },
|
||||
});
|
||||
var output = MakeTagHelperOutput("link");
|
||||
var loggerFactory = new Mock<ILoggerFactory>();
|
||||
|
||||
// Act
|
||||
var helper = new LinkTagHelper
|
||||
{
|
||||
LoggerFactory = loggerFactory.Object,
|
||||
FallbackHref = "test.css",
|
||||
FallbackTestClass = "hidden",
|
||||
FallbackTestProperty = "visible",
|
||||
FallbackTestValue = "hidden"
|
||||
};
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Null(output.TagName);
|
||||
Assert.NotNull(output.Content);
|
||||
Assert.True(output.ContentSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreservesOrderOfSourceAttributesWhenRun()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new Dictionary<string, object>
|
||||
{
|
||||
{ "rel", "stylesheet"},
|
||||
{ "data-extra", "something"},
|
||||
{ "href", "test.css"},
|
||||
{ "asp-fallback-href", "test.css" },
|
||||
{ "asp-fallback-test-class", "hidden" },
|
||||
{ "asp-fallback-test-property", "visible" },
|
||||
{ "asp-fallback-test-value", "hidden" }
|
||||
});
|
||||
var output = MakeTagHelperOutput("link",
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "rel", "stylesheet"},
|
||||
{ "data-extra", "something"},
|
||||
{ "href", "test.css"}
|
||||
});
|
||||
var loggerFactory = new Mock<ILoggerFactory>();
|
||||
|
||||
// Act
|
||||
var helper = new LinkTagHelper
|
||||
{
|
||||
LoggerFactory = loggerFactory.Object,
|
||||
FallbackHref = "test.css",
|
||||
FallbackTestClass = "hidden",
|
||||
FallbackTestProperty = "visible",
|
||||
FallbackTestValue = "hidden"
|
||||
};
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.StartsWith("<link rel=\"stylesheet\" data-extra=\"something\" href=\"test.css\"", output.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotRunWhenARequiredAttributeIsMissing()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new Dictionary<string, object>
|
||||
{
|
||||
// This is commented out on purpose: { "asp-fallback-href", "test.css" },
|
||||
{ "asp-fallback-test-class", "hidden" },
|
||||
{ "asp-fallback-test-property", "visible" },
|
||||
{ "asp-fallback-test-value", "hidden" },
|
||||
});
|
||||
var output = MakeTagHelperOutput("link");
|
||||
var logger = new Mock<ILogger>();
|
||||
var loggerFactory = new Mock<ILoggerFactory>();
|
||||
loggerFactory.Setup(f => f.Create(It.IsAny<string>())).Returns(logger.Object);
|
||||
|
||||
// Act
|
||||
var helper = new LinkTagHelper
|
||||
{
|
||||
LoggerFactory = loggerFactory.Object,
|
||||
// This is commented out on purpose: FallbackHref = "test.css",
|
||||
FallbackTestClass = "hidden",
|
||||
FallbackTestProperty = "visible",
|
||||
FallbackTestValue = "hidden"
|
||||
};
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(output.TagName);
|
||||
Assert.False(output.ContentSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotRunWhenAllRequiredAttributesAreMissing()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext();
|
||||
var output = MakeTagHelperOutput("link");
|
||||
var logger = new Mock<ILogger>();
|
||||
var loggerFactory = new Mock<ILoggerFactory>();
|
||||
loggerFactory.Setup(f => f.Create(It.IsAny<string>())).Returns(logger.Object);
|
||||
|
||||
// Act
|
||||
var helper = new LinkTagHelper
|
||||
{
|
||||
LoggerFactory = loggerFactory.Object
|
||||
};
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(output.TagName);
|
||||
Assert.False(output.ContentSet);
|
||||
}
|
||||
|
||||
private TagHelperContext MakeTagHelperContext(
|
||||
IDictionary<string, object> attributes = null,
|
||||
string content = null)
|
||||
{
|
||||
attributes = attributes ?? new Dictionary<string, object>();
|
||||
|
||||
return new TagHelperContext(attributes, Guid.NewGuid().ToString("N"), () => Task.FromResult(content));
|
||||
}
|
||||
|
||||
private TagHelperOutput MakeTagHelperOutput(string tagName, IDictionary<string, string> attributes = null)
|
||||
{
|
||||
attributes = attributes ?? new Dictionary<string, string>();
|
||||
|
||||
return new TagHelperOutput(tagName, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Interfaces": { "version": "1.0.0-*", "type": "build" },
|
||||
"xunit.runner.kre": "1.0.0-*"
|
||||
},
|
||||
"commands": {
|
||||
|
|
|
|||
|
|
@ -155,5 +155,10 @@ namespace MvcTagHelpersWebSite.Controllers
|
|||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Link()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Link</title>
|
||||
<link href="~/blank.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="~/link-test.min.css" data-extra="test"
|
||||
asp-fallback-href="~/link-test.css"
|
||||
asp-fallback-test-class="hidden"
|
||||
asp-fallback-test-property="visibility"
|
||||
asp-fallback-test-value="hidden" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Link Tag Helper Test</h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue