Add Visual Studio specific RC1 binaries.
- This is needed for Visual Studio RC1 backwards compatibility.
This commit is contained in:
parent
4212b7e713
commit
197d6a579f
|
|
@ -5,7 +5,9 @@
|
||||||
],
|
],
|
||||||
"packages": {
|
"packages": {
|
||||||
"Microsoft.AspNetCore.Razor": { },
|
"Microsoft.AspNetCore.Razor": { },
|
||||||
"Microsoft.AspNetCore.Razor.Runtime": { }
|
"Microsoft.AspNet.Razor.VSRC1": { },
|
||||||
|
"Microsoft.AspNetCore.Razor.Runtime": { },
|
||||||
|
"Microsoft.AspNet.Razor.Runtime.VSRC1": { }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"adx-nonshipping": { // Packages written by the ADX team but that don't ship on NuGet.org (thus, no need to scan anything in them)
|
"adx-nonshipping": { // Packages written by the ADX team but that don't ship on NuGet.org (thus, no need to scan anything in them)
|
||||||
|
|
|
||||||
17
Razor.sln
17
Razor.sln
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.24720.0
|
VisualStudioVersion = 14.0.25123.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C0D6505-79B3-49D0-B4C3-176F0F1836ED}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C0D6505-79B3-49D0-B4C3-176F0F1836ED}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -17,6 +16,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.Test.Sources", "src\Microsoft.AspNetCore.Razor.Test.Sources\Microsoft.AspNetCore.Razor.Test.Sources.xproj", "{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.Test.Sources", "src\Microsoft.AspNetCore.Razor.Test.Sources\Microsoft.AspNetCore.Razor.Test.Sources.xproj", "{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.VSRC1", "src\Microsoft.AspNet.Razor.Runtime.VSRC1\Microsoft.AspNet.Razor.Runtime.VSRC1.xproj", "{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.VSRC1", "src\Microsoft.AspNet.Razor.VSRC1\Microsoft.AspNet.Razor.VSRC1.xproj", "{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -43,6 +46,14 @@ Global
|
||||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -53,5 +64,7 @@ Global
|
||||||
{D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
{D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||||
{0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
{0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||||
|
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||||
|
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Html.Abstractions;
|
||||||
|
using Microsoft.Extensions.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerable object collection which knows how to write itself.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
internal class BufferedHtmlContent : IHtmlContentBuilder
|
||||||
|
{
|
||||||
|
// This is not List<IHtmlContent> because that would lead to wrapping all strings to IHtmlContent
|
||||||
|
// which is not space performant.
|
||||||
|
// internal for testing.
|
||||||
|
internal List<object> Entries { get; } = new List<object>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the <see cref="string"/> to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unencoded">The <c>string</c> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||||
|
public IHtmlContentBuilder Append(string unencoded)
|
||||||
|
{
|
||||||
|
Entries.Add(unencoded);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a <see cref="IHtmlContent"/> to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||||
|
public IHtmlContentBuilder Append(IHtmlContent htmlContent)
|
||||||
|
{
|
||||||
|
Entries.Add(htmlContent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the HTML encoded <see cref="string"/> to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoded">The HTML encoded <c>string</c> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||||
|
public IHtmlContentBuilder AppendHtml(string encoded)
|
||||||
|
{
|
||||||
|
Entries.Add(new HtmlEncodedString(encoded));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all the entries from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A reference to this instance after the Clear operation has completed.</returns>
|
||||||
|
public IHtmlContentBuilder Clear()
|
||||||
|
{
|
||||||
|
Entries.Clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryAsString = entry as string;
|
||||||
|
if (entryAsString != null)
|
||||||
|
{
|
||||||
|
encoder.HtmlEncode(entryAsString, writer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only string, IHtmlContent values can be added to the buffer.
|
||||||
|
((IHtmlContent)entry).WriteTo(writer, encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HtmlEncodedString : IHtmlContent
|
||||||
|
{
|
||||||
|
public static readonly IHtmlContent NewLine = new HtmlEncodedString(Environment.NewLine);
|
||||||
|
|
||||||
|
private readonly string _value;
|
||||||
|
|
||||||
|
public HtmlEncodedString(string value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
writer.Write(_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper related to generic interface definitions and implementing classes.
|
||||||
|
/// </summary>
|
||||||
|
internal static class ClosedGenericMatcher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
|
||||||
|
/// created from <paramref name="interfaceType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queryType">The <see cref="Type"/> of interest.</param>
|
||||||
|
/// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
|
||||||
|
/// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
|
||||||
|
/// relationship.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
|
||||||
|
/// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
|
||||||
|
/// <c>typeof(KeyValuePair{string, object})</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
|
||||||
|
{
|
||||||
|
if (queryType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(queryType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(interfaceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
Func<Type, bool> matchesInterface =
|
||||||
|
type => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == interfaceType;
|
||||||
|
if (matchesInterface(queryType))
|
||||||
|
{
|
||||||
|
// Checked type matches (i.e. is a closed generic type created from) the open generic type.
|
||||||
|
return queryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check all interfaces the type implements for a match.
|
||||||
|
return queryType.GetTypeInfo().ImplementedInterfaces.FirstOrDefault(matchesInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal
|
||||||
|
{
|
||||||
|
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||||
|
{
|
||||||
|
private readonly IDictionary<TKey, TValue> _sourceDictionary;
|
||||||
|
private readonly IEqualityComparer<TKey> _comparer;
|
||||||
|
private IDictionary<TKey, TValue> _innerDictionary;
|
||||||
|
|
||||||
|
public CopyOnWriteDictionary(
|
||||||
|
IDictionary<TKey, TValue> sourceDictionary,
|
||||||
|
IEqualityComparer<TKey> comparer)
|
||||||
|
{
|
||||||
|
if (sourceDictionary == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(sourceDictionary));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(comparer));
|
||||||
|
}
|
||||||
|
|
||||||
|
_sourceDictionary = sourceDictionary;
|
||||||
|
_comparer = comparer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<TKey, TValue> ReadDictionary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _innerDictionary ?? _sourceDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<TKey, TValue> WriteDictionary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_innerDictionary == null)
|
||||||
|
{
|
||||||
|
_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary,
|
||||||
|
_comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _innerDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ICollection<TKey> Keys
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ICollection<TValue> Values
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsReadOnly
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary[key];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
WriteDictionary[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool ContainsKey(TKey key)
|
||||||
|
{
|
||||||
|
return ReadDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
WriteDictionary.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool Remove(TKey key)
|
||||||
|
{
|
||||||
|
return WriteDictionary.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryGetValue(TKey key, out TValue value)
|
||||||
|
{
|
||||||
|
return ReadDictionary.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Add(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
WriteDictionary.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Clear()
|
||||||
|
{
|
||||||
|
WriteDictionary.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool Contains(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
return ReadDictionary.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
ReadDictionary.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
return WriteDictionary.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return ReadDictionary.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal
|
||||||
|
{
|
||||||
|
internal struct CopyOnWriteDictionaryHolder<TKey, TValue>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<TKey, TValue> _source;
|
||||||
|
private Dictionary<TKey, TValue> _copy;
|
||||||
|
|
||||||
|
public CopyOnWriteDictionaryHolder(Dictionary<TKey, TValue> source)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
_source = source;
|
||||||
|
_copy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder<TKey, TValue> source)
|
||||||
|
{
|
||||||
|
_source = source._copy ?? source._source;
|
||||||
|
_copy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasBeenCopied => _copy != null;
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue> ReadDictionary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_copy != null)
|
||||||
|
{
|
||||||
|
return _copy;
|
||||||
|
}
|
||||||
|
else if (_source != null)
|
||||||
|
{
|
||||||
|
return _source;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default-Constructor case
|
||||||
|
_copy = new Dictionary<TKey, TValue>();
|
||||||
|
return _copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue> WriteDictionary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_copy == null && _source == null)
|
||||||
|
{
|
||||||
|
// Default-Constructor case
|
||||||
|
_copy = new Dictionary<TKey, TValue>();
|
||||||
|
}
|
||||||
|
else if (_copy == null)
|
||||||
|
{
|
||||||
|
_copy = new Dictionary<TKey, TValue>(_source, _source.Comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue>.KeyCollection Keys
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue>.ValueCollection Values
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ReadDictionary[key];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
WriteDictionary[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(TKey key)
|
||||||
|
{
|
||||||
|
return ReadDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
WriteDictionary.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(TKey key)
|
||||||
|
{
|
||||||
|
return WriteDictionary.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
{
|
||||||
|
return ReadDictionary.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
WriteDictionary.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
return ((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||||
|
{
|
||||||
|
return ((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return ReadDictionary.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>5e8ec8bb-69b9-43d4-a095-7a06f65838e2</ProjectGuid>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||||
|
[assembly: NeutralResourcesLanguage("en-us")]
|
||||||
|
|
@ -0,0 +1,478 @@
|
||||||
|
// <auto-generated />
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
internal static class Resources
|
||||||
|
{
|
||||||
|
private static readonly ResourceManager _resourceManager
|
||||||
|
= new ResourceManager("Microsoft.AspNet.Razor.Runtime.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorResolver_InvalidTagHelperLookupText
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helper directive assembly name cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helper directive assembly name cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Must call '{2}.{1}' before calling '{2}.{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ScopeManager_EndCannotBeCalledWithoutACallToBegin
|
||||||
|
{
|
||||||
|
get { return GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Must call '{2}.{1}' before calling '{2}.{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatScopeManager_EndCannotBeCalledWithoutACallToBegin(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} name cannot be null or whitespace.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace
|
||||||
|
{
|
||||||
|
get { return GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} name cannot be null or whitespace.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ArgumentCannotBeNullOrEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("ArgumentCannotBeNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatArgumentCannotBeNullOrEmpty()
|
||||||
|
{
|
||||||
|
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorResolver_EncounteredUnexpectedError
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlTargetElementAttribute_InvalidName
|
||||||
|
{
|
||||||
|
get { return GetString("HtmlTargetElementAttribute_InvalidName"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHtmlTargetElementAttribute_InvalidName(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_InvalidName"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorResolver_InvalidTagHelperDirective
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperDirective"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperDirective(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperDirective"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorResolver_InvalidTagHelperPrefixValue
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperPrefixValue"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperPrefixValue"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Attribute
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Attribute"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Attribute()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// name
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Name
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Name"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// name
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Name()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// prefix
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Prefix
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Prefix"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// prefix
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Prefix()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Tag
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Tag"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Tag()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameAttribute
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNull
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNotNull
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot add a '{0}' with a null '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperAttributeList_CannotAddWithNullName
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperAttributeList_CannotAddWithNullName"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot add a '{0}' with a null '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperAttributeList_CannotAddWithNullName(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddWithNullName"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperAttributeList_CannotAddAttribute
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperAttributeList_CannotAddAttribute"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperAttributeList_CannotAddAttribute(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddAttribute"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent Tag
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_ParentTag
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_ParentTag"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent Tag
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_ParentTag()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_ParentTag");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Argument must be an instance of '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ArgumentMustBeAnInstanceOf
|
||||||
|
{
|
||||||
|
get { return GetString("ArgumentMustBeAnInstanceOf"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Argument must be an instance of '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatArgumentMustBeAnInstanceOf(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
{
|
||||||
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.Assert(value != null);
|
||||||
|
|
||||||
|
if (formatterNames != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < formatterNames.Length; i++)
|
||||||
|
{
|
||||||
|
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="TagHelperDescriptorResolver_InvalidTagHelperLookupText" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperTypeResolver_CannotResolveTagHelperAssembly" xml:space="preserve">
|
||||||
|
<value>Cannot resolve TagHelper containing assembly '{0}'. Error: {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull" xml:space="preserve">
|
||||||
|
<value>Tag helper directive assembly name cannot be null or empty.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
||||||
|
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace" xml:space="preserve">
|
||||||
|
<value>{0} name cannot be null or whitespace.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>The value cannot be null or empty.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorResolver_EncounteredUnexpectedError" xml:space="preserve">
|
||||||
|
<value>Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlTargetElementAttribute_InvalidName" xml:space="preserve">
|
||||||
|
<value>Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorResolver_InvalidTagHelperDirective" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorResolver_InvalidTagHelperPrefixValue" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Attribute" xml:space="preserve">
|
||||||
|
<value>Attribute</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Name" xml:space="preserve">
|
||||||
|
<value>name</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Prefix" xml:space="preserve">
|
||||||
|
<value>prefix</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
||||||
|
<value>Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameAttribute" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNull" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNotNull" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperAttributeList_CannotAddWithNullName" xml:space="preserve">
|
||||||
|
<value>Cannot add a '{0}' with a null '{1}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperAttributeList_CannotAddAttribute" xml:space="preserve">
|
||||||
|
<value>Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName" xml:space="preserve">
|
||||||
|
<value>Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
|
||||||
|
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
|
||||||
|
<value>Parent Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
|
||||||
|
<value>Argument must be an instance of '{0}'.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
public static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata common to types and properties.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMemberInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a collection of custom <see cref="Attribute"/>s of type <typeparamref name="TAttribute"/> applied
|
||||||
|
/// to this instance of <see cref="IMemberInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAttribute">The type of <see cref="Attribute"/> to search for.</typeparam>
|
||||||
|
/// <returns>A sequence of custom <see cref="Attribute"/>s of type
|
||||||
|
/// <typeparamref name="TAttribute"/>.</returns>
|
||||||
|
/// <remarks>Result not include inherited <see cref="Attribute"/>s.</remarks>
|
||||||
|
IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
|
||||||
|
where TAttribute : Attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains property metadata.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPropertyInfo : IMemberInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this property has a public getter.
|
||||||
|
/// </summary>
|
||||||
|
bool HasPublicGetter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this property has a public setter.
|
||||||
|
/// </summary>
|
||||||
|
bool HasPublicSetter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="ITypeInfo"/> of the property.
|
||||||
|
/// </summary>
|
||||||
|
ITypeInfo PropertyType { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains type metadata.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITypeInfo : IMemberInfo, IEquatable<ITypeInfo>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fully qualified name of the type.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// On CoreCLR, some BCL types get type forwarded to the full desktop framework implementations at
|
||||||
|
/// runtime. For e.g. we compile against System.String in System.Runtime which is type forwarded to
|
||||||
|
/// mscorlib at runtime. Consequently for generic types where the <see cref="FullName"/> includes the assembly
|
||||||
|
/// qualified name of generic parameters, FullNames would not match.
|
||||||
|
/// Use <see cref="IEquatable{ITypeInfo}.Equals(ITypeInfo)"/> to compare <see cref="ITypeInfo"/>s instead.
|
||||||
|
/// </remarks>
|
||||||
|
string FullName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets <see cref="IPropertyInfo"/>s for all properties of the current type excluding indexers.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Indexers in this context refer to the CLR notion of an indexer (<c>this [string name]</c>
|
||||||
|
/// and does not overlap with the semantics of
|
||||||
|
/// <see cref="Razor.Compilation.TagHelpers.TagHelperAttributeDescriptor.IsIndexer"/>.
|
||||||
|
/// </remarks>
|
||||||
|
IEnumerable<IPropertyInfo> Properties { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the type is public.
|
||||||
|
/// </summary>
|
||||||
|
bool IsPublic { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the type is abstract or an interface.
|
||||||
|
/// </summary>
|
||||||
|
bool IsAbstract { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the type is generic.
|
||||||
|
/// </summary>
|
||||||
|
bool IsGenericType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the type implements the <param name="interfaceTypeInfo"/> interface.
|
||||||
|
/// </summary>
|
||||||
|
bool ImplementsInterface(ITypeInfo interfaceTypeInfo);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of
|
||||||
|
/// <see cref="IDictionary{TKey, TValue}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="ITypeInfo"/> of <c>TKey</c> and <c>TValue</c>
|
||||||
|
/// parameters if the type implements <see cref="IDictionary{TKey, TValue}"/>, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// For open generic types, <see cref="ITypeInfo" /> for generic type parameters is <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
ITypeInfo[] GetGenericDictionaryParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IPropertyInfo"/> adapter for <see cref="PropertyInfo"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public class RuntimePropertyInfo : IPropertyInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="RuntimePropertyInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance to adapt.</param>
|
||||||
|
public RuntimePropertyInfo(PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(propertyInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
Property = propertyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="PropertyInfo"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public PropertyInfo Property { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasPublicGetter => Property.GetMethod != null && Property.GetMethod.IsPublic;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasPublicSetter => Property.SetMethod != null && Property.SetMethod.IsPublic;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => Property.Name;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ITypeInfo PropertyType => new RuntimeTypeInfo(Property.PropertyType.GetTypeInfo());
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
|
||||||
|
=> Property.GetCustomAttributes<TAttribute>(inherit: false);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString() =>
|
||||||
|
Property.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ITypeInfo"/> adapter for <see cref="System.Reflection.TypeInfo"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public class RuntimeTypeInfo : ITypeInfo
|
||||||
|
{
|
||||||
|
private static readonly Regex _fullNameSanitizer = new Regex(
|
||||||
|
@", [A-Za-z\.]+, Version=\d+\.\d+\.\d+\.\d+, Culture=neutral, PublicKeyToken=\w+",
|
||||||
|
RegexOptions.ExplicitCapture,
|
||||||
|
Constants.RegexMatchTimeout);
|
||||||
|
|
||||||
|
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
|
||||||
|
private IEnumerable<IPropertyInfo> _properties;
|
||||||
|
private string _sanitizedFullName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="RuntimeTypeInfo"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">The <see cref="System.Reflection.TypeInfo"/> instance to adapt.</param>
|
||||||
|
public RuntimeTypeInfo(TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
if (typeInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(typeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeInfo = typeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="System.Reflection.TypeInfo"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public TypeInfo TypeInfo { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => TypeInfo.Name;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string FullName => TypeInfo.FullName;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsAbstract => TypeInfo.IsAbstract;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsGenericType => TypeInfo.IsGenericType;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsPublic => TypeInfo.IsPublic;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<IPropertyInfo> Properties
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_properties == null)
|
||||||
|
{
|
||||||
|
_properties = TypeInfo
|
||||||
|
.AsType()
|
||||||
|
.GetRuntimeProperties()
|
||||||
|
.Where(property => property.GetIndexParameters().Length == 0)
|
||||||
|
.Select(property => new RuntimePropertyInfo(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
|
||||||
|
{
|
||||||
|
if (interfaceTypeInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(interfaceTypeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
|
||||||
|
if (runtimeTypeInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatArgumentMustBeAnInstanceOf(typeof(RuntimeTypeInfo).FullName),
|
||||||
|
nameof(interfaceTypeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtimeTypeInfo.TypeInfo.IsInterface && runtimeTypeInfo.TypeInfo.IsAssignableFrom(TypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SanitizedFullName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_sanitizedFullName == null)
|
||||||
|
{
|
||||||
|
_sanitizedFullName = SanitizeFullName(FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _sanitizedFullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute =>
|
||||||
|
TypeInfo.GetCustomAttributes<TAttribute>(inherit: false);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ITypeInfo[] GetGenericDictionaryParameters()
|
||||||
|
{
|
||||||
|
return ClosedGenericMatcher.ExtractGenericInterface(
|
||||||
|
TypeInfo.AsType(),
|
||||||
|
typeof(IDictionary<,>))
|
||||||
|
?.GenericTypeArguments
|
||||||
|
.Select(type => type.IsGenericParameter ? null : new RuntimeTypeInfo(type.GetTypeInfo()))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString() => TypeInfo.ToString();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as ITypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(ITypeInfo other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var otherRuntimeType = other as RuntimeTypeInfo;
|
||||||
|
if (otherRuntimeType != null)
|
||||||
|
{
|
||||||
|
return otherRuntimeType.TypeInfo == TypeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(
|
||||||
|
SanitizedFullName,
|
||||||
|
SanitizeFullName(other.FullName),
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode() => SanitizedFullName.GetHashCode();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes assembly qualification from generic type parameters for the specified <paramref name="fullName"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fullName">Full name.</param>
|
||||||
|
/// <returns>Full name without fully qualified generic parameters.</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <c>typeof(<see cref="List{string}"/>).FullName</c> is
|
||||||
|
/// List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]
|
||||||
|
/// <c>Sanitize(typeof(<see cref="List{string}"/>.FullName</c> returns
|
||||||
|
/// List`1[[System.String]
|
||||||
|
/// </example>
|
||||||
|
public static string SanitizeFullName(string fullName)
|
||||||
|
{
|
||||||
|
// In CoreCLR, some types (such as System.String) are type forwarded from System.Runtime
|
||||||
|
// to mscorlib at runtime. Type names of generic type parameters includes the assembly qualified name;
|
||||||
|
// consequently the type name generated at precompilation differs from the one at runtime. We'll
|
||||||
|
// avoid dealing with these inconsistencies by removing assembly information from TypeInfo.FullName.
|
||||||
|
return _fullNameSanitizer.Replace(fullName, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,753 @@
|
||||||
|
// 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 System.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="ITypeInfo"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public static class TagHelperDescriptorFactory
|
||||||
|
{
|
||||||
|
private const string DataDashPrefix = "data-";
|
||||||
|
private const string TagHelperNameEnding = "TagHelper";
|
||||||
|
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||||
|
|
||||||
|
// This matches the following AFTER the start of the input string (MATCH).
|
||||||
|
// Any letter/number followed by an uppercase letter then lowercase letter: 1(Aa), a(Aa), A(Aa)
|
||||||
|
// Any lowercase letter followed by an uppercase letter: a(A)
|
||||||
|
// Each match is then prefixed by a "-" via the ToHtmlCase method.
|
||||||
|
private static readonly Regex HtmlCaseRegex =
|
||||||
|
new Regex(
|
||||||
|
"(?<!^)((?<=[a-zA-Z0-9])[A-Z][a-z])|((?<=[a-z])[A-Z])",
|
||||||
|
RegexOptions.None,
|
||||||
|
Constants.RegexMatchTimeout);
|
||||||
|
|
||||||
|
private static readonly ITypeInfo StringTypeInfo = new RuntimeTypeInfo(typeof(string).GetTypeInfo());
|
||||||
|
|
||||||
|
// TODO: Investigate if we should cache TagHelperDescriptors for types:
|
||||||
|
// https://github.com/aspnet/Razor/issues/165
|
||||||
|
|
||||||
|
public static ICollection<char> InvalidNonWhitespaceNameCharacters { get; } = new HashSet<char>(
|
||||||
|
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="typeInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
||||||
|
/// <param name="typeInfo">The <see cref="ITypeInfo"/> to create a <see cref="TagHelperDescriptor"/> from.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="designTime">Indicates if the returned <see cref="TagHelperDescriptor"/>s should include
|
||||||
|
/// design time specific information.</param>
|
||||||
|
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
||||||
|
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="typeInfo"/>.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="typeInfo"/>.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
||||||
|
string assemblyName,
|
||||||
|
ITypeInfo typeInfo,
|
||||||
|
bool designTime,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
if (typeInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(typeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorSink == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorSink));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldSkipDescriptorCreation(designTime, typeInfo))
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<TagHelperDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeDescriptors = GetAttributeDescriptors(typeInfo, designTime, errorSink);
|
||||||
|
var targetElementAttributes = GetValidHtmlTargetElementAttributes(typeInfo, errorSink);
|
||||||
|
var allowedChildren = GetAllowedChildren(typeInfo, errorSink);
|
||||||
|
|
||||||
|
var tagHelperDescriptors =
|
||||||
|
BuildTagHelperDescriptors(
|
||||||
|
typeInfo,
|
||||||
|
assemblyName,
|
||||||
|
attributeDescriptors,
|
||||||
|
targetElementAttributes,
|
||||||
|
allowedChildren,
|
||||||
|
designTime);
|
||||||
|
|
||||||
|
return tagHelperDescriptors.Distinct(TagHelperDescriptorComparer.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<HtmlTargetElementAttribute> GetValidHtmlTargetElementAttributes(
|
||||||
|
ITypeInfo typeInfo,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var targetElementAttributes = typeInfo.GetCustomAttributes<HtmlTargetElementAttribute>();
|
||||||
|
|
||||||
|
return targetElementAttributes.Where(
|
||||||
|
attribute => ValidHtmlTargetElementAttributeNames(attribute, errorSink));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TagHelperDescriptor> BuildTagHelperDescriptors(
|
||||||
|
ITypeInfo typeInfo,
|
||||||
|
string assemblyName,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
|
IEnumerable<HtmlTargetElementAttribute> targetElementAttributes,
|
||||||
|
IEnumerable<string> allowedChildren,
|
||||||
|
bool designTime)
|
||||||
|
{
|
||||||
|
TagHelperDesignTimeDescriptor typeDesignTimeDescriptor = null;
|
||||||
|
|
||||||
|
#if !DOTNET5_4
|
||||||
|
if (designTime)
|
||||||
|
{
|
||||||
|
var runtimeTypeInfo = typeInfo as RuntimeTypeInfo;
|
||||||
|
if (runtimeTypeInfo != null)
|
||||||
|
{
|
||||||
|
typeDesignTimeDescriptor =
|
||||||
|
TagHelperDesignTimeDescriptorFactory.CreateDescriptor(runtimeTypeInfo.TypeInfo.AsType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var typeName = typeInfo.FullName;
|
||||||
|
|
||||||
|
// If there isn't an attribute specifying the tag name derive it from the name
|
||||||
|
if (!targetElementAttributes.Any())
|
||||||
|
{
|
||||||
|
var name = typeInfo.Name;
|
||||||
|
|
||||||
|
if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
name = name.Substring(0, name.Length - TagHelperNameEnding.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
BuildTagHelperDescriptor(
|
||||||
|
ToHtmlCase(name),
|
||||||
|
typeName,
|
||||||
|
assemblyName,
|
||||||
|
attributeDescriptors,
|
||||||
|
requiredAttributes: Enumerable.Empty<string>(),
|
||||||
|
allowedChildren: allowedChildren,
|
||||||
|
tagStructure: default(TagStructure),
|
||||||
|
parentTag: null,
|
||||||
|
designTimeDescriptor: typeDesignTimeDescriptor)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetElementAttributes.Select(
|
||||||
|
attribute =>
|
||||||
|
BuildTagHelperDescriptor(
|
||||||
|
typeName,
|
||||||
|
assemblyName,
|
||||||
|
attributeDescriptors,
|
||||||
|
attribute,
|
||||||
|
allowedChildren,
|
||||||
|
typeDesignTimeDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetAllowedChildren(ITypeInfo typeInfo, ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var restrictChildrenAttribute = typeInfo
|
||||||
|
.GetCustomAttributes<RestrictChildrenAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (restrictChildrenAttribute == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedChildren = restrictChildrenAttribute.ChildTags;
|
||||||
|
var validAllowedChildren = GetValidAllowedChildren(allowedChildren, typeInfo.FullName, errorSink);
|
||||||
|
|
||||||
|
if (validAllowedChildren.Any())
|
||||||
|
{
|
||||||
|
return validAllowedChildren;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// All allowed children were invalid, return null to indicate that any child is acceptable.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for unit testing
|
||||||
|
internal static IEnumerable<string> GetValidAllowedChildren(
|
||||||
|
IEnumerable<string> allowedChildren,
|
||||||
|
string tagHelperName,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var validAllowedChildren = new List<string>();
|
||||||
|
|
||||||
|
foreach (var name in allowedChildren)
|
||||||
|
{
|
||||||
|
var valid = TryValidateName(
|
||||||
|
name,
|
||||||
|
whitespaceError:
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(
|
||||||
|
nameof(RestrictChildrenAttribute),
|
||||||
|
tagHelperName),
|
||||||
|
characterErrorBuilder: (invalidCharacter) =>
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(
|
||||||
|
nameof(RestrictChildrenAttribute),
|
||||||
|
name,
|
||||||
|
tagHelperName,
|
||||||
|
invalidCharacter),
|
||||||
|
errorSink: errorSink);
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
validAllowedChildren.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validAllowedChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperDescriptor BuildTagHelperDescriptor(
|
||||||
|
string typeName,
|
||||||
|
string assemblyName,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
|
HtmlTargetElementAttribute targetElementAttribute,
|
||||||
|
IEnumerable<string> allowedChildren,
|
||||||
|
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||||
|
{
|
||||||
|
var requiredAttributes = GetCommaSeparatedValues(targetElementAttribute.Attributes);
|
||||||
|
|
||||||
|
return BuildTagHelperDescriptor(
|
||||||
|
targetElementAttribute.Tag,
|
||||||
|
typeName,
|
||||||
|
assemblyName,
|
||||||
|
attributeDescriptors,
|
||||||
|
requiredAttributes,
|
||||||
|
allowedChildren,
|
||||||
|
targetElementAttribute.ParentTag,
|
||||||
|
targetElementAttribute.TagStructure,
|
||||||
|
designTimeDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperDescriptor BuildTagHelperDescriptor(
|
||||||
|
string tagName,
|
||||||
|
string typeName,
|
||||||
|
string assemblyName,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
|
IEnumerable<string> requiredAttributes,
|
||||||
|
IEnumerable<string> allowedChildren,
|
||||||
|
string parentTag,
|
||||||
|
TagStructure tagStructure,
|
||||||
|
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||||
|
{
|
||||||
|
return new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = tagName,
|
||||||
|
TypeName = typeName,
|
||||||
|
AssemblyName = assemblyName,
|
||||||
|
Attributes = attributeDescriptors,
|
||||||
|
RequiredAttributes = requiredAttributes,
|
||||||
|
AllowedChildren = allowedChildren,
|
||||||
|
RequiredParent = parentTag,
|
||||||
|
TagStructure = tagStructure,
|
||||||
|
DesignTimeDescriptor = designTimeDescriptor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing.
|
||||||
|
/// </summary>
|
||||||
|
internal static IEnumerable<string> GetCommaSeparatedValues(string text)
|
||||||
|
{
|
||||||
|
// We don't want to remove empty entries, need to notify users of invalid values.
|
||||||
|
return text?.Split(',').Select(tagName => tagName.Trim()) ?? Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing.
|
||||||
|
/// </summary>
|
||||||
|
internal static bool ValidHtmlTargetElementAttributeNames(
|
||||||
|
HtmlTargetElementAttribute attribute,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var validTagName = ValidateName(attribute.Tag, targetingAttributes: false, errorSink: errorSink);
|
||||||
|
var validAttributeNames = true;
|
||||||
|
var attributeNames = GetCommaSeparatedValues(attribute.Attributes);
|
||||||
|
|
||||||
|
foreach (var attributeName in attributeNames)
|
||||||
|
{
|
||||||
|
if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
||||||
|
{
|
||||||
|
validAttributeNames = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
||||||
|
|
||||||
|
return validTagName && validAttributeNames && validParentTagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for unit testing.
|
||||||
|
/// </summary>
|
||||||
|
internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
return parentTag == null ||
|
||||||
|
TryValidateName(
|
||||||
|
parentTag,
|
||||||
|
Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(
|
||||||
|
Resources.TagHelperDescriptorFactory_ParentTag),
|
||||||
|
characterErrorBuilder: (invalidCharacter) =>
|
||||||
|
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||||
|
Resources.TagHelperDescriptorFactory_ParentTag.ToLower(),
|
||||||
|
parentTag,
|
||||||
|
invalidCharacter),
|
||||||
|
errorSink: errorSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ValidateName(
|
||||||
|
string name,
|
||||||
|
bool targetingAttributes,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
if (!targetingAttributes &&
|
||||||
|
string.Equals(
|
||||||
|
name,
|
||||||
|
TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// '*' as the entire name is OK in the HtmlTargetElement catch-all case.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (targetingAttributes &&
|
||||||
|
name.EndsWith(
|
||||||
|
TagHelperDescriptorProvider.RequiredAttributeWildcardSuffix,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// A single '*' at the end of a required attribute is valid; everywhere else is invalid. Strip it from
|
||||||
|
// the end so we can validate the rest of the name.
|
||||||
|
name = name.Substring(0, name.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetName = targetingAttributes ?
|
||||||
|
Resources.TagHelperDescriptorFactory_Attribute :
|
||||||
|
Resources.TagHelperDescriptorFactory_Tag;
|
||||||
|
|
||||||
|
var validName = TryValidateName(
|
||||||
|
name,
|
||||||
|
whitespaceError: Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName),
|
||||||
|
characterErrorBuilder: (invalidCharacter) =>
|
||||||
|
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||||
|
targetName.ToLower(),
|
||||||
|
name,
|
||||||
|
invalidCharacter),
|
||||||
|
errorSink: errorSink);
|
||||||
|
|
||||||
|
return validName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryValidateName(
|
||||||
|
string name,
|
||||||
|
string whitespaceError,
|
||||||
|
Func<char, string> characterErrorBuilder,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var validName = true;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0);
|
||||||
|
|
||||||
|
validName = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var character in name)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(character) ||
|
||||||
|
InvalidNonWhitespaceNameCharacters.Contains(character))
|
||||||
|
{
|
||||||
|
var error = characterErrorBuilder(character);
|
||||||
|
errorSink.OnError(SourceLocation.Zero, error, length: 0);
|
||||||
|
|
||||||
|
validName = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
|
||||||
|
ITypeInfo type,
|
||||||
|
bool designTime,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
|
// Keep indexer descriptors separate to avoid sorting the combined list later.
|
||||||
|
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
|
var accessibleProperties = type.Properties.Where(IsAccessibleProperty);
|
||||||
|
foreach (var property in accessibleProperties)
|
||||||
|
{
|
||||||
|
if (ShouldSkipDescriptorCreation(designTime, property))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeNameAttribute = property
|
||||||
|
.GetCustomAttributes<HtmlAttributeNameAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
var hasExplicitName =
|
||||||
|
attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
|
||||||
|
var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);
|
||||||
|
|
||||||
|
TagHelperAttributeDescriptor mainDescriptor = null;
|
||||||
|
if (property.HasPublicSetter)
|
||||||
|
{
|
||||||
|
mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
|
||||||
|
if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
|
||||||
|
{
|
||||||
|
// HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (hasExplicitName)
|
||||||
|
{
|
||||||
|
// Specified HtmlAttributeNameAttribute.Name though property has no public setter.
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(
|
||||||
|
type.FullName,
|
||||||
|
property.Name,
|
||||||
|
typeof(HtmlAttributeNameAttribute).FullName,
|
||||||
|
nameof(HtmlAttributeNameAttribute.Name)),
|
||||||
|
length: 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInvalid;
|
||||||
|
var indexerDescriptor = ToIndexerAttributeDescriptor(
|
||||||
|
property,
|
||||||
|
attributeNameAttribute,
|
||||||
|
parentType: type,
|
||||||
|
errorSink: errorSink,
|
||||||
|
defaultPrefix: attributeName + "-",
|
||||||
|
designTime: designTime,
|
||||||
|
isInvalid: out isInvalid);
|
||||||
|
if (indexerDescriptor != null &&
|
||||||
|
!ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
|
||||||
|
{
|
||||||
|
isInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid)
|
||||||
|
{
|
||||||
|
// The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the
|
||||||
|
// HTML-casing of the property name) is invalid. Ignore this property completely.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainDescriptor != null)
|
||||||
|
{
|
||||||
|
attributeDescriptors.Add(mainDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexerDescriptor != null)
|
||||||
|
{
|
||||||
|
indexerDescriptors.Add(indexerDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeDescriptors.AddRange(indexerDescriptors);
|
||||||
|
|
||||||
|
return attributeDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing.
|
||||||
|
internal static bool ValidateTagHelperAttributeDescriptor(
|
||||||
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
ITypeInfo parentType,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
string nameOrPrefix;
|
||||||
|
if (attributeDescriptor.IsIndexer)
|
||||||
|
{
|
||||||
|
nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(attributeDescriptor.Name))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
|
||||||
|
parentType.FullName,
|
||||||
|
attributeDescriptor.PropertyName),
|
||||||
|
length: 0);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateTagHelperAttributeNameOrPrefix(
|
||||||
|
attributeDescriptor.Name,
|
||||||
|
parentType,
|
||||||
|
attributeDescriptor.PropertyName,
|
||||||
|
errorSink,
|
||||||
|
nameOrPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldSkipDescriptorCreation(bool designTime, IMemberInfo memberInfo)
|
||||||
|
{
|
||||||
|
if (designTime)
|
||||||
|
{
|
||||||
|
var editorBrowsableAttribute = memberInfo
|
||||||
|
.GetCustomAttributes<EditorBrowsableAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return editorBrowsableAttribute != null &&
|
||||||
|
editorBrowsableAttribute.State == EditorBrowsableState.Never;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ValidateTagHelperAttributeNameOrPrefix(
|
||||||
|
string attributeNameOrPrefix,
|
||||||
|
ITypeInfo parentType,
|
||||||
|
string propertyName,
|
||||||
|
ErrorSink errorSink,
|
||||||
|
string nameOrPrefix)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(attributeNameOrPrefix))
|
||||||
|
{
|
||||||
|
// ValidateTagHelperAttributeDescriptor validates Name is non-null and non-empty. The empty string is
|
||||||
|
// valid for DictionaryAttributePrefix and null is impossible at this point because it means "don't
|
||||||
|
// create a descriptor". (Empty DictionaryAttributePrefix is a corner case which would bind every
|
||||||
|
// attribute of a target element. Likely not particularly useful but unclear what minimum length
|
||||||
|
// should be required and what scenarios a minimum length would break.)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(attributeNameOrPrefix))
|
||||||
|
{
|
||||||
|
// Provide a single error if the entire name is whitespace, not an error per character.
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(
|
||||||
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix),
|
||||||
|
length: 0);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data-* attributes are explicitly not implemented by user agents and are not intended for use on
|
||||||
|
// the server; therefore it's invalid for TagHelpers to bind to them.
|
||||||
|
if (attributeNameOrPrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(
|
||||||
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix,
|
||||||
|
attributeNameOrPrefix,
|
||||||
|
DataDashPrefix),
|
||||||
|
length: 0);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isValid = true;
|
||||||
|
foreach (var character in attributeNameOrPrefix)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(
|
||||||
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix,
|
||||||
|
attributeNameOrPrefix,
|
||||||
|
character),
|
||||||
|
length: 0);
|
||||||
|
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||||
|
IPropertyInfo property,
|
||||||
|
string attributeName,
|
||||||
|
bool designTime)
|
||||||
|
{
|
||||||
|
return ToAttributeDescriptor(
|
||||||
|
property,
|
||||||
|
attributeName,
|
||||||
|
property.PropertyType.FullName,
|
||||||
|
isIndexer: false,
|
||||||
|
isStringProperty: StringTypeInfo.Equals(property.PropertyType),
|
||||||
|
designTime: designTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
|
||||||
|
IPropertyInfo property,
|
||||||
|
HtmlAttributeNameAttribute attributeNameAttribute,
|
||||||
|
ITypeInfo parentType,
|
||||||
|
ErrorSink errorSink,
|
||||||
|
string defaultPrefix,
|
||||||
|
bool designTime,
|
||||||
|
out bool isInvalid)
|
||||||
|
{
|
||||||
|
isInvalid = false;
|
||||||
|
var hasPublicSetter = property.HasPublicSetter;
|
||||||
|
var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameters();
|
||||||
|
if (!StringTypeInfo.Equals(dictionaryTypeArguments?[0]))
|
||||||
|
{
|
||||||
|
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
|
||||||
|
{
|
||||||
|
// DictionaryAttributePrefix is not supported unless associated with an
|
||||||
|
// IDictionary<string, TValue> property.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
||||||
|
"IDictionary<string, TValue>"),
|
||||||
|
length: 0);
|
||||||
|
}
|
||||||
|
else if (attributeNameAttribute != null && !hasPublicSetter)
|
||||||
|
{
|
||||||
|
// Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public
|
||||||
|
// setter.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
"IDictionary<string, TValue>"),
|
||||||
|
length: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (!hasPublicSetter &&
|
||||||
|
attributeNameAttribute != null &&
|
||||||
|
!attributeNameAttribute.DictionaryAttributePrefixSet)
|
||||||
|
{
|
||||||
|
// Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property
|
||||||
|
// that lacks a public setter.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
||||||
|
"IDictionary<string, TValue>"),
|
||||||
|
length: 0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potential prefix case. Use default prefix (based on name)?
|
||||||
|
var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;
|
||||||
|
|
||||||
|
var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
// DictionaryAttributePrefix explicitly set to null. Ignore.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToAttributeDescriptor(
|
||||||
|
property,
|
||||||
|
attributeName: prefix,
|
||||||
|
typeName: dictionaryTypeArguments[1].FullName,
|
||||||
|
isIndexer: true,
|
||||||
|
isStringProperty: StringTypeInfo.Equals(dictionaryTypeArguments[1]),
|
||||||
|
designTime: designTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||||
|
IPropertyInfo property,
|
||||||
|
string attributeName,
|
||||||
|
string typeName,
|
||||||
|
bool isIndexer,
|
||||||
|
bool isStringProperty,
|
||||||
|
bool designTime)
|
||||||
|
{
|
||||||
|
TagHelperAttributeDesignTimeDescriptor propertyDesignTimeDescriptor = null;
|
||||||
|
|
||||||
|
#if !DOTNET5_4
|
||||||
|
if (designTime)
|
||||||
|
{
|
||||||
|
var runtimeProperty = property as RuntimePropertyInfo;
|
||||||
|
if (runtimeProperty != null)
|
||||||
|
{
|
||||||
|
propertyDesignTimeDescriptor =
|
||||||
|
TagHelperDesignTimeDescriptorFactory.CreateAttributeDescriptor(runtimeProperty.Property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return new TagHelperAttributeDescriptor
|
||||||
|
{
|
||||||
|
Name = attributeName,
|
||||||
|
PropertyName = property.Name,
|
||||||
|
TypeName = typeName,
|
||||||
|
IsStringProperty = isStringProperty,
|
||||||
|
IsIndexer = isIndexer,
|
||||||
|
DesignTimeDescriptor = propertyDesignTimeDescriptor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAccessibleProperty(IPropertyInfo property)
|
||||||
|
{
|
||||||
|
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
|
||||||
|
return property.HasPublicGetter &&
|
||||||
|
property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>().FirstOrDefault() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts from pascal/camel case to lower kebab-case.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// SomeThing => some-thing
|
||||||
|
/// capsONInside => caps-on-inside
|
||||||
|
/// CAPSOnOUTSIDE => caps-on-outside
|
||||||
|
/// ALLCAPS => allcaps
|
||||||
|
/// One1Two2Three3 => one1-two2-three3
|
||||||
|
/// ONE1TWO2THREE3 => one1two2three3
|
||||||
|
/// First_Second_ThirdHi => first_second_third-hi
|
||||||
|
/// </example>
|
||||||
|
private static string ToHtmlCase(string name)
|
||||||
|
{
|
||||||
|
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.Parser;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to resolve <see cref="TagHelperDescriptor"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlyDictionary<TagHelperDirectiveType, string> _directiveNames =
|
||||||
|
new Dictionary<TagHelperDirectiveType, string>
|
||||||
|
{
|
||||||
|
{ TagHelperDirectiveType.AddTagHelper, SyntaxConstants.CSharp.AddTagHelperKeyword },
|
||||||
|
{ TagHelperDirectiveType.RemoveTagHelper, SyntaxConstants.CSharp.RemoveTagHelperKeyword },
|
||||||
|
{ TagHelperDirectiveType.TagHelperPrefix, SyntaxConstants.CSharp.TagHelperPrefixKeyword },
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly TagHelperTypeResolver _typeResolver;
|
||||||
|
private readonly bool _designTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="TagHelperDescriptorResolver"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
|
||||||
|
/// design time specific information.</param>
|
||||||
|
public TagHelperDescriptorResolver(bool designTime)
|
||||||
|
: this(new TagHelperTypeResolver(), designTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperDescriptorResolver"/> class with the
|
||||||
|
/// specified <paramref name="typeResolver"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="typeResolver">The <see cref="TagHelperTypeResolver"/>.</param>
|
||||||
|
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
|
||||||
|
/// design time specific information.</param>
|
||||||
|
public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver, bool designTime)
|
||||||
|
{
|
||||||
|
_typeResolver = typeResolver;
|
||||||
|
_designTime = designTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedDescriptors = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
|
||||||
|
|
||||||
|
// tagHelperPrefix directives do not affect which TagHelperDescriptors are added or removed from the final
|
||||||
|
// list, need to remove them.
|
||||||
|
var actionableDirectiveDescriptors = context.DirectiveDescriptors.Where(
|
||||||
|
directive => directive.DirectiveType != TagHelperDirectiveType.TagHelperPrefix);
|
||||||
|
|
||||||
|
foreach (var directiveDescriptor in actionableDirectiveDescriptors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var lookupInfo = GetLookupInfo(directiveDescriptor, context.ErrorSink);
|
||||||
|
|
||||||
|
// Could not resolve the lookup info.
|
||||||
|
if (lookupInfo == null)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<TagHelperDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper)
|
||||||
|
{
|
||||||
|
resolvedDescriptors.RemoveWhere(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
|
||||||
|
}
|
||||||
|
else if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.AddTagHelper)
|
||||||
|
{
|
||||||
|
var descriptors = ResolveDescriptorsInAssembly(
|
||||||
|
lookupInfo.AssemblyName,
|
||||||
|
lookupInfo.AssemblyNameLocation,
|
||||||
|
context.ErrorSink);
|
||||||
|
|
||||||
|
// Only use descriptors that match our lookup info
|
||||||
|
descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
|
||||||
|
|
||||||
|
resolvedDescriptors.UnionWith(descriptors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string directiveName;
|
||||||
|
_directiveNames.TryGetValue(directiveDescriptor.DirectiveType, out directiveName);
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(directiveName));
|
||||||
|
|
||||||
|
context.ErrorSink.OnError(
|
||||||
|
directiveDescriptor.Location,
|
||||||
|
Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(
|
||||||
|
"@" + directiveName,
|
||||||
|
directiveDescriptor.DirectiveText,
|
||||||
|
ex.Message),
|
||||||
|
GetErrorLength(directiveDescriptor.DirectiveText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefixedDescriptors = PrefixDescriptors(context, resolvedDescriptors);
|
||||||
|
|
||||||
|
return prefixedDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves all <see cref="TagHelperDescriptor"/>s for <see cref="Razor.TagHelpers.ITagHelper"/>s from the
|
||||||
|
/// given <paramref name="assemblyName"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName">
|
||||||
|
/// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the directive.</param>
|
||||||
|
/// <param name="errorSink">Used to record errors found when resolving <see cref="TagHelperDescriptor"/>s
|
||||||
|
/// within the given <paramref name="assemblyName"/>.</param>
|
||||||
|
/// <returns><see cref="TagHelperDescriptor"/>s for <see cref="Razor.TagHelpers.ITagHelper"/>s from the given
|
||||||
|
/// <paramref name="assemblyName"/>.</returns>
|
||||||
|
// This is meant to be overridden by tooling to enable assembly level caching.
|
||||||
|
protected virtual IEnumerable<TagHelperDescriptor> ResolveDescriptorsInAssembly(
|
||||||
|
string assemblyName,
|
||||||
|
SourceLocation documentLocation,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
// Resolve valid tag helper types from the assembly.
|
||||||
|
var tagHelperTypes = _typeResolver.Resolve(assemblyName, documentLocation, errorSink);
|
||||||
|
|
||||||
|
// Convert types to TagHelperDescriptors
|
||||||
|
var descriptors = tagHelperTypes.SelectMany(
|
||||||
|
type => TagHelperDescriptorFactory.CreateDescriptors(assemblyName, type, _designTime, errorSink));
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TagHelperDescriptor> PrefixDescriptors(
|
||||||
|
TagHelperDescriptorResolutionContext context,
|
||||||
|
IEnumerable<TagHelperDescriptor> descriptors)
|
||||||
|
{
|
||||||
|
var tagHelperPrefix = ResolveTagHelperPrefix(context);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(tagHelperPrefix))
|
||||||
|
{
|
||||||
|
return descriptors.Select(descriptor =>
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
Prefix = tagHelperPrefix,
|
||||||
|
TagName = descriptor.TagName,
|
||||||
|
TypeName = descriptor.TypeName,
|
||||||
|
AssemblyName = descriptor.AssemblyName,
|
||||||
|
Attributes = descriptor.Attributes,
|
||||||
|
RequiredAttributes = descriptor.RequiredAttributes,
|
||||||
|
AllowedChildren = descriptor.AllowedChildren,
|
||||||
|
RequiredParent = descriptor.RequiredParent,
|
||||||
|
TagStructure = descriptor.TagStructure,
|
||||||
|
DesignTimeDescriptor = descriptor.DesignTimeDescriptor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveTagHelperPrefix(TagHelperDescriptorResolutionContext context)
|
||||||
|
{
|
||||||
|
var prefixDirectiveDescriptors = context.DirectiveDescriptors.Where(
|
||||||
|
descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix);
|
||||||
|
|
||||||
|
TagHelperDirectiveDescriptor prefixDirective = null;
|
||||||
|
|
||||||
|
foreach (var directive in prefixDirectiveDescriptors)
|
||||||
|
{
|
||||||
|
if (prefixDirective == null)
|
||||||
|
{
|
||||||
|
prefixDirective = directive;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For each invalid @tagHelperPrefix we need to create an error.
|
||||||
|
context.ErrorSink.OnError(
|
||||||
|
directive.Location,
|
||||||
|
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperDirective(
|
||||||
|
SyntaxConstants.CSharp.TagHelperPrefixKeyword),
|
||||||
|
GetErrorLength(directive.DirectiveText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix = prefixDirective?.DirectiveText;
|
||||||
|
|
||||||
|
if (prefix != null && !EnsureValidPrefix(prefix, prefixDirective.Location, context.ErrorSink))
|
||||||
|
{
|
||||||
|
prefix = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool EnsureValidPrefix(
|
||||||
|
string prefix,
|
||||||
|
SourceLocation directiveLocation,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
foreach (var character in prefix)
|
||||||
|
{
|
||||||
|
// Prefixes are correlated with tag names, tag names cannot have whitespace.
|
||||||
|
if (char.IsWhiteSpace(character) ||
|
||||||
|
TagHelperDescriptorFactory.InvalidNonWhitespaceNameCharacters.Contains(character))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
directiveLocation,
|
||||||
|
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(
|
||||||
|
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
|
||||||
|
character,
|
||||||
|
prefix),
|
||||||
|
prefix.Length);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesLookupInfo(TagHelperDescriptor descriptor, LookupInfo lookupInfo)
|
||||||
|
{
|
||||||
|
if (!string.Equals(descriptor.AssemblyName, lookupInfo.AssemblyName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lookupInfo.TypePattern.EndsWith("*", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
if (lookupInfo.TypePattern.Length == 1)
|
||||||
|
{
|
||||||
|
// TypePattern is "*".
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lookupTypeName = lookupInfo.TypePattern.Substring(0, lookupInfo.TypePattern.Length - 1);
|
||||||
|
|
||||||
|
return descriptor.TypeName.StartsWith(lookupTypeName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(descriptor.TypeName, lookupInfo.TypePattern, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LookupInfo GetLookupInfo(
|
||||||
|
TagHelperDirectiveDescriptor directiveDescriptor,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
var lookupText = directiveDescriptor.DirectiveText;
|
||||||
|
var lookupStrings = lookupText?.Split(new[] { ',' });
|
||||||
|
|
||||||
|
// Ensure that we have valid lookupStrings to work with. The valid format is "typeName, assemblyName"
|
||||||
|
if (lookupStrings == null ||
|
||||||
|
lookupStrings.Any(string.IsNullOrWhiteSpace) ||
|
||||||
|
lookupStrings.Length != 2)
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
directiveDescriptor.Location,
|
||||||
|
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
|
||||||
|
GetErrorLength(lookupText));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trimmedAssemblyName = lookupStrings[1].Trim();
|
||||||
|
|
||||||
|
// + 1 is for the comma separator in the lookup text.
|
||||||
|
var assemblyNameIndex = lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName);
|
||||||
|
var assemblyNamePrefix = directiveDescriptor.DirectiveText.Substring(0, assemblyNameIndex);
|
||||||
|
var assemblyNameLocation = SourceLocation.Advance(directiveDescriptor.Location, assemblyNamePrefix);
|
||||||
|
|
||||||
|
return new LookupInfo
|
||||||
|
{
|
||||||
|
TypePattern = lookupStrings[0].Trim(),
|
||||||
|
AssemblyName = trimmedAssemblyName,
|
||||||
|
AssemblyNameLocation = assemblyNameLocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetErrorLength(string directiveText)
|
||||||
|
{
|
||||||
|
var nonNullLength = directiveText == null ? 1 : directiveText.Length;
|
||||||
|
var normalizeEmptyStringLength = Math.Max(nonNullLength, 1);
|
||||||
|
|
||||||
|
return normalizeEmptyStringLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LookupInfo
|
||||||
|
{
|
||||||
|
public string AssemblyName { get; set; }
|
||||||
|
|
||||||
|
public string TypePattern { get; set; }
|
||||||
|
|
||||||
|
public SourceLocation AssemblyNameLocation { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#if !DOTNET5_4 // Cannot accurately resolve the location of the documentation XML file in coreclr.
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for providing <see cref="TagHelperDesignTimeDescriptor"/>s from <see cref="Type"/>s and
|
||||||
|
/// <see cref="TagHelperAttributeDesignTimeDescriptor"/>s from <see cref="PropertyInfo"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public static class TagHelperDesignTimeDescriptorFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="TagHelperDesignTimeDescriptor"/> from the given <paramref name="type"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">
|
||||||
|
/// The <see cref="Type"/> to create a <see cref="TagHelperDesignTimeDescriptor"/> from.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A <see cref="TagHelperDesignTimeDescriptor"/> that describes design time specific information
|
||||||
|
/// for the given <paramref name="type"/>.</returns>
|
||||||
|
public static TagHelperDesignTimeDescriptor CreateDescriptor(Type type)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = XmlDocumentationProvider.GetId(type);
|
||||||
|
var documentationDescriptor = CreateDocumentationDescriptor(type.Assembly, id);
|
||||||
|
|
||||||
|
// Purposefully not using the TypeInfo.GetCustomAttributes method here to make it easier to mock the Type.
|
||||||
|
var outputElementHintAttribute = type
|
||||||
|
.GetCustomAttributes(inherit: false)
|
||||||
|
?.OfType<OutputElementHintAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
var outputElementHint = outputElementHintAttribute?.OutputElement;
|
||||||
|
|
||||||
|
if (documentationDescriptor != null || outputElementHint != null)
|
||||||
|
{
|
||||||
|
return new TagHelperDesignTimeDescriptor
|
||||||
|
{
|
||||||
|
Summary = documentationDescriptor?.Summary,
|
||||||
|
Remarks = documentationDescriptor?.Remarks,
|
||||||
|
OutputElementHint = outputElementHint
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="TagHelperAttributeDesignTimeDescriptor"/> from the given
|
||||||
|
/// <paramref name="propertyInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">
|
||||||
|
/// The <see cref="PropertyInfo"/> to create a <see cref="TagHelperAttributeDesignTimeDescriptor"/> from.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A <see cref="TagHelperAttributeDesignTimeDescriptor"/> that describes design time specific
|
||||||
|
/// information for the given <paramref name="propertyInfo"/>.</returns>
|
||||||
|
public static TagHelperAttributeDesignTimeDescriptor CreateAttributeDescriptor(
|
||||||
|
PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(propertyInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = XmlDocumentationProvider.GetId(propertyInfo);
|
||||||
|
var declaringAssembly = propertyInfo.DeclaringType.Assembly;
|
||||||
|
var documentationDescriptor = CreateDocumentationDescriptor(declaringAssembly, id);
|
||||||
|
|
||||||
|
if (documentationDescriptor != null)
|
||||||
|
{
|
||||||
|
return new TagHelperAttributeDesignTimeDescriptor
|
||||||
|
{
|
||||||
|
Summary = documentationDescriptor.Summary,
|
||||||
|
Remarks = documentationDescriptor.Remarks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DocumentationDescriptor CreateDocumentationDescriptor(Assembly assembly, string id)
|
||||||
|
{
|
||||||
|
var assemblyLocation = assembly.Location;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(assemblyLocation) && !string.IsNullOrEmpty(assembly.CodeBase))
|
||||||
|
{
|
||||||
|
var uri = new UriBuilder(assembly.CodeBase);
|
||||||
|
|
||||||
|
// Normalize the path to a UNC path. This will remove things like file:// from start of the uri.Path.
|
||||||
|
assemblyLocation = Uri.UnescapeDataString(uri.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't resolve a valid assemblyLocation.
|
||||||
|
if (string.IsNullOrEmpty(assemblyLocation))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlDocumentationFile = GetXmlDocumentationFile(assembly, assemblyLocation);
|
||||||
|
|
||||||
|
// Only want to process the file if it exists.
|
||||||
|
if (xmlDocumentationFile != null)
|
||||||
|
{
|
||||||
|
var documentationProvider = new XmlDocumentationProvider(xmlDocumentationFile.FullName);
|
||||||
|
|
||||||
|
var summary = documentationProvider.GetSummary(id);
|
||||||
|
var remarks = documentationProvider.GetRemarks(id);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(summary) || !string.IsNullOrEmpty(remarks))
|
||||||
|
{
|
||||||
|
return new DocumentationDescriptor
|
||||||
|
{
|
||||||
|
Summary = summary,
|
||||||
|
Remarks = remarks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileInfo GetXmlDocumentationFile(Assembly assembly, string assemblyLocation)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assemblyDirectory = Path.GetDirectoryName(assemblyLocation);
|
||||||
|
var assemblyName = Path.GetFileName(assemblyLocation);
|
||||||
|
var assemblyXmlDocumentationName = Path.ChangeExtension(assemblyName, ".xml");
|
||||||
|
|
||||||
|
// Check for a localized XML file for the current culture.
|
||||||
|
var xmlDocumentationFile = GetLocalizedXmlDocumentationFile(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
assemblyDirectory,
|
||||||
|
assemblyXmlDocumentationName);
|
||||||
|
|
||||||
|
if (xmlDocumentationFile == null)
|
||||||
|
{
|
||||||
|
// Check for a culture-neutral XML file next to the assembly
|
||||||
|
xmlDocumentationFile = new FileInfo(
|
||||||
|
Path.Combine(assemblyDirectory, assemblyXmlDocumentationName));
|
||||||
|
|
||||||
|
if (!xmlDocumentationFile.Exists)
|
||||||
|
{
|
||||||
|
xmlDocumentationFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlDocumentationFile;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// Could not resolve XML file.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> ExpandPaths(
|
||||||
|
CultureInfo culture,
|
||||||
|
string assemblyDirectory,
|
||||||
|
string assemblyXmlDocumentationName)
|
||||||
|
{
|
||||||
|
// Following the fall-back process defined by:
|
||||||
|
// https://msdn.microsoft.com/en-us/library/sb6a8618.aspx#cpconpackagingdeployingresourcesanchor1
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var cultureName = culture.Name;
|
||||||
|
var cultureSpecificFileName =
|
||||||
|
Path.ChangeExtension(assemblyXmlDocumentationName, cultureName + ".xml");
|
||||||
|
|
||||||
|
// Look for a culture specific XML file next to the assembly.
|
||||||
|
yield return Path.Combine(assemblyDirectory, cultureSpecificFileName);
|
||||||
|
|
||||||
|
// Look for an XML file with the same name as the assembly in a culture specific directory.
|
||||||
|
yield return Path.Combine(assemblyDirectory, cultureName, assemblyXmlDocumentationName);
|
||||||
|
|
||||||
|
// Look for a culture specific XML file in a culture specific directory.
|
||||||
|
yield return Path.Combine(assemblyDirectory, cultureName, cultureSpecificFileName);
|
||||||
|
|
||||||
|
culture = culture.Parent;
|
||||||
|
} while (culture != null && culture != CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileInfo GetLocalizedXmlDocumentationFile(
|
||||||
|
CultureInfo culture,
|
||||||
|
string assemblyDirectory,
|
||||||
|
string assemblyXmlDocumentationName)
|
||||||
|
{
|
||||||
|
var localizedXmlPaths = ExpandPaths(culture, assemblyDirectory, assemblyXmlDocumentationName);
|
||||||
|
var xmlDocumentationFile = localizedXmlPaths
|
||||||
|
.Select(path => new FileInfo(path))
|
||||||
|
.FirstOrDefault(file => file.Exists);
|
||||||
|
|
||||||
|
return xmlDocumentationFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DocumentationDescriptor
|
||||||
|
{
|
||||||
|
public string Summary { get; set; }
|
||||||
|
public string Remarks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class used to store information about a <see cref="ITagHelper"/>'s execution lifetime.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperExecutionContext
|
||||||
|
{
|
||||||
|
private readonly List<ITagHelper> _tagHelpers;
|
||||||
|
private readonly Func<Task> _executeChildContentAsync;
|
||||||
|
private readonly Action _startTagHelperWritingScope;
|
||||||
|
private readonly Func<TagHelperContent> _endTagHelperWritingScope;
|
||||||
|
private TagHelperContent _childContent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only.
|
||||||
|
/// </summary>
|
||||||
|
internal TagHelperExecutionContext(string tagName, TagMode tagMode)
|
||||||
|
: this(tagName,
|
||||||
|
tagMode,
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: string.Empty,
|
||||||
|
executeChildContentAsync: async () => await Task.FromResult(result: true),
|
||||||
|
startTagHelperWritingScope: () => { },
|
||||||
|
endTagHelperWritingScope: () => new DefaultTagHelperContent())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperExecutionContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The HTML tag name in the Razor source.</param>
|
||||||
|
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||||
|
/// <param name="items">The collection of items used to communicate with other
|
||||||
|
/// <see cref="ITagHelper"/>s</param>
|
||||||
|
/// <param name="uniqueId">An identifier unique to the HTML element this context is for.</param>
|
||||||
|
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
|
||||||
|
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param>
|
||||||
|
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
|
||||||
|
public TagHelperExecutionContext(
|
||||||
|
string tagName,
|
||||||
|
TagMode tagMode,
|
||||||
|
IDictionary<object, object> items,
|
||||||
|
string uniqueId,
|
||||||
|
Func<Task> executeChildContentAsync,
|
||||||
|
Action startTagHelperWritingScope,
|
||||||
|
Func<TagHelperContent> endTagHelperWritingScope)
|
||||||
|
{
|
||||||
|
if (tagName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueId == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(uniqueId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executeChildContentAsync == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(executeChildContentAsync));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTagHelperWritingScope == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(startTagHelperWritingScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTagHelperWritingScope == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endTagHelperWritingScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tagHelpers = new List<ITagHelper>();
|
||||||
|
_executeChildContentAsync = executeChildContentAsync;
|
||||||
|
_startTagHelperWritingScope = startTagHelperWritingScope;
|
||||||
|
_endTagHelperWritingScope = endTagHelperWritingScope;
|
||||||
|
|
||||||
|
TagMode = tagMode;
|
||||||
|
HTMLAttributes = new TagHelperAttributeList();
|
||||||
|
AllAttributes = new TagHelperAttributeList();
|
||||||
|
TagName = tagName;
|
||||||
|
Items = items;
|
||||||
|
UniqueId = uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTML syntax of the element in the Razor source.
|
||||||
|
/// </summary>
|
||||||
|
public TagMode TagMode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if <see cref="GetChildContentAsync"/> has been called.
|
||||||
|
/// </summary>
|
||||||
|
public bool ChildContentRetrieved
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _childContent != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<object, object> Items { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperAttributeList HTMLAttributes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperAttributeList AllAttributes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An identifier unique to the HTML element this context is for.
|
||||||
|
/// </summary>
|
||||||
|
public string UniqueId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ITagHelper"/>s that should be run.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<ITagHelper> TagHelpers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _tagHelpers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML tag name in the Razor source.
|
||||||
|
/// </summary>
|
||||||
|
public string TagName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ITagHelper"/>s' output.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperOutput Output { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the given <paramref name="tagHelper"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagHelper">The tag helper to track.</param>
|
||||||
|
public void Add(ITagHelper tagHelper)
|
||||||
|
{
|
||||||
|
if (tagHelper == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tagHelpers.Add(tagHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the minimized HTML attribute in <see cref="AllAttributes"/> and <see cref="HTMLAttributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The minimized HTML attribute name.</param>
|
||||||
|
public void AddMinimizedHtmlAttribute(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLAttributes.Add(
|
||||||
|
new TagHelperAttribute
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Minimized = true
|
||||||
|
});
|
||||||
|
AllAttributes.Add(
|
||||||
|
new TagHelperAttribute
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Minimized = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the HTML attribute in <see cref="AllAttributes"/> and <see cref="HTMLAttributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The HTML attribute name.</param>
|
||||||
|
/// <param name="value">The HTML attribute value.</param>
|
||||||
|
public void AddHtmlAttribute(string name, object value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLAttributes.Add(name, value);
|
||||||
|
AllAttributes.Add(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the <see cref="ITagHelper"/> bound attribute in <see cref="AllAttributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The bound attribute name.</param>
|
||||||
|
/// <param name="value">The attribute value.</param>
|
||||||
|
public void AddTagHelperAttribute(string name, object value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
AllAttributes.Add(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the child content asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> which on completion executes all child content.</returns>
|
||||||
|
public Task ExecuteChildContentAsync()
|
||||||
|
{
|
||||||
|
return _executeChildContentAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute and retrieve the rendered child content asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered child content.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Child content is only executed once. Successive calls to this method or successive executions of the
|
||||||
|
/// returned <see cref="Task{TagHelperContent}"/> return a cached result.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||||
|
{
|
||||||
|
if (!useCachedResult || _childContent == null)
|
||||||
|
{
|
||||||
|
_startTagHelperWritingScope();
|
||||||
|
await _executeChildContentAsync();
|
||||||
|
_childContent = _endTagHelperWritingScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultTagHelperContent().SetContent(_childContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class used to run <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperRunner
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="executionContext">Contains information associated with running <see cref="ITagHelper"/>s.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
|
||||||
|
/// <paramref name="executionContext"/>'s <see cref="ITagHelper"/>s.</returns>
|
||||||
|
public async Task<TagHelperOutput> RunAsync(TagHelperExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
if (executionContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(executionContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagHelperContext = new TagHelperContext(
|
||||||
|
executionContext.AllAttributes,
|
||||||
|
executionContext.Items,
|
||||||
|
executionContext.UniqueId);
|
||||||
|
var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);
|
||||||
|
|
||||||
|
foreach (var tagHelper in orderedTagHelpers)
|
||||||
|
{
|
||||||
|
tagHelper.Init(tagHelperContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagHelperOutput = new TagHelperOutput(
|
||||||
|
executionContext.TagName,
|
||||||
|
executionContext.HTMLAttributes,
|
||||||
|
executionContext.GetChildContentAsync)
|
||||||
|
{
|
||||||
|
TagMode = executionContext.TagMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var tagHelper in orderedTagHelpers)
|
||||||
|
{
|
||||||
|
await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagHelperOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that manages <see cref="TagHelperExecutionContext"/> scopes.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperScopeManager
|
||||||
|
{
|
||||||
|
private readonly Stack<TagHelperExecutionContext> _executionScopes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperScopeManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperScopeManager()
|
||||||
|
{
|
||||||
|
_executionScopes = new Stack<TagHelperExecutionContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a <see cref="TagHelperExecutionContext"/> scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The HTML tag name that the scope is associated with.</param>
|
||||||
|
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||||
|
/// <param name="uniqueId">An identifier unique to the HTML element this scope is for.</param>
|
||||||
|
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
|
||||||
|
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param>
|
||||||
|
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
|
||||||
|
/// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns>
|
||||||
|
public TagHelperExecutionContext Begin(
|
||||||
|
string tagName,
|
||||||
|
TagMode tagMode,
|
||||||
|
string uniqueId,
|
||||||
|
Func<Task> executeChildContentAsync,
|
||||||
|
Action startTagHelperWritingScope,
|
||||||
|
Func<TagHelperContent> endTagHelperWritingScope)
|
||||||
|
{
|
||||||
|
if (tagName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueId == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(uniqueId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executeChildContentAsync == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(executeChildContentAsync));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTagHelperWritingScope == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(startTagHelperWritingScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTagHelperWritingScope == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endTagHelperWritingScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
IDictionary<object, object> items;
|
||||||
|
|
||||||
|
// If we're not wrapped by another TagHelper, then there will not be a parentExecutionContext.
|
||||||
|
if (_executionScopes.Count > 0)
|
||||||
|
{
|
||||||
|
items = new CopyOnWriteDictionary<object, object>(
|
||||||
|
_executionScopes.Peek().Items,
|
||||||
|
comparer: EqualityComparer<object>.Default);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
items = new Dictionary<object, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var executionContext = new TagHelperExecutionContext(
|
||||||
|
tagName,
|
||||||
|
tagMode,
|
||||||
|
items,
|
||||||
|
uniqueId,
|
||||||
|
executeChildContentAsync,
|
||||||
|
startTagHelperWritingScope,
|
||||||
|
endTagHelperWritingScope);
|
||||||
|
|
||||||
|
_executionScopes.Push(executionContext);
|
||||||
|
|
||||||
|
return executionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends a <see cref="TagHelperExecutionContext"/> scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>If the current scope is nested, the parent <see cref="TagHelperExecutionContext"/>.
|
||||||
|
/// <c>null</c> otherwise.</returns>
|
||||||
|
public TagHelperExecutionContext End()
|
||||||
|
{
|
||||||
|
if (_executionScopes.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatScopeManager_EndCannotBeCalledWithoutACallToBegin(
|
||||||
|
nameof(End),
|
||||||
|
nameof(Begin),
|
||||||
|
nameof(TagHelperScopeManager)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_executionScopes.Pop();
|
||||||
|
|
||||||
|
if (_executionScopes.Count != 0)
|
||||||
|
{
|
||||||
|
return _executionScopes.Peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that locates valid <see cref="ITagHelper"/>s within an assembly.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperTypeResolver
|
||||||
|
{
|
||||||
|
private static readonly ITypeInfo ITagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of an <see cref="Assembly"/> to search.</param>
|
||||||
|
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
|
||||||
|
/// <see cref="Parser.SyntaxTree.SyntaxTreeNode"/> responsible for the current <see cref="Resolve"/> call.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
|
||||||
|
/// <see cref="ITagHelper"/> types.</param>
|
||||||
|
/// <returns>An <see cref="IEnumerable{ITypeInfo}"/> of valid <see cref="ITagHelper"/> types.</returns>
|
||||||
|
public IEnumerable<ITypeInfo> Resolve(
|
||||||
|
string name,
|
||||||
|
SourceLocation documentLocation,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
if (errorSink == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorSink));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
|
||||||
|
errorSink.OnError(
|
||||||
|
documentLocation,
|
||||||
|
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
|
||||||
|
errorLength);
|
||||||
|
|
||||||
|
return Enumerable.Empty<ITypeInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblyName = new AssemblyName(name);
|
||||||
|
|
||||||
|
IEnumerable<ITypeInfo> libraryTypes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
libraryTypes = GetTopLevelExportedTypes(assemblyName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
documentLocation,
|
||||||
|
Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
|
||||||
|
assemblyName.Name,
|
||||||
|
ex.Message),
|
||||||
|
name.Length);
|
||||||
|
|
||||||
|
return Enumerable.Empty<ITypeInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryTypes.Where(IsTagHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all non-nested exported types from the given <paramref name="assemblyName"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="ITypeInfo"/>s from.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IEnumerable{ITypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
|
||||||
|
/// </returns>
|
||||||
|
protected virtual IEnumerable<ITypeInfo> GetTopLevelExportedTypes(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
if (assemblyName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assemblyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var exportedTypeInfos = GetExportedTypes(assemblyName);
|
||||||
|
|
||||||
|
return exportedTypeInfos
|
||||||
|
.Where(typeInfo => !typeInfo.IsNested)
|
||||||
|
.Select(typeInfo => new RuntimeTypeInfo(typeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all exported types from the given <paramref name="assemblyName"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="TypeInfo"/>s from.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IEnumerable{TypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
|
||||||
|
/// </returns>
|
||||||
|
protected virtual IEnumerable<TypeInfo> GetExportedTypes(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
var assembly = Assembly.Load(assemblyName);
|
||||||
|
|
||||||
|
return assembly.ExportedTypes.Select(type => type.GetTypeInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing.
|
||||||
|
internal virtual bool IsTagHelper(ITypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
return typeInfo.IsPublic &&
|
||||||
|
!typeInfo.IsAbstract &&
|
||||||
|
!typeInfo.IsGenericType &&
|
||||||
|
typeInfo.ImplementsInterface(ITagHelperTypeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#if !DOTNET5_4
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts summary and remarks XML documentation from an XML documentation file.
|
||||||
|
/// </summary>
|
||||||
|
public class XmlDocumentationProvider
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<XElement> _members;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="XmlDocumentationProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xmlFileLocation">Path to the XML documentation file to read.</param>
|
||||||
|
public XmlDocumentationProvider(string xmlFileLocation)
|
||||||
|
{
|
||||||
|
// XML file processing is defined by: https://msdn.microsoft.com/en-us/library/fsbx0t7x.aspx
|
||||||
|
var xmlDocumentation = XDocument.Load(xmlFileLocation);
|
||||||
|
var documentationRootMembers = xmlDocumentation.Root.Element("members");
|
||||||
|
_members = documentationRootMembers.Elements("member");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <c><summary></c> documentation for the given <paramref name="id"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The id to lookup.</param>
|
||||||
|
/// <returns><c><summary></c> documentation for the given <paramref name="id"/>.</returns>
|
||||||
|
public string GetSummary(string id)
|
||||||
|
{
|
||||||
|
var associatedMemeber = GetMember(id);
|
||||||
|
var summaryElement = associatedMemeber?.Element("summary");
|
||||||
|
|
||||||
|
if (summaryElement != null)
|
||||||
|
{
|
||||||
|
var summaryValue = GetElementValue(summaryElement);
|
||||||
|
|
||||||
|
return summaryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <c><remarks></c> documentation for the given <paramref name="id"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The id to lookup.</param>
|
||||||
|
/// <returns><c><remarks></c> documentation for the given <paramref name="id"/>.</returns>
|
||||||
|
public string GetRemarks(string id)
|
||||||
|
{
|
||||||
|
var associatedMemeber = GetMember(id);
|
||||||
|
var remarksElement = associatedMemeber?.Element("remarks");
|
||||||
|
|
||||||
|
if (remarksElement != null)
|
||||||
|
{
|
||||||
|
var remarksValue = GetElementValue(remarksElement);
|
||||||
|
|
||||||
|
return remarksValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the <see cref="string"/> identifier for the given <paramref name="type"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The <see cref="Type"/> to get the identifier for.</param>
|
||||||
|
/// <returns>The <see cref="string"/> identifier for the given <paramref name="type"/>.</returns>
|
||||||
|
public static string GetId(Type type)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"T:{type.FullName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to get the identifier for.</param>
|
||||||
|
/// <returns>The <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.</returns>
|
||||||
|
public static string GetId(PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(propertyInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
var declaringTypeInfo = propertyInfo.DeclaringType;
|
||||||
|
return $"P:{declaringTypeInfo.FullName}.{propertyInfo.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private XElement GetMember(string id)
|
||||||
|
{
|
||||||
|
var associatedMemeber = _members
|
||||||
|
.FirstOrDefault(element =>
|
||||||
|
string.Equals(element.Attribute("name").Value, id, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
return associatedMemeber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetElementValue(XElement element)
|
||||||
|
{
|
||||||
|
var stringBuilder = new StringBuilder();
|
||||||
|
var node = element.FirstNode;
|
||||||
|
|
||||||
|
while (node != null)
|
||||||
|
{
|
||||||
|
stringBuilder.Append(node.ToString(SaveOptions.DisableFormatting));
|
||||||
|
|
||||||
|
node = node.NextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.ToString().Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNet.Html.Abstractions;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default concrete <see cref="TagHelperContent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||||
|
public class DefaultTagHelperContent : TagHelperContent
|
||||||
|
{
|
||||||
|
private BufferedHtmlContent _buffer;
|
||||||
|
|
||||||
|
private BufferedHtmlContent Buffer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
_buffer = new BufferedHtmlContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IsModified => _buffer != null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>Returns <c>true</c> for a cleared <see cref="TagHelperContent"/>.</remarks>
|
||||||
|
public override bool IsWhiteSpace
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var writer = new EmptyOrWhiteSpaceWriter())
|
||||||
|
{
|
||||||
|
foreach (var entry in _buffer.Entries)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue = entry as string;
|
||||||
|
if (stringValue != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(stringValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
((IHtmlContent)entry).WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
if (!writer.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IsEmpty
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var writer = new EmptyOrWhiteSpaceWriter())
|
||||||
|
{
|
||||||
|
foreach (var entry in _buffer.Entries)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue = entry as string;
|
||||||
|
if (stringValue != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(stringValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
((IHtmlContent)entry).WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
if (!writer.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TagHelperContent Append(string unencoded)
|
||||||
|
{
|
||||||
|
Buffer.Append(unencoded);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TagHelperContent AppendHtml(string encoded)
|
||||||
|
{
|
||||||
|
Buffer.AppendHtml(encoded);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TagHelperContent Append(IHtmlContent htmlContent)
|
||||||
|
{
|
||||||
|
Buffer.Append(htmlContent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TagHelperContent Clear()
|
||||||
|
{
|
||||||
|
Buffer.Clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetContent()
|
||||||
|
{
|
||||||
|
return GetContent(HtmlEncoder.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetContent(IHtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
WriteTo(writer, encoder);
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer.WriteTo(writer, encoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
return GetContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides Write(string) to find if the content written is empty/whitespace.
|
||||||
|
private class EmptyOrWhiteSpaceWriter : TextWriter
|
||||||
|
{
|
||||||
|
public override Encoding Encoding
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty { get; private set; } = true;
|
||||||
|
|
||||||
|
public bool IsWhiteSpace { get; private set; } = true;
|
||||||
|
|
||||||
|
#if DOTNET5_4
|
||||||
|
// This is an abstract method in DNXCore
|
||||||
|
public override void Write(char value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public override void Write(string value)
|
||||||
|
{
|
||||||
|
if (IsEmpty && !string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
IsEmpty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWhiteSpace && !string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
IsWhiteSpace = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to override an <see cref="ITagHelper"/> property's HTML attribute name.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||||
|
public sealed class HtmlAttributeNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
private string _dictionaryAttributePrefix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class with <see cref="Name"/>
|
||||||
|
/// equal to <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Associated property must not have a public setter and must be compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public HtmlAttributeNameAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// HTML attribute name for the associated property. Must be <c>null</c> or empty if associated property does
|
||||||
|
/// not have a public setter and is compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>. Otherwise must not be <c>null</c> or empty.
|
||||||
|
/// </param>
|
||||||
|
public HtmlAttributeNameAttribute(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML attribute name of the associated property.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>null</c> or empty if and only if associated property does not have a public setter and is compatible
|
||||||
|
/// with <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </value>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefix used to match HTML attribute names. Matching attributes are added to the
|
||||||
|
/// associated property (an <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If non-<c>null</c> associated property must be compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// <para>
|
||||||
|
/// If associated property is compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, default value is <c>Name + "-"</c>.
|
||||||
|
/// <see cref="Name"/> must not be <c>null</c> or empty in this case.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Otherwise default value is <c>null</c>.
|
||||||
|
/// </para>
|
||||||
|
/// </value>
|
||||||
|
public string DictionaryAttributePrefix
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _dictionaryAttributePrefix;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dictionaryAttributePrefix = value;
|
||||||
|
DictionaryAttributePrefixSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an indication whether <see cref="DictionaryAttributePrefix"/> has been set. Used to distinguish an
|
||||||
|
/// uninitialized <see cref="DictionaryAttributePrefix"/> value from an explicit <c>null</c> setting.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if <see cref="DictionaryAttributePrefix"/> was set. <c>false</c> otherwise.</value>
|
||||||
|
public bool DictionaryAttributePrefixSet { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the associated <see cref="ITagHelper"/> property should not be bound to HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||||
|
public sealed class HtmlAttributeNotBoundAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNotBoundAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public HtmlAttributeNotBoundAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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.Razor.Compilation.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an <see cref="ITagHelper"/>'s target.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||||
|
public sealed class HtmlTargetElementAttribute : Attribute
|
||||||
|
{
|
||||||
|
public const string ElementCatchAllTarget = TagHelperDescriptorProvider.ElementCatchAllTarget;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlTargetElementAttribute"/> class that targets all HTML
|
||||||
|
/// elements with the required <see cref="Attributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks><see cref="Tag"/> is set to <c>*</c>.</remarks>
|
||||||
|
public HtmlTargetElementAttribute()
|
||||||
|
: this(ElementCatchAllTarget)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlTargetElementAttribute"/> class with the given
|
||||||
|
/// <paramref name="tag"/> as its <see cref="Tag"/> value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">
|
||||||
|
/// The HTML tag the <see cref="ITagHelper"/> targets.
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>A <c>*</c> <paramref name="tag"/> value indicates this <see cref="ITagHelper"/>
|
||||||
|
/// targets all HTML elements with the required <see cref="Attributes"/>.</remarks>
|
||||||
|
public HtmlTargetElementAttribute(string tag)
|
||||||
|
{
|
||||||
|
Tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML tag the <see cref="ITagHelper"/> targets. A <c>*</c> value indicates this <see cref="ITagHelper"/>
|
||||||
|
/// targets all HTML elements with the required <see cref="Attributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string Tag { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A comma-separated <see cref="string"/> of attribute names the HTML element must contain for the
|
||||||
|
/// <see cref="ITagHelper"/> to run. <c>*</c> at the end of an attribute name acts as a prefix match.
|
||||||
|
/// </summary>
|
||||||
|
public string Attributes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The expected tag structure. Defaults to <see cref="TagStructure.Unspecified"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If <see cref="TagStructure.Unspecified"/> and no other tag helpers applying to the same element specify
|
||||||
|
/// their <see cref="TagStructure"/> the <see cref="TagStructure.NormalOrSelfClosing"/> behavior is used:
|
||||||
|
/// <para>
|
||||||
|
/// <code>
|
||||||
|
/// <my-tag-helper></my-tag-helper>
|
||||||
|
/// <!-- OR -->
|
||||||
|
/// <my-tag-helper />
|
||||||
|
/// </code>
|
||||||
|
/// Otherwise, if another tag helper applying to the same element does specify their behavior, that behavior
|
||||||
|
/// is used.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If <see cref="TagStructure.WithoutEndTag"/> HTML elements can be written in the following formats:
|
||||||
|
/// <code>
|
||||||
|
/// <my-tag-helper>
|
||||||
|
/// <!-- OR -->
|
||||||
|
/// <my-tag-helper />
|
||||||
|
/// </code>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public TagStructure TagStructure { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The required HTML element name of the direct parent. A <c>null</c> value indicates any HTML element name is
|
||||||
|
/// allowed.
|
||||||
|
/// </summary>
|
||||||
|
public string ParentTag { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A read-only HTML tag helper attribute.
|
||||||
|
/// </summary>
|
||||||
|
public interface IReadOnlyTagHelperAttribute : IEquatable<IReadOnlyTagHelperAttribute>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
object Value { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an indication whether the attribute is minimized or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If <c>true</c>, <see cref="Value"/> will be ignored.</remarks>
|
||||||
|
bool Minimized { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contract used to filter matching HTML elements.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITagHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When a set of<see cref= "ITagHelper" /> s are executed, their<see cref="Init(TagHelperContext)"/>'s
|
||||||
|
/// are first invoked in the specified <see cref="Order"/>; then their
|
||||||
|
/// <see cref="ProcessAsync(TagHelperContext, TagHelperOutput)"/>'s are invoked in the specified
|
||||||
|
/// <see cref="Order"/>. Lower values are executed first.
|
||||||
|
/// </summary>
|
||||||
|
int Order { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the <see cref="ITagHelper"/> with the given <paramref name="context"/>. Additions to
|
||||||
|
/// <see cref="TagHelperContext.Items"/> should be done within this method to ensure they're added prior to
|
||||||
|
/// executing the children.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||||
|
/// <remarks>When more than one <see cref="ITagHelper"/> runs on the same element,
|
||||||
|
/// <see cref="TagHelperOutput.GetChildContentAsync"/> may be invoked prior to <see cref="ProcessAsync"/>.
|
||||||
|
/// </remarks>
|
||||||
|
void Init(TagHelperContext context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes the <see cref="ITagHelper"/> with the given <paramref name="context"/> and
|
||||||
|
/// <paramref name="output"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||||
|
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
|
||||||
|
Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a hint of the <see cref="ITagHelper"/>'s output element.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||||
|
public sealed class OutputElementHintAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="OutputElementHintAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputElement">
|
||||||
|
/// The HTML element the <see cref="ITagHelper"/> may output.
|
||||||
|
/// </param>
|
||||||
|
public OutputElementHintAttribute(string outputElement)
|
||||||
|
{
|
||||||
|
if (outputElement == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputElement = outputElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element the <see cref="ITagHelper"/> may output.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputElement { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,235 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A read-only collection of <typeparamref name="TAttribute"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAttribute">
|
||||||
|
/// The type of <see cref="IReadOnlyTagHelperAttribute"/>s in the collection.
|
||||||
|
/// </typeparam>
|
||||||
|
public class ReadOnlyTagHelperAttributeList<TAttribute> : IReadOnlyList<TAttribute>
|
||||||
|
where TAttribute : IReadOnlyTagHelperAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with an empty
|
||||||
|
/// collection.
|
||||||
|
/// </summary>
|
||||||
|
protected ReadOnlyTagHelperAttributeList()
|
||||||
|
{
|
||||||
|
Attributes = new List<TAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with the specified
|
||||||
|
/// <paramref name="attributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributes">The collection to wrap.</param>
|
||||||
|
public ReadOnlyTagHelperAttributeList(IEnumerable<TAttribute> attributes)
|
||||||
|
{
|
||||||
|
if (attributes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes = new List<TAttribute>(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The underlying collection of <typeparamref name="TAttribute"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Intended for use in a non-read-only subclass. Changes to this <see cref="List{TAttribute}"/> will
|
||||||
|
/// affect all getters that <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> provides.</remarks>
|
||||||
|
protected List<TAttribute> Attributes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TAttribute this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Attributes[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||||
|
/// matching <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the <typeparamref name="TAttribute"/> to get.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||||
|
/// matching <paramref name="name"/>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||||
|
public TAttribute this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.FirstOrDefault(attribute => NameEquals(name, attribute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Attributes.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
|
||||||
|
/// collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if an <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
|
||||||
|
/// collection; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
|
||||||
|
/// </remarks>
|
||||||
|
public bool Contains(TAttribute item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a <typeparamref name="TAttribute"/> with the same
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||||
|
/// <typeparamref name="TAttribute"/> to get.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if a <typeparamref name="TAttribute"/> with the same
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||||
|
public bool ContainsName(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.Any(attribute => NameEquals(name, attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches for a <typeparamref name="TAttribute"/> matching <paramref name="item"/> in the collection and
|
||||||
|
/// returns the zero-based index of the first occurrence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
|
||||||
|
/// <returns>The zero-based index of the first occurrence of a <typeparamref name="TAttribute"/> matching
|
||||||
|
/// <paramref name="item"/> in the collection, if found; otherwise, –1.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
|
||||||
|
/// </remarks>
|
||||||
|
public int IndexOf(TAttribute item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||||
|
/// matching <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||||
|
/// <typeparamref name="TAttribute"/> to get.</param>
|
||||||
|
/// <param name="attribute">When this method returns, the first <typeparamref name="TAttribute"/> with
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if found; otherwise,
|
||||||
|
/// <c>null</c>.</param>
|
||||||
|
/// <returns><c>true</c> if a <typeparamref name="TAttribute"/> with the same
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||||
|
public bool TryGetAttribute(string name, out TAttribute attribute)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute = Attributes.FirstOrDefault(attr => NameEquals(name, attr));
|
||||||
|
|
||||||
|
return attribute != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves <typeparamref name="TAttribute"/>s in the collection with
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||||
|
/// <typeparamref name="TAttribute"/>s to get.</param>
|
||||||
|
/// <param name="attributes">When this method returns, the <typeparamref name="TAttribute"/>s with
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if at least one is
|
||||||
|
/// found; otherwise, <c>null</c>.</param>
|
||||||
|
/// <returns><c>true</c> if at least one <typeparamref name="TAttribute"/> with the same
|
||||||
|
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||||
|
public bool TryGetAttributes(string name, out IEnumerable<TAttribute> attributes)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes = Attributes.Where(attribute => NameEquals(name, attribute));
|
||||||
|
|
||||||
|
return attributes.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<TAttribute> GetEnumerator()
|
||||||
|
{
|
||||||
|
return Attributes.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the specified <paramref name="attribute"/> has the same name as <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The value to compare against <paramref name="attribute"/>s
|
||||||
|
/// <see cref="TagHelperAttribute.Name"/>.</param>
|
||||||
|
/// <param name="attribute">The attribute to compare against.</param>
|
||||||
|
/// <returns><c>true</c> if <paramref name="name"/> case-insensitively matches <paramref name="attribute"/>s
|
||||||
|
/// <see cref="TagHelperAttribute.Name"/>.</returns>
|
||||||
|
protected static bool NameEquals(string name, TAttribute attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(name, attribute.Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Restricts children of the <see cref="ITagHelper"/>'s element.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Combining this attribute with a <see cref="HtmlTargetElementAttribute"/> that specifies its
|
||||||
|
/// <see cref="HtmlTargetElementAttribute.TagStructure"/> as <see cref="TagStructure.WithoutEndTag"/> will result
|
||||||
|
/// in this attribute being ignored.</remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public class RestrictChildrenAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="RestrictChildrenAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="childTag">
|
||||||
|
/// The tag name of an element allowed as a child.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="childTags">
|
||||||
|
/// Additional names of elements allowed as children.
|
||||||
|
/// </param>
|
||||||
|
public RestrictChildrenAttribute(string childTag, params string[] childTags)
|
||||||
|
{
|
||||||
|
var concatenatedNames = new string[1 + childTags.Length];
|
||||||
|
concatenatedNames[0] = childTag;
|
||||||
|
|
||||||
|
childTags.CopyTo(concatenatedNames, 1);
|
||||||
|
|
||||||
|
ChildTags = concatenatedNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the names of elements allowed as children.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> ChildTags { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class used to filter matching HTML elements.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class TagHelper : ITagHelper
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>Default order is <c>0</c>.</remarks>
|
||||||
|
public virtual int Order { get; } = 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void Init(TagHelperContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
|
||||||
|
/// <paramref name="output"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||||
|
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||||
|
public virtual void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
|
||||||
|
/// <paramref name="output"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||||
|
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
|
||||||
|
/// <remarks>By default this calls into <see cref="Process"/>.</remarks>.
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public virtual async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||||
|
{
|
||||||
|
Process(context, output);
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An HTML tag helper attribute.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperAttribute : IReadOnlyTagHelperAttribute
|
||||||
|
{
|
||||||
|
private static readonly int TypeHashCode = typeof(TagHelperAttribute).GetHashCode();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperAttribute" /> with values provided by the given
|
||||||
|
/// <paramref name="attribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attribute">A <see cref="IReadOnlyTagHelperAttribute"/> whose values should be copied.</param>
|
||||||
|
public TagHelperAttribute(IReadOnlyTagHelperAttribute attribute)
|
||||||
|
: this(attribute?.Name, attribute?.Value)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
Minimized = attribute.Minimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperAttribute"/> with the specified <paramref name="name"/>
|
||||||
|
/// and <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The <see cref="Name"/> of the attribute.</param>
|
||||||
|
/// <param name="value">The <see cref="Value"/> of the attribute.</param>
|
||||||
|
public TagHelperAttribute(string name, object value)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
public object Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an indication whether the attribute is minimized or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If <c>true</c>, <see cref="Value"/> will be ignored.</remarks>
|
||||||
|
public bool Minimized { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the specified <paramref name="value"/> into a <see cref="TagHelperAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="Value"/> of the created <see cref="TagHelperAttribute"/>.</param>
|
||||||
|
/// <remarks>Created <see cref="TagHelperAttribute"/>s <see cref="Name"/> is set to <c>null</c>.</remarks>
|
||||||
|
public static implicit operator TagHelperAttribute(string value)
|
||||||
|
{
|
||||||
|
return new TagHelperAttribute
|
||||||
|
{
|
||||||
|
Value = value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks><see cref="Name"/> is compared case-insensitively.</remarks>
|
||||||
|
public bool Equals(IReadOnlyTagHelperAttribute other)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
other != null &&
|
||||||
|
string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
Minimized == other.Minimized &&
|
||||||
|
(Minimized || Equals(Value, other.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as IReadOnlyTagHelperAttribute;
|
||||||
|
|
||||||
|
return Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return TypeHashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
// 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 Microsoft.AspNet.Razor.Runtime;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of <see cref="TagHelperAttribute"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperAttributeList : ReadOnlyTagHelperAttributeList<TagHelperAttribute>, IList<TagHelperAttribute>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with an empty collection.
|
||||||
|
/// </summary>
|
||||||
|
public TagHelperAttributeList()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with the specified
|
||||||
|
/// <paramref name="attributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributes">The collection to wrap.</param>
|
||||||
|
public TagHelperAttributeList(IEnumerable<TagHelperAttribute> attributes)
|
||||||
|
: base(attributes)
|
||||||
|
{
|
||||||
|
if (attributes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="value"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public new TagHelperAttribute this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return base[index];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||||
|
typeof(TagHelperAttribute).FullName,
|
||||||
|
nameof(TagHelperAttribute.Name)),
|
||||||
|
nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
|
||||||
|
/// <paramref name="name"/>. When setting, replaces the first matching
|
||||||
|
/// <see cref="TagHelperAttribute"/> with the specified <paramref name="value"/> and removes any additional
|
||||||
|
/// matching <see cref="TagHelperAttribute"/>s. If a matching <see cref="TagHelperAttribute"/> is not found,
|
||||||
|
/// adds the specified <paramref name="value"/> to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The <see cref="TagHelperAttribute.Name"/> of the <see cref="TagHelperAttribute"/> to get or set.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
|
||||||
|
/// <paramref name="name"/>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively. When setting,
|
||||||
|
/// <see cref="TagHelperAttribute"/>s <see cref="TagHelperAttribute.Name"/> must be <c>null</c> or
|
||||||
|
/// case-insensitively match the specified <paramref name="name"/>.</remarks>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var attributes = new TagHelperAttributeList();
|
||||||
|
///
|
||||||
|
/// // Will "value" be converted to a TagHelperAttribute with a null Name
|
||||||
|
/// attributes["name"] = "value";
|
||||||
|
///
|
||||||
|
/// // TagHelperAttribute.Name must match the specified name.
|
||||||
|
/// attributes["name"] = new TagHelperAttribute("name", "value");
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public new TagHelperAttribute this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base[name];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name will be null if user attempts to set the attribute via an implicit conversion:
|
||||||
|
// output.Attributes["someName"] = "someValue"
|
||||||
|
if (value.Name == null)
|
||||||
|
{
|
||||||
|
value.Name = name;
|
||||||
|
}
|
||||||
|
else if (!NameEquals(name, value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatTagHelperAttributeList_CannotAddAttribute(
|
||||||
|
nameof(TagHelperAttribute),
|
||||||
|
nameof(TagHelperAttribute.Name),
|
||||||
|
value.Name,
|
||||||
|
name),
|
||||||
|
nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeReplaced = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < Attributes.Count; i++)
|
||||||
|
{
|
||||||
|
if (NameEquals(name, Attributes[i]))
|
||||||
|
{
|
||||||
|
// We replace the first attribute with the provided value, remove all the rest.
|
||||||
|
if (!attributeReplaced)
|
||||||
|
{
|
||||||
|
// We replace the first attribute we find with the same name.
|
||||||
|
Attributes[i] = value;
|
||||||
|
attributeReplaced = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Attributes.RemoveAt(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't replace an attribute value we should add value to the end of the collection.
|
||||||
|
if (!attributeReplaced)
|
||||||
|
{
|
||||||
|
Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
bool ICollection<TagHelperAttribute>.IsReadOnly
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="TagHelperAttribute"/> to the end of the collection with the specified
|
||||||
|
/// <paramref name="name"/> and <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The <see cref="TagHelperAttribute.Name"/> of the attribute to add.</param>
|
||||||
|
/// <param name="value">The <see cref="TagHelperAttribute.Value"/> of the attribute to add.</param>
|
||||||
|
public void Add(string name, object value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.Add(new TagHelperAttribute(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public void Add(TagHelperAttribute attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.Name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||||
|
typeof(TagHelperAttribute).FullName,
|
||||||
|
nameof(TagHelperAttribute.Name)),
|
||||||
|
nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.Add(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public void Insert(int index, TagHelperAttribute attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.Name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||||
|
typeof(TagHelperAttribute).FullName,
|
||||||
|
nameof(TagHelperAttribute.Name)),
|
||||||
|
nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.Insert(index, attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(TagHelperAttribute[] array, int index)
|
||||||
|
{
|
||||||
|
if (array == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.CopyTo(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="attribute"/>s <see cref="TagHelperAttribute.Name"/> is compared case-insensitively.
|
||||||
|
/// </remarks>
|
||||||
|
public bool Remove(TagHelperAttribute attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.Remove(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
Attributes.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all <see cref="TagHelperAttribute"/>s with <see cref="TagHelperAttribute.Name"/> matching
|
||||||
|
/// <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The <see cref="TagHelperAttribute.Name"/> of <see cref="TagHelperAttribute"/>s to remove.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if at least 1 <see cref="TagHelperAttribute"/> was removed; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||||
|
public bool RemoveAll(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attributes.RemoveAll(attribute => NameEquals(name, attribute)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Attributes.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
// 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.IO;
|
||||||
|
using Microsoft.AspNet.Html.Abstractions;
|
||||||
|
using Microsoft.Extensions.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract class used to buffer content returned by <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class TagHelperContent : IHtmlContentBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the content was modifed.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool IsModified { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the content is empty.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool IsEmpty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the content is whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool IsWhiteSpace { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlContent">The <see cref="IHtmlContent"/> that replaces the content.</param>
|
||||||
|
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||||
|
public TagHelperContent SetContent(IHtmlContent htmlContent)
|
||||||
|
{
|
||||||
|
HtmlContentBuilderExtensions.SetContent(this, htmlContent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unencoded">
|
||||||
|
/// The <see cref="string"/> that replaces the content. The value is assume to be unencoded
|
||||||
|
/// as-provided and will be HTML encoded before being written.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||||
|
public TagHelperContent SetContent(string unencoded)
|
||||||
|
{
|
||||||
|
HtmlContentBuilderExtensions.SetContent(this, unencoded);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoded">
|
||||||
|
/// The <see cref="string"/> that replaces the content. The value is assume to be HTML encoded
|
||||||
|
/// as-provided and no further encoding will be performed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||||
|
public TagHelperContent SetHtmlContent(string encoded)
|
||||||
|
{
|
||||||
|
HtmlContentBuilderExtensions.SetHtmlContent(this, encoded);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends <paramref name="unencoded"/> to the existing content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unencoded">The <see cref="string"/> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||||
|
public abstract TagHelperContent Append(string unencoded);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends <paramref name="htmlContent"/> to the existing content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||||
|
public abstract TagHelperContent Append(IHtmlContent htmlContent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends <paramref name="encoded"/> to the existing content. <paramref name="encoded"/> is assumed
|
||||||
|
/// to be an HTML encoded <see cref="string"/> and no further encoding will be performed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoded">The <see cref="string"/> to be appended.</param>
|
||||||
|
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||||
|
public abstract TagHelperContent AppendHtml(string encoded);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the specified <paramref name="format"/> to the existing content after
|
||||||
|
/// replacing each format item with the HTML encoded <see cref="string"/> representation of the
|
||||||
|
/// corresponding item in the <paramref name="args"/> array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">
|
||||||
|
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">The object array to format.</param>
|
||||||
|
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||||
|
public TagHelperContent AppendFormat(string format, params object[] args)
|
||||||
|
{
|
||||||
|
HtmlContentBuilderExtensions.AppendFormat(this, null, format, args);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the specified <paramref name="format"/> to the existing content with information from the
|
||||||
|
/// <paramref name="provider"/> after replacing each format item with the HTML encoded <see cref="string"/>
|
||||||
|
/// representation of the corresponding item in the <paramref name="args"/> array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
|
||||||
|
/// <param name="format">
|
||||||
|
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">The object array to format.</param>
|
||||||
|
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||||
|
public TagHelperContent AppendFormat(IFormatProvider provider, string format, params object[] args)
|
||||||
|
{
|
||||||
|
HtmlContentBuilderExtensions.AppendFormat(this, provider, format, args);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A reference to this instance after the clear operation has completed.</returns>
|
||||||
|
public abstract TagHelperContent Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="string"/> containing the content.</returns>
|
||||||
|
public abstract string GetContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoder">The <see cref="IHtmlEncoder"/>.</param>
|
||||||
|
/// <returns>A <see cref="string"/> containing the content.</returns>
|
||||||
|
public abstract string GetContent(IHtmlEncoder encoder);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract void WriteTo(TextWriter writer, IHtmlEncoder encoder);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IHtmlContentBuilder IHtmlContentBuilder.Append(IHtmlContent content)
|
||||||
|
{
|
||||||
|
return Append(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IHtmlContentBuilder IHtmlContentBuilder.Append(string unencoded)
|
||||||
|
{
|
||||||
|
return Append(unencoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IHtmlContentBuilder IHtmlContentBuilder.AppendHtml(string encoded)
|
||||||
|
{
|
||||||
|
return AppendHtml(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IHtmlContentBuilder IHtmlContentBuilder.Clear()
|
||||||
|
{
|
||||||
|
return Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
// 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.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information related to the execution of <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
|
||||||
|
/// <param name="items">Collection of items used to communicate with other <see cref="ITagHelper"/>s.</param>
|
||||||
|
/// <param name="uniqueId">The unique identifier for the source element this <see cref="TagHelperContext" />
|
||||||
|
/// applies to.</param>
|
||||||
|
public TagHelperContext(
|
||||||
|
IEnumerable<IReadOnlyTagHelperAttribute> allAttributes,
|
||||||
|
IDictionary<object, object> items,
|
||||||
|
string uniqueId)
|
||||||
|
{
|
||||||
|
if (allAttributes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(allAttributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueId == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(uniqueId));
|
||||||
|
}
|
||||||
|
|
||||||
|
AllAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||||
|
allAttributes.Select(attribute => new TagHelperAttribute(attribute.Name, attribute.Value)));
|
||||||
|
Items = items;
|
||||||
|
UniqueId = uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Every attribute associated with the current HTML element.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> AllAttributes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This <see cref="IDictionary{object, object}"/> is copy-on-write in order to ensure items added to this
|
||||||
|
/// collection are visible only to other <see cref="ITagHelper"/>s targeting child elements.
|
||||||
|
/// </remarks>
|
||||||
|
public IDictionary<object, object> Items { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An identifier unique to the HTML element this context is for.
|
||||||
|
/// </summary>
|
||||||
|
public string UniqueId { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class used to represent the output of an <see cref="ITagHelper"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperOutput
|
||||||
|
{
|
||||||
|
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync;
|
||||||
|
|
||||||
|
// Internal for testing
|
||||||
|
internal TagHelperOutput(string tagName)
|
||||||
|
: this(
|
||||||
|
tagName,
|
||||||
|
new TagHelperAttributeList(),
|
||||||
|
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of <see cref="TagHelperOutput"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The HTML element's tag name.</param>
|
||||||
|
/// <param name="attributes">The HTML attributes.</param>
|
||||||
|
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
|
||||||
|
/// asynchronously.</param>
|
||||||
|
public TagHelperOutput(
|
||||||
|
string tagName,
|
||||||
|
TagHelperAttributeList attributes,
|
||||||
|
Func<bool, Task<TagHelperContent>> getChildContentAsync)
|
||||||
|
{
|
||||||
|
if (attributes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getChildContentAsync == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(getChildContentAsync));
|
||||||
|
}
|
||||||
|
|
||||||
|
TagName = tagName;
|
||||||
|
Attributes = new TagHelperAttributeList(attributes);
|
||||||
|
_getChildContentAsync = getChildContentAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element's tag name.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A whitespace or <c>null</c> value results in no start or end tag being rendered.
|
||||||
|
/// </remarks>
|
||||||
|
public string TagName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content that precedes the HTML element.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Value is rendered before the HTML element.</remarks>
|
||||||
|
public TagHelperContent PreElement { get; } = new DefaultTagHelperContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element's pre content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Value is prepended to the <see cref="ITagHelper"/>'s final output.</remarks>
|
||||||
|
public TagHelperContent PreContent { get; } = new DefaultTagHelperContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element's main content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Value occurs in the <see cref="ITagHelper"/>'s final output after <see cref="PreContent"/> and
|
||||||
|
/// before <see cref="PostContent"/></remarks>
|
||||||
|
public TagHelperContent Content { get; } = new DefaultTagHelperContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element's post content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Value is appended to the <see cref="ITagHelper"/>'s final output.</remarks>
|
||||||
|
public TagHelperContent PostContent { get; } = new DefaultTagHelperContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content that follows the HTML element.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Value is rendered after the HTML element.</remarks>
|
||||||
|
public TagHelperContent PostElement { get; } = new DefaultTagHelperContent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>true</c> if <see cref="Content"/> has been set, <c>false</c> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsContentModified
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Content.IsModified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Syntax of the element in the generated HTML.
|
||||||
|
/// </summary>
|
||||||
|
public TagMode TagMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML element's attributes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// MVC will HTML encode <see cref="string"/> values when generating the start tag. It will not HTML encode
|
||||||
|
/// a <c>Microsoft.AspNet.Mvc.Rendering.HtmlString</c> instance. MVC converts most other types to a
|
||||||
|
/// <see cref="string"/>, then HTML encodes the result.
|
||||||
|
/// </remarks>
|
||||||
|
public TagHelperAttributeList Attributes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes <see cref="TagHelperOutput"/> to generate nothing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Sets <see cref="TagName"/> to <c>null</c>, and clears <see cref="PreElement"/>, <see cref="PreContent"/>,
|
||||||
|
/// <see cref="Content"/>, <see cref="PostContent"/>, and <see cref="PostElement"/> to suppress output.
|
||||||
|
/// </remarks>
|
||||||
|
public void SuppressOutput()
|
||||||
|
{
|
||||||
|
TagName = null;
|
||||||
|
PreElement.Clear();
|
||||||
|
PreContent.Clear();
|
||||||
|
Content.Clear();
|
||||||
|
PostContent.Clear();
|
||||||
|
PostElement.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate used to execute children asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||||
|
/// <remarks>This method is memoized.</remarks>
|
||||||
|
public Task<TagHelperContent> GetChildContentAsync()
|
||||||
|
{
|
||||||
|
return GetChildContentAsync(useCachedResult: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate used to execute children asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="useCachedResult">If <c>true</c> multiple calls to this method will not cause re-execution
|
||||||
|
/// of child content; cached content will be returned.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||||
|
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||||
|
{
|
||||||
|
return _getChildContentAsync(useCachedResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"description": "Runtime components for rendering Razor pages.",
|
||||||
|
"version": "4.0.0-rc1-final",
|
||||||
|
"compilationOptions": {
|
||||||
|
"warningsAsErrors": true,
|
||||||
|
"keyFile": "../../tools/Key.snk"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/aspnet/razor"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Html.Abstractions": "1.0.0-rc1-final",
|
||||||
|
"Microsoft.AspNet.Razor.VSRC1": "4.0.0-rc1-final"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net451": {
|
||||||
|
"frameworkAssemblies": {
|
||||||
|
"System.Xml": "4.0.0.0",
|
||||||
|
"System.Xml.Linq": "4.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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.Razor.Chunks.Generators;
|
||||||
|
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||||
|
using Microsoft.AspNet.Razor.Parser;
|
||||||
|
#if NET451
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the C# Code Language for Razor
|
||||||
|
/// </summary>
|
||||||
|
public class CSharpRazorCodeLanguage : RazorCodeLanguage
|
||||||
|
{
|
||||||
|
private const string CSharpLanguageName = "csharp";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the name of the language: "csharp"
|
||||||
|
/// </summary>
|
||||||
|
public override string LanguageName
|
||||||
|
{
|
||||||
|
get { return CSharpLanguageName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of the code parser for this language
|
||||||
|
/// </summary>
|
||||||
|
public override ParserBase CreateCodeParser()
|
||||||
|
{
|
||||||
|
return new CSharpCodeParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of the chunk generator for this language with the specified settings
|
||||||
|
/// </summary>
|
||||||
|
public override RazorChunkGenerator CreateChunkGenerator(
|
||||||
|
string className,
|
||||||
|
string rootNamespaceName,
|
||||||
|
string sourceFileName,
|
||||||
|
RazorEngineHost host)
|
||||||
|
{
|
||||||
|
return new RazorChunkGenerator(className, rootNamespaceName, sourceFileName, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CodeGenerator CreateCodeGenerator(CodeGeneratorContext context)
|
||||||
|
{
|
||||||
|
return new CSharpCodeGenerator(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Chunk"/> used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class AddTagHelperChunk : Chunk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public string LookupText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class Chunk
|
||||||
|
{
|
||||||
|
public SourceLocation Start { get; set; }
|
||||||
|
public SyntaxTreeNode Association { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class ChunkTree
|
||||||
|
{
|
||||||
|
public ChunkTree()
|
||||||
|
{
|
||||||
|
Chunks = new List<Chunk>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<Chunk> Chunks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class ChunkTreeBuilder
|
||||||
|
{
|
||||||
|
private readonly Stack<ParentChunk> _parentStack;
|
||||||
|
private Chunk _lastChunk;
|
||||||
|
|
||||||
|
public ChunkTreeBuilder()
|
||||||
|
{
|
||||||
|
ChunkTree = new ChunkTree();
|
||||||
|
_parentStack = new Stack<ParentChunk>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkTree ChunkTree { get; private set; }
|
||||||
|
|
||||||
|
public void AddChunk(Chunk chunk, SyntaxTreeNode association, bool topLevel = false)
|
||||||
|
{
|
||||||
|
_lastChunk = chunk;
|
||||||
|
|
||||||
|
chunk.Start = association.Start;
|
||||||
|
chunk.Association = association;
|
||||||
|
|
||||||
|
// If we're not in the middle of a parent chunk
|
||||||
|
if (_parentStack.Count == 0 || topLevel == true)
|
||||||
|
{
|
||||||
|
ChunkTree.Chunks.Add(chunk);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_parentStack.Peek().Children.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTagHelperPrefixDirectiveChunk(string prefix, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(
|
||||||
|
new TagHelperPrefixDirectiveChunk
|
||||||
|
{
|
||||||
|
Prefix = prefix
|
||||||
|
},
|
||||||
|
association,
|
||||||
|
topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAddTagHelperChunk(string lookupText, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new AddTagHelperChunk
|
||||||
|
{
|
||||||
|
LookupText = lookupText
|
||||||
|
}, association, topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRemoveTagHelperChunk(string lookupText, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new RemoveTagHelperChunk
|
||||||
|
{
|
||||||
|
LookupText = lookupText
|
||||||
|
}, association, topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLiteralChunk(string literal, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
// If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.
|
||||||
|
var literalChunk = _lastChunk as LiteralChunk;
|
||||||
|
if (literalChunk != null)
|
||||||
|
{
|
||||||
|
// Literal chunks are always associated with Spans
|
||||||
|
var lastSpan = (Span)literalChunk.Association;
|
||||||
|
var currentSpan = (Span)association;
|
||||||
|
|
||||||
|
var builder = new SpanBuilder(lastSpan);
|
||||||
|
foreach (var symbol in currentSpan.Symbols)
|
||||||
|
{
|
||||||
|
builder.Accept(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
literalChunk.Association = builder.Build();
|
||||||
|
literalChunk.Text += literal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddChunk(new LiteralChunk
|
||||||
|
{
|
||||||
|
Text = literal,
|
||||||
|
}, association);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddExpressionChunk(string expression, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new ExpressionChunk
|
||||||
|
{
|
||||||
|
Code = expression
|
||||||
|
}, association);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddStatementChunk(string code, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new StatementChunk
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
}, association);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddUsingChunk(string usingNamespace, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new UsingChunk
|
||||||
|
{
|
||||||
|
Namespace = usingNamespace,
|
||||||
|
}, association, topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTypeMemberChunk(string code, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new TypeMemberChunk
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
}, association, topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLiteralCodeAttributeChunk(string code, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new LiteralCodeAttributeChunk
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
}, association);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSetBaseTypeChunk(string typeName, SyntaxTreeNode association)
|
||||||
|
{
|
||||||
|
AddChunk(new SetBaseTypeChunk
|
||||||
|
{
|
||||||
|
TypeName = typeName.Trim()
|
||||||
|
}, association, topLevel: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T StartParentChunk<T>(SyntaxTreeNode association) where T : ParentChunk, new()
|
||||||
|
{
|
||||||
|
return StartParentChunk<T>(association, topLevel: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T StartParentChunk<T>(SyntaxTreeNode association, bool topLevel) where T : ParentChunk, new()
|
||||||
|
{
|
||||||
|
var parentChunk = new T();
|
||||||
|
|
||||||
|
return StartParentChunk<T>(parentChunk, association, topLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T StartParentChunk<T>(T parentChunk, SyntaxTreeNode association, bool topLevel) where T : ParentChunk
|
||||||
|
{
|
||||||
|
AddChunk(parentChunk, association, topLevel);
|
||||||
|
|
||||||
|
_parentStack.Push(parentChunk);
|
||||||
|
|
||||||
|
return parentChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndParentChunk()
|
||||||
|
{
|
||||||
|
_lastChunk = _parentStack.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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.Razor.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class CodeAttributeChunk : ParentChunk
|
||||||
|
{
|
||||||
|
public string Attribute { get; set; }
|
||||||
|
public LocationTagged<string> Prefix { get; set; }
|
||||||
|
public LocationTagged<string> Suffix { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +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 Microsoft.AspNet.Razor.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class DynamicCodeAttributeChunk : ParentChunk
|
||||||
|
{
|
||||||
|
public LocationTagged<string> Prefix { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class ExpressionBlockChunk : ParentChunk
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class ExpressionChunk : Chunk
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Start + " = " + Code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class AddImportChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public AddImportChunkGenerator(string ns)
|
||||||
|
{
|
||||||
|
Namespace = ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Namespace { get; }
|
||||||
|
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var ns = Namespace;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
|
||||||
|
{
|
||||||
|
ns = ns.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ChunkTreeBuilder.AddUsingChunk(ns, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Import:" + Namespace + ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as AddImportChunkGenerator;
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(Namespace, other.Namespace, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// Hash code should include only immutable properties.
|
||||||
|
return Namespace == null ? 0 : StringComparer.Ordinal.GetHashCode(Namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="SpanChunkGenerator"/> responsible for generating <see cref="AddTagHelperChunk"/>s and
|
||||||
|
/// <see cref="RemoveTagHelperChunk"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class AddOrRemoveTagHelperChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lookupText">
|
||||||
|
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added or removed.
|
||||||
|
/// </param>
|
||||||
|
public AddOrRemoveTagHelperChunkGenerator(bool removeTagHelperDescriptors, string lookupText)
|
||||||
|
{
|
||||||
|
RemoveTagHelperDescriptors = removeTagHelperDescriptors;
|
||||||
|
LookupText = lookupText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added to or
|
||||||
|
/// removed from the Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public string LookupText { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we want to remove <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s from the Razor page.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If <c>true</c> <see cref="GenerateChunk"/> generates <see cref="AddTagHelperChunk"/>s,
|
||||||
|
/// <see cref="RemoveTagHelperChunk"/>s otherwise.</remarks>
|
||||||
|
public bool RemoveTagHelperDescriptors { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates <see cref="AddTagHelperChunk"/>s if <see cref="RemoveTagHelperDescriptors"/> is
|
||||||
|
/// <c>true</c>, otherwise <see cref="RemoveTagHelperChunk"/>s are generated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">
|
||||||
|
/// The <see cref="Span"/> responsible for this <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current chunk generation process.</param>
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
if (RemoveTagHelperDescriptors)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class AttributeBlockChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
public AttributeBlockChunkGenerator(string name, LocationTagged<string> prefix, LocationTagged<string> suffix)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Prefix = prefix;
|
||||||
|
Suffix = suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public LocationTagged<string> Prefix { get; }
|
||||||
|
|
||||||
|
public LocationTagged<string> Suffix { get; }
|
||||||
|
|
||||||
|
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var chunk = context.ChunkTreeBuilder.StartParentChunk<CodeAttributeChunk>(target);
|
||||||
|
|
||||||
|
chunk.Attribute = Name;
|
||||||
|
chunk.Prefix = Prefix;
|
||||||
|
chunk.Suffix = Suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as AttributeBlockChunkGenerator;
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(other.Name, Name, StringComparison.Ordinal) &&
|
||||||
|
Equals(other.Prefix, Prefix) &&
|
||||||
|
Equals(other.Suffix, Suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
hashCodeCombiner.Add(Name, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(Prefix);
|
||||||
|
hashCodeCombiner.Add(Suffix);
|
||||||
|
|
||||||
|
return hashCodeCombiner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class ChunkGeneratorContext
|
||||||
|
{
|
||||||
|
protected ChunkGeneratorContext(ChunkGeneratorContext context)
|
||||||
|
: this(
|
||||||
|
context.Host,
|
||||||
|
context.ClassName,
|
||||||
|
context.RootNamespace,
|
||||||
|
context.SourceFile,
|
||||||
|
// True because we're pulling from the provided context's source file.
|
||||||
|
shouldGenerateLinePragmas: true)
|
||||||
|
{
|
||||||
|
ChunkTreeBuilder = context.ChunkTreeBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGeneratorContext(
|
||||||
|
RazorEngineHost host,
|
||||||
|
string className,
|
||||||
|
string rootNamespace,
|
||||||
|
string sourceFile,
|
||||||
|
bool shouldGenerateLinePragmas)
|
||||||
|
{
|
||||||
|
ChunkTreeBuilder = new ChunkTreeBuilder();
|
||||||
|
Host = host;
|
||||||
|
SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
|
||||||
|
RootNamespace = rootNamespace;
|
||||||
|
ClassName = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SourceFile { get; internal set; }
|
||||||
|
|
||||||
|
public string RootNamespace { get; }
|
||||||
|
|
||||||
|
public string ClassName { get; }
|
||||||
|
|
||||||
|
public RazorEngineHost Host { get; }
|
||||||
|
|
||||||
|
public ChunkTreeBuilder ChunkTreeBuilder { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, int offset, int line, int col)
|
||||||
|
: this(prefix, new SourceLocation(offset, line, col))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, SourceLocation valueStart)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
|
ValueStart = valueStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocationTagged<string> Prefix { get; }
|
||||||
|
|
||||||
|
public SourceLocation ValueStart { get; }
|
||||||
|
|
||||||
|
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var chunk = context.ChunkTreeBuilder.StartParentChunk<DynamicCodeAttributeChunk>(target);
|
||||||
|
chunk.Start = ValueStart;
|
||||||
|
chunk.Prefix = Prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as DynamicAttributeBlockChunkGenerator;
|
||||||
|
return other != null &&
|
||||||
|
Equals(other.Prefix, Prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Prefix == null ? 0 : Prefix.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
|
||||||
|
{
|
||||||
|
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
|
||||||
|
|
||||||
|
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.StartParentChunk<ExpressionBlockChunk>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Expr";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj != null &&
|
||||||
|
GetType() == obj.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return TypeHashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public interface IParentChunkGenerator
|
||||||
|
{
|
||||||
|
void GenerateStartParentChunk(Block target, ChunkGeneratorContext context);
|
||||||
|
void GenerateEndParentChunk(Block target, ChunkGeneratorContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +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 Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public interface ISpanChunkGenerator
|
||||||
|
{
|
||||||
|
void GenerateChunk(Span target, ChunkGeneratorContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class LiteralAttributeChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public LiteralAttributeChunkGenerator(
|
||||||
|
LocationTagged<string> prefix,
|
||||||
|
LocationTagged<SpanChunkGenerator> valueGenerator)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
|
ValueGenerator = valueGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiteralAttributeChunkGenerator(LocationTagged<string> prefix, LocationTagged<string> value)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocationTagged<string> Prefix { get; }
|
||||||
|
|
||||||
|
public LocationTagged<string> Value { get; }
|
||||||
|
|
||||||
|
public LocationTagged<SpanChunkGenerator> ValueGenerator { get; }
|
||||||
|
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var chunk = context.ChunkTreeBuilder.StartParentChunk<LiteralCodeAttributeChunk>(target);
|
||||||
|
chunk.Prefix = Prefix;
|
||||||
|
chunk.Value = Value;
|
||||||
|
|
||||||
|
if (ValueGenerator != null)
|
||||||
|
{
|
||||||
|
chunk.ValueLocation = ValueGenerator.Location;
|
||||||
|
|
||||||
|
ValueGenerator.Value.GenerateChunk(target, context);
|
||||||
|
|
||||||
|
chunk.ValueLocation = ValueGenerator.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (ValueGenerator == null)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},{1:F}", Prefix, Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},<Sub:{1:F}>", Prefix, ValueGenerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as LiteralAttributeChunkGenerator;
|
||||||
|
return other != null &&
|
||||||
|
Equals(other.Prefix, Prefix) &&
|
||||||
|
Equals(other.Value, Value) &&
|
||||||
|
Equals(other.ValueGenerator, ValueGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
|
||||||
|
hashCodeCombiner.Add(Prefix);
|
||||||
|
hashCodeCombiner.Add(Value);
|
||||||
|
hashCodeCombiner.Add(ValueGenerator);
|
||||||
|
|
||||||
|
return hashCodeCombiner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class MarkupChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddLiteralChunk(target.Content, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Markup";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.Diagnostics.CodeAnalysis;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public abstract class ParentChunkGenerator : IParentChunkGenerator
|
||||||
|
{
|
||||||
|
private static readonly int TypeHashCode = typeof(ParentChunkGenerator).GetHashCode();
|
||||||
|
|
||||||
|
[SuppressMessage(
|
||||||
|
"Microsoft.Security",
|
||||||
|
"CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
|
||||||
|
Justification = "This class has no instance state")]
|
||||||
|
public static readonly IParentChunkGenerator Null = new NullParentChunkGenerator();
|
||||||
|
|
||||||
|
public virtual void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj != null &&
|
||||||
|
GetType() == obj.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return TypeHashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullParentChunkGenerator : IParentChunkGenerator
|
||||||
|
{
|
||||||
|
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// 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.Razor.Parser;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class RazorChunkGenerator : ParserVisitor
|
||||||
|
{
|
||||||
|
private ChunkGeneratorContext _context;
|
||||||
|
|
||||||
|
public RazorChunkGenerator(
|
||||||
|
string className,
|
||||||
|
string rootNamespaceName,
|
||||||
|
string sourceFileName,
|
||||||
|
RazorEngineHost host)
|
||||||
|
{
|
||||||
|
if (rootNamespaceName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(rootNamespaceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(host));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(className))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, nameof(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassName = className;
|
||||||
|
RootNamespaceName = rootNamespaceName;
|
||||||
|
SourceFileName = sourceFileName;
|
||||||
|
GenerateLinePragmas = string.IsNullOrEmpty(SourceFileName) ? false : true;
|
||||||
|
Host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data pulled from constructor
|
||||||
|
public string ClassName { get; private set; }
|
||||||
|
public string RootNamespaceName { get; private set; }
|
||||||
|
public string SourceFileName { get; private set; }
|
||||||
|
public RazorEngineHost Host { get; private set; }
|
||||||
|
|
||||||
|
// Generation settings
|
||||||
|
public bool GenerateLinePragmas { get; set; }
|
||||||
|
public bool DesignTimeMode { get; set; }
|
||||||
|
|
||||||
|
public ChunkGeneratorContext Context
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
EnsureContextInitialized();
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitStartBlock(Block block)
|
||||||
|
{
|
||||||
|
block.ChunkGenerator.GenerateStartParentChunk(block, Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitEndBlock(Block block)
|
||||||
|
{
|
||||||
|
block.ChunkGenerator.GenerateEndParentChunk(block, Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitSpan(Span span)
|
||||||
|
{
|
||||||
|
span.ChunkGenerator.GenerateChunk(span, Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureContextInitialized()
|
||||||
|
{
|
||||||
|
if (_context == null)
|
||||||
|
{
|
||||||
|
_context = new ChunkGeneratorContext(Host,
|
||||||
|
ClassName,
|
||||||
|
RootNamespaceName,
|
||||||
|
SourceFileName,
|
||||||
|
GenerateLinePragmas);
|
||||||
|
Initialize(_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Initialize(ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class RazorCommentChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class SectionChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
public SectionChunkGenerator(string sectionName)
|
||||||
|
{
|
||||||
|
SectionName = sectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SectionName { get; }
|
||||||
|
|
||||||
|
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var chunk = context.ChunkTreeBuilder.StartParentChunk<SectionChunk>(target);
|
||||||
|
|
||||||
|
chunk.Name = SectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as SectionChunkGenerator;
|
||||||
|
return base.Equals(other) &&
|
||||||
|
string.Equals(SectionName, other.SectionName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return SectionName == null ? 0 : StringComparer.Ordinal.GetHashCode(SectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Section:" + SectionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class SetBaseTypeChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public SetBaseTypeChunkGenerator(string baseType)
|
||||||
|
{
|
||||||
|
BaseType = baseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BaseType { get; }
|
||||||
|
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddSetBaseTypeChunk(BaseType, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Base:" + BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as SetBaseTypeChunkGenerator;
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(BaseType, other.BaseType, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return BaseType.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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.Diagnostics.CodeAnalysis;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public abstract class SpanChunkGenerator : ISpanChunkGenerator
|
||||||
|
{
|
||||||
|
private static readonly int TypeHashCode = typeof(SpanChunkGenerator).GetHashCode();
|
||||||
|
|
||||||
|
[SuppressMessage(
|
||||||
|
"Microsoft.Security",
|
||||||
|
"CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
|
||||||
|
Justification = "This class has no instance state")]
|
||||||
|
public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator();
|
||||||
|
|
||||||
|
public virtual void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj != null &&
|
||||||
|
GetType() == obj.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return TypeHashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullSpanChunkGenerator : ISpanChunkGenerator
|
||||||
|
{
|
||||||
|
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class StatementChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddStatementChunk(target.Content, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Stmt";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ParentChunkGenerator"/> that is responsible for generating valid <see cref="TagHelperChunk"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
private IEnumerable<TagHelperDescriptor> _tagHelperDescriptors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperChunkGenerator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagHelperDescriptors">
|
||||||
|
/// <see cref="TagHelperDescriptor"/>s associated with the current HTML tag.
|
||||||
|
/// </param>
|
||||||
|
public TagHelperChunkGenerator(IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
_tagHelperDescriptors = tagHelperDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the generation of a <see cref="TagHelperChunk"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">
|
||||||
|
/// The <see cref="Block"/> responsible for this <see cref="TagHelperChunkGenerator"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current chunk generation process.</param>
|
||||||
|
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
var tagHelperBlock = target as TagHelperBlock;
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
tagHelperBlock != null,
|
||||||
|
$"A {nameof(TagHelperChunkGenerator)} must only be used with {nameof(TagHelperBlock)}s.");
|
||||||
|
|
||||||
|
var attributes = new List<KeyValuePair<string, Chunk>>();
|
||||||
|
|
||||||
|
// We need to create a chunk generator to create chunks for each of the attributes.
|
||||||
|
var chunkGenerator = context.Host.CreateChunkGenerator(
|
||||||
|
context.ClassName,
|
||||||
|
context.RootNamespace,
|
||||||
|
context.SourceFile);
|
||||||
|
|
||||||
|
foreach (var attribute in tagHelperBlock.Attributes)
|
||||||
|
{
|
||||||
|
ParentChunk attributeChunkValue = null;
|
||||||
|
|
||||||
|
if (attribute.Value != null)
|
||||||
|
{
|
||||||
|
// Populates the chunk tree with chunks associated with attributes
|
||||||
|
attribute.Value.Accept(chunkGenerator);
|
||||||
|
|
||||||
|
var chunks = chunkGenerator.Context.ChunkTreeBuilder.ChunkTree.Chunks;
|
||||||
|
var first = chunks.FirstOrDefault();
|
||||||
|
|
||||||
|
attributeChunkValue = new ParentChunk
|
||||||
|
{
|
||||||
|
Association = first?.Association,
|
||||||
|
Children = chunks,
|
||||||
|
Start = first == null ? SourceLocation.Zero : first.Start
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.Add(new KeyValuePair<string, Chunk>(attribute.Key, attributeChunkValue));
|
||||||
|
|
||||||
|
// Reset the chunk tree builder so we can build a new one for the next attribute
|
||||||
|
chunkGenerator.Context.ChunkTreeBuilder = new ChunkTreeBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
var unprefixedTagName = tagHelperBlock.TagName.Substring(_tagHelperDescriptors.First().Prefix.Length);
|
||||||
|
|
||||||
|
context.ChunkTreeBuilder.StartParentChunk(
|
||||||
|
new TagHelperChunk(
|
||||||
|
unprefixedTagName,
|
||||||
|
tagHelperBlock.TagMode,
|
||||||
|
attributes,
|
||||||
|
_tagHelperDescriptors),
|
||||||
|
target,
|
||||||
|
topLevel: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the generation of a <see cref="TagHelperChunk"/> capturing all previously visited children
|
||||||
|
/// since the <see cref="GenerateStartParentChunk"/> method was called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">
|
||||||
|
/// The <see cref="Block"/> responsible for this <see cref="TagHelperChunkGenerator"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current chunk generation process.</param>
|
||||||
|
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="SpanChunkGenerator"/> responsible for generating
|
||||||
|
/// <see cref="TagHelperPrefixDirectiveChunk"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperPrefixDirectiveChunkGenerator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefix">
|
||||||
|
/// Text used as a required prefix when matching HTML.
|
||||||
|
/// </param>
|
||||||
|
public TagHelperPrefixDirectiveChunkGenerator(string prefix)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text used as a required prefix when matching HTML.
|
||||||
|
/// </summary>
|
||||||
|
public string Prefix { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates <see cref="TagHelperPrefixDirectiveChunk"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">
|
||||||
|
/// The <see cref="Span"/> responsible for this <see cref="TagHelperPrefixDirectiveChunkGenerator"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current chunk generation process.</param>
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class TemplateBlockChunkGenerator : ParentChunkGenerator
|
||||||
|
{
|
||||||
|
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.StartParentChunk<TemplateChunk>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.EndParentChunk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||||
|
{
|
||||||
|
public class TypeMemberChunkGenerator : SpanChunkGenerator
|
||||||
|
{
|
||||||
|
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
context.ChunkTreeBuilder.AddTypeMemberChunk(target.Content, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "TypeMember";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class LiteralChunk : Chunk
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Start + " = " + Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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.Razor.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class LiteralCodeAttributeChunk : ParentChunk
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
public LocationTagged<string> Prefix { get; set; }
|
||||||
|
public LocationTagged<string> Value { get; set; }
|
||||||
|
public SourceLocation ValueLocation { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class ParentChunk : Chunk
|
||||||
|
{
|
||||||
|
public ParentChunk()
|
||||||
|
{
|
||||||
|
Children = new List<Chunk>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<Chunk> Children { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Chunk"/> used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be ignored
|
||||||
|
/// within the Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public class RemoveTagHelperChunk : Chunk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be ignored within the Razor
|
||||||
|
/// page.
|
||||||
|
/// </summary>
|
||||||
|
public string LookupText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class SectionChunk : ParentChunk
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class SetBaseTypeChunk : Chunk
|
||||||
|
{
|
||||||
|
public string TypeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class StatementChunk : Chunk
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Start + " = " + Code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ParentChunk"/> that represents a special HTML tag.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperChunk : ParentChunk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperChunk"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The tag name associated with the tag helpers HTML element.</param>
|
||||||
|
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||||
|
/// <param name="attributes">The attributes associated with the tag helpers HTML element.</param>
|
||||||
|
/// <param name="descriptors">
|
||||||
|
/// The <see cref="TagHelperDescriptor"/>s associated with this tag helpers HTML element.
|
||||||
|
/// </param>
|
||||||
|
public TagHelperChunk(
|
||||||
|
string tagName,
|
||||||
|
TagMode tagMode,
|
||||||
|
IList<KeyValuePair<string, Chunk>> attributes,
|
||||||
|
IEnumerable<TagHelperDescriptor> descriptors)
|
||||||
|
{
|
||||||
|
TagName = tagName;
|
||||||
|
TagMode = tagMode;
|
||||||
|
Attributes = attributes;
|
||||||
|
Descriptors = descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These attributes are <see cref="string"/> => <see cref="Chunk"/> so attribute values can consist
|
||||||
|
/// of all sorts of Razor specific pieces.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<KeyValuePair<string, Chunk>> Attributes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="TagHelperDescriptor"/>s that are associated with the tag helpers HTML element.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<TagHelperDescriptor> Descriptors { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML tag name.
|
||||||
|
/// </summary>
|
||||||
|
public string TagName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTML syntax of the element in the Razor source.
|
||||||
|
/// </summary>
|
||||||
|
public TagMode TagMode { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Chunk"/> for the <c>@tagHelperPrefix</c> directive.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperPrefixDirectiveChunk : Chunk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
|
||||||
|
/// tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class TemplateChunk : ParentChunk
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class TypeMemberChunk : Chunk
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Chunks
|
||||||
|
{
|
||||||
|
public class UsingChunk : Chunk
|
||||||
|
{
|
||||||
|
public string Namespace { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Razor.Chunks;
|
||||||
|
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CSharpCodeGenerator : CodeGenerator
|
||||||
|
{
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/system.codedom.codechecksumpragma.checksumalgorithmid.aspx
|
||||||
|
private const string Sha1AlgorithmId = "{ff1816ec-aa5e-4d10-87f7-6f4963833460}";
|
||||||
|
private const int DisableAsyncWarning = 1998;
|
||||||
|
|
||||||
|
public CSharpCodeGenerator(CodeGeneratorContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkTree Tree { get { return Context.ChunkTreeBuilder.ChunkTree; } }
|
||||||
|
public RazorEngineHost Host { get { return Context.Host; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected for testing.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of <see cref="CSharpCodeWriter"/>.</returns>
|
||||||
|
protected virtual CSharpCodeWriter CreateCodeWriter()
|
||||||
|
{
|
||||||
|
return new CSharpCodeWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CodeGeneratorResult Generate()
|
||||||
|
{
|
||||||
|
var writer = CreateCodeWriter();
|
||||||
|
|
||||||
|
if (!Host.DesignTimeMode && !string.IsNullOrEmpty(Context.Checksum))
|
||||||
|
{
|
||||||
|
writer.Write("#pragma checksum \"")
|
||||||
|
.Write(Context.SourceFile)
|
||||||
|
.Write("\" \"")
|
||||||
|
.Write(Sha1AlgorithmId)
|
||||||
|
.Write("\" \"")
|
||||||
|
.Write(Context.Checksum)
|
||||||
|
.WriteLine("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (writer.BuildNamespace(Context.RootNamespace))
|
||||||
|
{
|
||||||
|
// Write out using directives
|
||||||
|
AddImports(Tree, writer, Host.NamespaceImports);
|
||||||
|
// Separate the usings and the class
|
||||||
|
writer.WriteLine();
|
||||||
|
|
||||||
|
using (BuildClassDeclaration(writer))
|
||||||
|
{
|
||||||
|
if (Host.DesignTimeMode)
|
||||||
|
{
|
||||||
|
writer.WriteLine("private static object @__o;");
|
||||||
|
}
|
||||||
|
|
||||||
|
var csharpCodeVisitor = CreateCSharpCodeVisitor(writer, Context);
|
||||||
|
|
||||||
|
new CSharpTypeMemberVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
|
||||||
|
CreateCSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, Context)
|
||||||
|
.AcceptTree(Tree);
|
||||||
|
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||||
|
|
||||||
|
BuildConstructor(writer);
|
||||||
|
|
||||||
|
// Add space in-between constructor and method body
|
||||||
|
writer.WriteLine();
|
||||||
|
|
||||||
|
using (writer.BuildDisableWarningScope(DisableAsyncWarning))
|
||||||
|
{
|
||||||
|
using (writer.BuildMethodDeclaration("public override async", "Task", Host.GeneratedClassContext.ExecuteMethodName))
|
||||||
|
{
|
||||||
|
new CSharpTagHelperRunnerInitializationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||||
|
csharpCodeVisitor.Accept(Tree.Chunks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CodeGeneratorResult(writer.GenerateCode(), writer.LineMappingManager.Mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual CSharpCodeVisitor CreateCSharpCodeVisitor(
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
CodeGeneratorContext context)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CSharpCodeVisitor(writer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual CSharpDesignTimeCodeVisitor CreateCSharpDesignTimeCodeVisitor(
|
||||||
|
CSharpCodeVisitor csharpCodeVisitor,
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
CodeGeneratorContext context)
|
||||||
|
{
|
||||||
|
if (csharpCodeVisitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(csharpCodeVisitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
|
||||||
|
{
|
||||||
|
var baseTypeVisitor = new CSharpBaseTypeVisitor(writer, Context);
|
||||||
|
baseTypeVisitor.Accept(Tree.Chunks);
|
||||||
|
|
||||||
|
var baseType = baseTypeVisitor.CurrentBaseType ?? Host.DefaultBaseClass;
|
||||||
|
|
||||||
|
var baseTypes = string.IsNullOrEmpty(baseType) ? Enumerable.Empty<string>() : new string[] { baseType };
|
||||||
|
|
||||||
|
return writer.BuildClassDeclaration("public", Context.ClassName, baseTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void BuildConstructor(CSharpCodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteLineHiddenDirective();
|
||||||
|
using (writer.BuildConstructor(Context.ClassName))
|
||||||
|
{
|
||||||
|
// Any constructor based logic that we need to add?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddImports(ChunkTree chunkTree, CSharpCodeWriter writer, IEnumerable<string> defaultImports)
|
||||||
|
{
|
||||||
|
// Write out using directives
|
||||||
|
var usingVisitor = new CSharpUsingVisitor(writer, Context);
|
||||||
|
foreach (Chunk chunk in Tree.Chunks)
|
||||||
|
{
|
||||||
|
usingVisitor.Accept(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultImports = defaultImports.Except(usingVisitor.ImportedUsings);
|
||||||
|
|
||||||
|
foreach (string import in defaultImports)
|
||||||
|
{
|
||||||
|
writer.WriteUsing(import);
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskNamespace = typeof(Task).Namespace;
|
||||||
|
|
||||||
|
// We need to add the task namespace but ONLY if it hasn't been added by the default imports or using imports yet.
|
||||||
|
if (!defaultImports.Contains(taskNamespace) && !usingVisitor.ImportedUsings.Contains(taskNamespace))
|
||||||
|
{
|
||||||
|
writer.WriteUsing(taskNamespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,513 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CSharpCodeWriter : CodeWriter
|
||||||
|
{
|
||||||
|
private const string InstanceMethodFormat = "{0}.{1}";
|
||||||
|
|
||||||
|
public CSharpCodeWriter()
|
||||||
|
{
|
||||||
|
LineMappingManager = new LineMappingManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineMappingManager LineMappingManager { get; private set; }
|
||||||
|
|
||||||
|
public new CSharpCodeWriter Write(string data)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.Write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter Indent(int size)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.Indent(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter ResetIndent()
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.ResetIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter SetIndent(int size)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.SetIndent(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter IncreaseIndent(int size)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.IncreaseIndent(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter DecreaseIndent(int size)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.DecreaseIndent(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter WriteLine(string data)
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.WriteLine(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new CSharpCodeWriter WriteLine()
|
||||||
|
{
|
||||||
|
return (CSharpCodeWriter)base.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteVariableDeclaration(string type, string name, string value)
|
||||||
|
{
|
||||||
|
Write(type).Write(" ").Write(name);
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
Write(" = ").Write(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write(" = null");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine(";");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteComment(string comment)
|
||||||
|
{
|
||||||
|
return Write("// ").WriteLine(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteBooleanLiteral(bool value)
|
||||||
|
{
|
||||||
|
return Write(value.ToString().ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartAssignment(string name)
|
||||||
|
{
|
||||||
|
return Write(name).Write(" = ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteParameterSeparator()
|
||||||
|
{
|
||||||
|
return Write(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartNewObject(string typeName)
|
||||||
|
{
|
||||||
|
return Write("new ").Write(typeName).Write("(");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteLocationTaggedString(LocationTagged<string> value)
|
||||||
|
{
|
||||||
|
WriteStringLiteral(value.Value);
|
||||||
|
WriteParameterSeparator();
|
||||||
|
Write(value.Location.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStringLiteral(string literal)
|
||||||
|
{
|
||||||
|
if (literal.Length >= 256 && literal.Length <= 1500 && literal.IndexOf('\0') == -1)
|
||||||
|
{
|
||||||
|
WriteVerbatimStringLiteral(literal);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteCStyleStringLiteral(literal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteLineHiddenDirective()
|
||||||
|
{
|
||||||
|
return WriteLine("#line hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WritePragma(string value)
|
||||||
|
{
|
||||||
|
return Write("#pragma ").WriteLine(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteUsing(string name)
|
||||||
|
{
|
||||||
|
return WriteUsing(name, endLine: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteUsing(string name, bool endLine)
|
||||||
|
{
|
||||||
|
Write(string.Format("using {0}", name));
|
||||||
|
|
||||||
|
if (endLine)
|
||||||
|
{
|
||||||
|
WriteLine(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteLineDefaultDirective()
|
||||||
|
{
|
||||||
|
return WriteLine("#line default");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartReturn()
|
||||||
|
{
|
||||||
|
return Write("return ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteReturn(string value)
|
||||||
|
{
|
||||||
|
return WriteReturn(value, endLine: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteReturn(string value, bool endLine)
|
||||||
|
{
|
||||||
|
Write("return ").Write(value);
|
||||||
|
|
||||||
|
if (endLine)
|
||||||
|
{
|
||||||
|
Write(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a <c>#line</c> pragma directive for the line number at the specified <paramref name="location"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="location">The location to generate the line pragma for.</param>
|
||||||
|
/// <param name="file">The file to generate the line pragma for.</param>
|
||||||
|
/// <returns>The current instance of <see cref="CSharpCodeWriter"/>.</returns>
|
||||||
|
public CSharpCodeWriter WriteLineNumberDirective(SourceLocation location, string file)
|
||||||
|
{
|
||||||
|
if (location.FilePath != null)
|
||||||
|
{
|
||||||
|
file = location.FilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(LastWrite) &&
|
||||||
|
!LastWrite.EndsWith(NewLine, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineNumberAsString = (location.LineIndex + 1).ToString(CultureInfo.InvariantCulture);
|
||||||
|
return Write("#line ").Write(lineNumberAsString).Write(" \"").Write(file).WriteLine("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartMethodInvocation(string methodName)
|
||||||
|
{
|
||||||
|
return WriteStartMethodInvocation(methodName, new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, params string[] genericArguments)
|
||||||
|
{
|
||||||
|
Write(methodName);
|
||||||
|
|
||||||
|
if (genericArguments.Length > 0)
|
||||||
|
{
|
||||||
|
Write("<").Write(string.Join(", ", genericArguments)).Write(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Write("(");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteEndMethodInvocation()
|
||||||
|
{
|
||||||
|
return WriteEndMethodInvocation(endLine: true);
|
||||||
|
}
|
||||||
|
public CSharpCodeWriter WriteEndMethodInvocation(bool endLine)
|
||||||
|
{
|
||||||
|
Write(")");
|
||||||
|
if (endLine)
|
||||||
|
{
|
||||||
|
WriteLine(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a method invocation for the given instance name.
|
||||||
|
public CSharpCodeWriter WriteInstanceMethodInvocation(string instanceName,
|
||||||
|
string methodName,
|
||||||
|
params string[] parameters)
|
||||||
|
{
|
||||||
|
if (instanceName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(instanceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteInstanceMethodInvocation(instanceName, methodName, endLine: true, parameters: parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a method invocation for the given instance name.
|
||||||
|
public CSharpCodeWriter WriteInstanceMethodInvocation(string instanceName,
|
||||||
|
string methodName,
|
||||||
|
bool endLine,
|
||||||
|
params string[] parameters)
|
||||||
|
{
|
||||||
|
if (instanceName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(instanceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteMethodInvocation(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
|
||||||
|
endLine,
|
||||||
|
parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartInstanceMethodInvocation(string instanceName,
|
||||||
|
string methodName)
|
||||||
|
{
|
||||||
|
if (instanceName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(instanceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteStartMethodInvocation(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteMethodInvocation(string methodName, params string[] parameters)
|
||||||
|
{
|
||||||
|
return WriteMethodInvocation(methodName, endLine: true, parameters: parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteMethodInvocation(string methodName, bool endLine, params string[] parameters)
|
||||||
|
{
|
||||||
|
return WriteStartMethodInvocation(methodName).Write(string.Join(", ", parameters)).WriteEndMethodInvocation(endLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpDisableWarningScope BuildDisableWarningScope(int warning)
|
||||||
|
{
|
||||||
|
return new CSharpDisableWarningScope(this, warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildScope()
|
||||||
|
{
|
||||||
|
return new CSharpCodeWritingScope(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildLambda(bool endLine, params string[] parameterNames)
|
||||||
|
{
|
||||||
|
return BuildLambda(endLine, async: false, parameterNames: parameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildAsyncLambda(bool endLine, params string[] parameterNames)
|
||||||
|
{
|
||||||
|
return BuildLambda(endLine, async: true, parameterNames: parameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CSharpCodeWritingScope BuildLambda(bool endLine, bool async, string[] parameterNames)
|
||||||
|
{
|
||||||
|
if (async)
|
||||||
|
{
|
||||||
|
Write("async");
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("(").Write(string.Join(", ", parameterNames)).Write(") => ");
|
||||||
|
|
||||||
|
var scope = new CSharpCodeWritingScope(this);
|
||||||
|
|
||||||
|
if (endLine)
|
||||||
|
{
|
||||||
|
// End the lambda with a semicolon
|
||||||
|
scope.OnClose += () =>
|
||||||
|
{
|
||||||
|
WriteLine(";");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildNamespace(string name)
|
||||||
|
{
|
||||||
|
Write("namespace ").WriteLine(name);
|
||||||
|
|
||||||
|
return new CSharpCodeWritingScope(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name)
|
||||||
|
{
|
||||||
|
return BuildClassDeclaration(accessibility, name, Enumerable.Empty<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name, string baseType)
|
||||||
|
{
|
||||||
|
return BuildClassDeclaration(accessibility, name, new string[] { baseType });
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name, IEnumerable<string> baseTypes)
|
||||||
|
{
|
||||||
|
Write(accessibility).Write(" class ").Write(name);
|
||||||
|
|
||||||
|
if (baseTypes.Count() > 0)
|
||||||
|
{
|
||||||
|
Write(" : ");
|
||||||
|
Write(string.Join(", ", baseTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
|
||||||
|
return new CSharpCodeWritingScope(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildConstructor(string name)
|
||||||
|
{
|
||||||
|
return BuildConstructor("public", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildConstructor(string accessibility, string name)
|
||||||
|
{
|
||||||
|
return BuildConstructor(accessibility, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildConstructor(string accessibility, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||||
|
{
|
||||||
|
Write(accessibility).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||||
|
|
||||||
|
return new CSharpCodeWritingScope(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name)
|
||||||
|
{
|
||||||
|
return BuildMethodDeclaration(accessibility, returnType, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||||
|
{
|
||||||
|
Write(accessibility).Write(" ").Write(returnType).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||||
|
|
||||||
|
return new CSharpCodeWritingScope(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do I need to look at the document content to determine its mapping length?
|
||||||
|
public CSharpLineMappingWriter BuildLineMapping(SourceLocation documentLocation, int contentLength, string sourceFilename)
|
||||||
|
{
|
||||||
|
return new CSharpLineMappingWriter(this, documentLocation, contentLength, sourceFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVerbatimStringLiteral(string literal)
|
||||||
|
{
|
||||||
|
Write("@\"");
|
||||||
|
|
||||||
|
foreach (char c in literal)
|
||||||
|
{
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
Write("\"\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write(c.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCStyleStringLiteral(string literal)
|
||||||
|
{
|
||||||
|
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
|
||||||
|
Write("\"");
|
||||||
|
for (int i = 0; i < literal.Length; i++)
|
||||||
|
{
|
||||||
|
switch (literal[i])
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
Write("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
Write("\\t");
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
Write("\\\"");
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
Write("\\\'");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
Write("\\\\");
|
||||||
|
break;
|
||||||
|
case '\0':
|
||||||
|
Write("\\\0");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
Write("\\n");
|
||||||
|
break;
|
||||||
|
case '\u2028':
|
||||||
|
case '\u2029':
|
||||||
|
Write("\\u");
|
||||||
|
Write(((int)literal[i]).ToString("X4", CultureInfo.InvariantCulture));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Write(literal[i].ToString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i > 0 && i % 80 == 0)
|
||||||
|
{
|
||||||
|
// If current character is a high surrogate and the following
|
||||||
|
// character is a low surrogate, don't break them.
|
||||||
|
// Otherwise when we write the string to a file, we might lose
|
||||||
|
// the characters.
|
||||||
|
if (Char.IsHighSurrogate(literal[i])
|
||||||
|
&& (i < literal.Length - 1)
|
||||||
|
&& Char.IsLowSurrogate(literal[i + 1]))
|
||||||
|
{
|
||||||
|
Write(literal[++i].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("\" +");
|
||||||
|
Write(NewLine);
|
||||||
|
Write("\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartInstrumentationContext(
|
||||||
|
ChunkGeneratorContext context,
|
||||||
|
SyntaxTreeNode syntaxNode,
|
||||||
|
bool isLiteral)
|
||||||
|
{
|
||||||
|
WriteStartMethodInvocation(context.Host.GeneratedClassContext.BeginContextMethodName);
|
||||||
|
Write(syntaxNode.Start.AbsoluteIndex.ToString(CultureInfo.InvariantCulture));
|
||||||
|
WriteParameterSeparator();
|
||||||
|
Write(syntaxNode.Length.ToString(CultureInfo.InvariantCulture));
|
||||||
|
WriteParameterSeparator();
|
||||||
|
Write(isLiteral ? "true" : "false");
|
||||||
|
return WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteEndInstrumentationContext(ChunkGeneratorContext context)
|
||||||
|
{
|
||||||
|
return WriteMethodInvocation(context.Host.GeneratedClassContext.EndContextMethodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public struct CSharpCodeWritingScope : IDisposable
|
||||||
|
{
|
||||||
|
private CodeWriter _writer;
|
||||||
|
private bool _autoSpace;
|
||||||
|
private int _tabSize;
|
||||||
|
private int _startIndent;
|
||||||
|
|
||||||
|
public CSharpCodeWritingScope(CodeWriter writer) : this(writer, true) { }
|
||||||
|
public CSharpCodeWritingScope(CodeWriter writer, int tabSize) : this(writer, tabSize, true) { }
|
||||||
|
// TODO: Make indents (tabs) environment specific
|
||||||
|
public CSharpCodeWritingScope(CodeWriter writer, bool autoSpace) : this(writer, 4, autoSpace) { }
|
||||||
|
public CSharpCodeWritingScope(CodeWriter writer, int tabSize, bool autoSpace)
|
||||||
|
{
|
||||||
|
_writer = writer;
|
||||||
|
_autoSpace = true;
|
||||||
|
_tabSize = tabSize;
|
||||||
|
_startIndent = -1; // Set in WriteStartScope
|
||||||
|
|
||||||
|
OnClose = () => { };
|
||||||
|
|
||||||
|
WriteStartScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action OnClose;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
WriteEndScope();
|
||||||
|
OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteStartScope()
|
||||||
|
{
|
||||||
|
TryAutoSpace(" ");
|
||||||
|
|
||||||
|
_writer.WriteLine("{").IncreaseIndent(_tabSize);
|
||||||
|
_startIndent = _writer.CurrentIndent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEndScope()
|
||||||
|
{
|
||||||
|
TryAutoSpace(_writer.NewLine);
|
||||||
|
|
||||||
|
// Ensure the scope hasn't been modified
|
||||||
|
if (_writer.CurrentIndent == _startIndent)
|
||||||
|
{
|
||||||
|
_writer.DecreaseIndent(_tabSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryAutoSpace(string spaceCharacter)
|
||||||
|
{
|
||||||
|
if (_autoSpace && _writer.LastWrite.Length > 0 && !Char.IsWhiteSpace(_writer.LastWrite.Last()))
|
||||||
|
{
|
||||||
|
_writer.Write(spaceCharacter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public struct CSharpDisableWarningScope : IDisposable
|
||||||
|
{
|
||||||
|
private CSharpCodeWriter _writer;
|
||||||
|
int _warningNumber;
|
||||||
|
|
||||||
|
public CSharpDisableWarningScope(CSharpCodeWriter writer) : this(writer, 219)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public CSharpDisableWarningScope(CSharpCodeWriter writer, int warningNumber)
|
||||||
|
{
|
||||||
|
_writer = writer;
|
||||||
|
_warningNumber = warningNumber;
|
||||||
|
|
||||||
|
_writer.WritePragma("warning disable " + _warningNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_writer.WritePragma("warning restore " + _warningNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CSharpLineMappingWriter : IDisposable
|
||||||
|
{
|
||||||
|
private readonly CSharpCodeWriter _writer;
|
||||||
|
private readonly MappingLocation _documentMapping;
|
||||||
|
private readonly int _startIndent;
|
||||||
|
private readonly bool _writePragmas;
|
||||||
|
private readonly bool _addLineMapping;
|
||||||
|
|
||||||
|
private SourceLocation _generatedLocation;
|
||||||
|
private int _generatedContentLength;
|
||||||
|
|
||||||
|
private CSharpLineMappingWriter(CSharpCodeWriter writer, bool addLineMappings)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer = writer;
|
||||||
|
_addLineMapping = addLineMappings;
|
||||||
|
_startIndent = _writer.CurrentIndent;
|
||||||
|
_writer.ResetIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength)
|
||||||
|
: this(writer, addLineMappings: true)
|
||||||
|
{
|
||||||
|
_documentMapping = new MappingLocation(documentLocation, contentLength);
|
||||||
|
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpLineMappingWriter(
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
SourceLocation documentLocation,
|
||||||
|
int contentLength,
|
||||||
|
string sourceFilename)
|
||||||
|
: this(writer, documentLocation, contentLength)
|
||||||
|
{
|
||||||
|
_writePragmas = true;
|
||||||
|
|
||||||
|
_writer.WriteLineNumberDirective(documentLocation, sourceFilename);
|
||||||
|
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="CSharpLineMappingWriter"/> used for generation of runtime
|
||||||
|
/// line mappings. The constructed instance of <see cref="CSharpLineMappingWriter"/> does not track
|
||||||
|
/// mappings between the Razor content and the generated content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="CSharpCodeWriter"/> to write output to.</param>
|
||||||
|
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the Razor content being mapping.</param>
|
||||||
|
/// <param name="sourceFileName">The input file path.</param>
|
||||||
|
public CSharpLineMappingWriter(
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
SourceLocation documentLocation,
|
||||||
|
string sourceFileName)
|
||||||
|
: this(writer, addLineMappings: false)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
_writePragmas = true;
|
||||||
|
_writer.WriteLineNumberDirective(documentLocation, sourceFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkLineMappingStart()
|
||||||
|
{
|
||||||
|
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkLineMappingEnd()
|
||||||
|
{
|
||||||
|
_generatedContentLength = _writer.GenerateCode().Length - _generatedLocation.AbsoluteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_addLineMapping)
|
||||||
|
{
|
||||||
|
// Verify that the generated length has not already been calculated
|
||||||
|
if (_generatedContentLength == 0)
|
||||||
|
{
|
||||||
|
_generatedContentLength = _writer.GenerateCode().Length - _generatedLocation.AbsoluteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedLocation = new MappingLocation(_generatedLocation, _generatedContentLength);
|
||||||
|
var documentMapping = _documentMapping;
|
||||||
|
if (documentMapping.ContentLength == -1)
|
||||||
|
{
|
||||||
|
documentMapping = new MappingLocation(
|
||||||
|
location: new SourceLocation(
|
||||||
|
_documentMapping.FilePath,
|
||||||
|
_documentMapping.AbsoluteIndex,
|
||||||
|
_documentMapping.LineIndex,
|
||||||
|
_documentMapping.CharacterIndex),
|
||||||
|
contentLength: _generatedContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.LineMappingManager.AddMapping(
|
||||||
|
documentLocation: documentMapping,
|
||||||
|
generatedLocation: generatedLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_writePragmas)
|
||||||
|
{
|
||||||
|
// Need to add an additional line at the end IF there wasn't one already written.
|
||||||
|
// This is needed to work with the C# editor's handling of #line ...
|
||||||
|
var endsWithNewline = _writer.GenerateCode().EndsWith("\n");
|
||||||
|
|
||||||
|
// Always write at least 1 empty line to potentially separate code from pragmas.
|
||||||
|
_writer.WriteLine();
|
||||||
|
|
||||||
|
// Check if the previous empty line wasn't enough to separate code from pragmas.
|
||||||
|
if (!endsWithNewline)
|
||||||
|
{
|
||||||
|
_writer.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteLineDefaultDirective()
|
||||||
|
.WriteLineHiddenDirective();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset indent back to when it was started
|
||||||
|
_writer.SetIndent(_startIndent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
// 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.Razor.Parser;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CSharpPaddingBuilder
|
||||||
|
{
|
||||||
|
private static readonly char[] _newLineChars = { '\r', '\n' };
|
||||||
|
|
||||||
|
private readonly RazorEngineHost _host;
|
||||||
|
|
||||||
|
public CSharpPaddingBuilder(RazorEngineHost host)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for statement padding to account for brace positioning in the editor.
|
||||||
|
public string BuildStatementPadding(Span target)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = CalculatePadding(target, generatedStart: 0);
|
||||||
|
|
||||||
|
// We treat statement padding specially so for brace positioning, so that in the following example:
|
||||||
|
// @if (foo > 0)
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// the braces shows up under the @ rather than under the if.
|
||||||
|
if (_host.DesignTimeMode &&
|
||||||
|
padding > 0 &&
|
||||||
|
target.Previous.Kind == SpanKind.Transition && // target.Previous is guaranteed to not be null if you have padding.
|
||||||
|
string.Equals(target.Previous.Content, SyntaxConstants.TransitionString, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
padding--;
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedCode = BuildPaddingInternal(padding);
|
||||||
|
|
||||||
|
return generatedCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildExpressionPadding(Span target)
|
||||||
|
{
|
||||||
|
return BuildExpressionPadding(target, generatedStart: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildExpressionPadding(Span target, int generatedStart)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = CalculatePadding(target, generatedStart);
|
||||||
|
|
||||||
|
return BuildPaddingInternal(padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int CalculatePadding(Span target, int generatedStart)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
int padding;
|
||||||
|
|
||||||
|
padding = CollectSpacesAndTabs(target, _host.TabSize) - generatedStart;
|
||||||
|
|
||||||
|
// if we add generated text that is longer than the padding we wanted to insert we have no recourse and we have to skip padding
|
||||||
|
// example:
|
||||||
|
// Razor code at column zero: @somecode()
|
||||||
|
// Generated code will be:
|
||||||
|
// In design time: __o = somecode();
|
||||||
|
// In Run time: Write(somecode());
|
||||||
|
//
|
||||||
|
// In both cases the padding would have been 1 space to remote the space the @ symbol takes, which will be smaller than the 6
|
||||||
|
// chars the hidden generated code takes.
|
||||||
|
if (padding < 0)
|
||||||
|
{
|
||||||
|
padding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildPaddingInternal(int padding)
|
||||||
|
{
|
||||||
|
if (_host.DesignTimeMode && _host.IsIndentingWithTabs)
|
||||||
|
{
|
||||||
|
var spaces = padding % _host.TabSize;
|
||||||
|
var tabs = padding / _host.TabSize;
|
||||||
|
|
||||||
|
return new string('\t', tabs) + new string(' ', spaces);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new string(' ', padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CollectSpacesAndTabs(Span target, int tabSize)
|
||||||
|
{
|
||||||
|
var firstSpanInLine = target;
|
||||||
|
|
||||||
|
string currentContent = null;
|
||||||
|
|
||||||
|
while (firstSpanInLine.Previous != null)
|
||||||
|
{
|
||||||
|
// When scanning previous spans we need to be break down the spans with spaces. The parser combines
|
||||||
|
// whitespace into existing spans so you'll see tabs, newlines etc. within spans. We only care about
|
||||||
|
// the \t in existing spans.
|
||||||
|
var previousContent = firstSpanInLine.Previous.Content ?? string.Empty;
|
||||||
|
|
||||||
|
var lastNewLineIndex = previousContent.LastIndexOfAny(_newLineChars);
|
||||||
|
|
||||||
|
if (lastNewLineIndex < 0)
|
||||||
|
{
|
||||||
|
firstSpanInLine = firstSpanInLine.Previous;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (lastNewLineIndex != previousContent.Length - 1)
|
||||||
|
{
|
||||||
|
firstSpanInLine = firstSpanInLine.Previous;
|
||||||
|
currentContent = previousContent.Substring(lastNewLineIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to walk from the beginning of the line, because space + tab(tabSize) = tabSize columns, but tab(tabSize) + space = tabSize+1 columns.
|
||||||
|
var currentSpanInLine = firstSpanInLine;
|
||||||
|
|
||||||
|
if (currentContent == null)
|
||||||
|
{
|
||||||
|
currentContent = currentSpanInLine.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = 0;
|
||||||
|
while (currentSpanInLine != target)
|
||||||
|
{
|
||||||
|
if (currentContent != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < currentContent.Length; i++)
|
||||||
|
{
|
||||||
|
if (currentContent[i] == '\t')
|
||||||
|
{
|
||||||
|
// Example:
|
||||||
|
// <space><space><tab><tab>:
|
||||||
|
// iter 1) 1
|
||||||
|
// iter 2) 2
|
||||||
|
// iter 3) 4 = 2 + (4 - 2)
|
||||||
|
// iter 4) 8 = 4 + (4 - 0)
|
||||||
|
padding = padding + (tabSize - (padding % tabSize));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
padding++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSpanInLine = currentSpanInLine.Next;
|
||||||
|
currentContent = currentSpanInLine.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,761 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.Chunks;
|
||||||
|
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders tag helper rendering code.
|
||||||
|
/// </summary>
|
||||||
|
public class CSharpTagHelperCodeRenderer
|
||||||
|
{
|
||||||
|
internal static readonly string ExecutionContextVariableName = "__tagHelperExecutionContext";
|
||||||
|
internal static readonly string StringValueBufferVariableName = "__tagHelperStringValueBuffer";
|
||||||
|
internal static readonly string ScopeManagerVariableName = "__tagHelperScopeManager";
|
||||||
|
internal static readonly string RunnerVariableName = "__tagHelperRunner";
|
||||||
|
|
||||||
|
private readonly CSharpCodeWriter _writer;
|
||||||
|
private readonly CodeGeneratorContext _context;
|
||||||
|
private readonly IChunkVisitor _bodyVisitor;
|
||||||
|
private readonly IChunkVisitor _literalBodyVisitor;
|
||||||
|
private readonly TagHelperAttributeCodeVisitor _attributeCodeVisitor;
|
||||||
|
private readonly GeneratedTagHelperContext _tagHelperContext;
|
||||||
|
private readonly bool _designTimeMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="CSharpTagHelperCodeRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bodyVisitor">The <see cref="IChunkVisitor"/> used to render chunks found in the body.</param>
|
||||||
|
/// <param name="writer">The <see cref="CSharpCodeWriter"/> used to write code.</param>
|
||||||
|
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current code generation process.</param>
|
||||||
|
public CSharpTagHelperCodeRenderer(
|
||||||
|
IChunkVisitor bodyVisitor,
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
CodeGeneratorContext context)
|
||||||
|
{
|
||||||
|
if (bodyVisitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(bodyVisitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
_bodyVisitor = bodyVisitor;
|
||||||
|
_writer = writer;
|
||||||
|
_context = context;
|
||||||
|
_tagHelperContext = context.Host.GeneratedClassContext.GeneratedTagHelperContext;
|
||||||
|
_designTimeMode = context.Host.DesignTimeMode;
|
||||||
|
|
||||||
|
_literalBodyVisitor = new CSharpLiteralCodeVisitor(this, writer, context);
|
||||||
|
_attributeCodeVisitor = new TagHelperAttributeCodeVisitor(writer, context);
|
||||||
|
AttributeValueCodeRenderer = new TagHelperAttributeValueCodeRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagHelperAttributeValueCodeRenderer AttributeValueCodeRenderer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the code for the given <paramref name="chunk"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
|
||||||
|
public void RenderTagHelper(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
// Remove any duplicate TagHelperDescriptors that reference the same type name. Duplicates can occur when
|
||||||
|
// multiple HtmlTargetElement attributes are on a TagHelper type and matches overlap for an HTML element.
|
||||||
|
// Having more than one descriptor with the same TagHelper type results in generated code that runs
|
||||||
|
// the same TagHelper X many times (instead of once) over a single HTML element.
|
||||||
|
var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default);
|
||||||
|
|
||||||
|
RenderBeginTagHelperScope(chunk.TagName, chunk.TagMode, chunk.Children);
|
||||||
|
|
||||||
|
RenderTagHelpersCreation(chunk, tagHelperDescriptors);
|
||||||
|
|
||||||
|
RenderAttributes(chunk.Attributes, tagHelperDescriptors);
|
||||||
|
|
||||||
|
// No need to run anything in design time mode.
|
||||||
|
if (!_designTimeMode)
|
||||||
|
{
|
||||||
|
RenderRunTagHelpers();
|
||||||
|
RenderWriteTagHelperMethodCall(chunk);
|
||||||
|
RenderEndTagHelpersScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetVariableName(TagHelperDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return "__" + descriptor.TypeName.Replace('.', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBeginTagHelperScope(string tagName, TagMode tagMode, IList<Chunk> children)
|
||||||
|
{
|
||||||
|
// Scopes/execution contexts are a runtime feature.
|
||||||
|
if (_designTimeMode)
|
||||||
|
{
|
||||||
|
// Render all of the tag helper children inline for IntelliSense.
|
||||||
|
_bodyVisitor.Accept(children);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call into the tag helper scope manager to start a new tag helper scope.
|
||||||
|
// Also capture the value as the current execution context.
|
||||||
|
_writer
|
||||||
|
.WriteStartAssignment(ExecutionContextVariableName)
|
||||||
|
.WriteStartInstanceMethodInvocation(
|
||||||
|
ScopeManagerVariableName,
|
||||||
|
_tagHelperContext.ScopeManagerBeginMethodName);
|
||||||
|
|
||||||
|
// Assign a unique ID for this instance of the source HTML tag. This must be unique
|
||||||
|
// per call site, e.g. if the tag is on the view twice, there should be two IDs.
|
||||||
|
_writer.WriteStringLiteral(tagName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(nameof(TagMode))
|
||||||
|
.Write(".")
|
||||||
|
.Write(tagMode.ToString())
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(GenerateUniqueId())
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
|
||||||
|
// We remove the target writer so TagHelper authors can retrieve content.
|
||||||
|
var oldWriter = _context.TargetWriterName;
|
||||||
|
_context.TargetWriterName = null;
|
||||||
|
|
||||||
|
using (_writer.BuildAsyncLambda(endLine: false))
|
||||||
|
{
|
||||||
|
// Render all of the tag helper children.
|
||||||
|
_bodyVisitor.Accept(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.TargetWriterName = oldWriter;
|
||||||
|
|
||||||
|
_writer.WriteParameterSeparator()
|
||||||
|
.Write(_tagHelperContext.StartTagHelperWritingScopeMethodName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(_tagHelperContext.EndTagHelperWritingScopeMethodName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a unique ID for an HTML element.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A globally unique ID.
|
||||||
|
/// </returns>
|
||||||
|
protected virtual string GenerateUniqueId()
|
||||||
|
{
|
||||||
|
return Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTagHelpersCreation(
|
||||||
|
TagHelperChunk chunk,
|
||||||
|
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
// This is to maintain value accessors for attributes when creating the TagHelpers.
|
||||||
|
// Ultimately it enables us to do scenarios like this:
|
||||||
|
// myTagHelper1.Foo = DateTime.Now;
|
||||||
|
// myTagHelper2.Foo = myTagHelper1.Foo;
|
||||||
|
var htmlAttributeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var tagHelperDescriptor in tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
var tagHelperVariableName = GetVariableName(tagHelperDescriptor);
|
||||||
|
|
||||||
|
// Create the tag helper
|
||||||
|
_writer.WriteStartAssignment(tagHelperVariableName)
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.CreateTagHelperMethodName,
|
||||||
|
tagHelperDescriptor.TypeName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
|
||||||
|
// Execution contexts and throwing errors for null dictionary properties are a runtime feature.
|
||||||
|
if (_designTimeMode)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddMethodName,
|
||||||
|
tagHelperVariableName);
|
||||||
|
|
||||||
|
// Track dictionary properties we have confirmed are non-null.
|
||||||
|
var confirmedDictionaries = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
// Ensure that all created TagHelpers have initialized dictionary bound properties which are used
|
||||||
|
// via TagHelper indexers.
|
||||||
|
foreach (var chunkAttribute in chunk.Attributes)
|
||||||
|
{
|
||||||
|
var associatedAttributeDescriptor = tagHelperDescriptor.Attributes.FirstOrDefault(
|
||||||
|
attributeDescriptor => attributeDescriptor.IsNameMatch(chunkAttribute.Key));
|
||||||
|
|
||||||
|
if (associatedAttributeDescriptor != null &&
|
||||||
|
associatedAttributeDescriptor.IsIndexer &&
|
||||||
|
confirmedDictionaries.Add(associatedAttributeDescriptor.PropertyName))
|
||||||
|
{
|
||||||
|
// Throw a reasonable Exception at runtime if the dictionary property is null.
|
||||||
|
_writer
|
||||||
|
.Write("if (")
|
||||||
|
.Write(tagHelperVariableName)
|
||||||
|
.Write(".")
|
||||||
|
.Write(associatedAttributeDescriptor.PropertyName)
|
||||||
|
.WriteLine(" == null)");
|
||||||
|
using (_writer.BuildScope())
|
||||||
|
{
|
||||||
|
// System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName
|
||||||
|
// of InvalidOperationException type.
|
||||||
|
_writer
|
||||||
|
.Write("throw ")
|
||||||
|
.WriteStartNewObject(nameof(InvalidOperationException))
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.FormatInvalidIndexerAssignmentMethodName)
|
||||||
|
.WriteStringLiteral(chunkAttribute.Key)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(tagHelperDescriptor.TypeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(associatedAttributeDescriptor.PropertyName)
|
||||||
|
.WriteEndMethodInvocation(endLine: false) // End of method call
|
||||||
|
.WriteEndMethodInvocation(endLine: true); // End of new expression / throw statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAttributes(
|
||||||
|
IList<KeyValuePair<string, Chunk>> chunkAttributes,
|
||||||
|
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
var renderedBoundAttributeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Go through the HTML attributes in source order, assigning to properties or indexers or adding to
|
||||||
|
// TagHelperExecutionContext.HTMLAttributes' as we go.
|
||||||
|
foreach (var attribute in chunkAttributes)
|
||||||
|
{
|
||||||
|
var attributeName = attribute.Key;
|
||||||
|
var attributeValueChunk = attribute.Value;
|
||||||
|
var associatedDescriptors = tagHelperDescriptors.Where(descriptor =>
|
||||||
|
descriptor.Attributes.Any(attributeDescriptor => attributeDescriptor.IsNameMatch(attributeName)));
|
||||||
|
|
||||||
|
// Bound attributes have associated descriptors. First attribute value wins if there are duplicates;
|
||||||
|
// later values of duplicate bound attributes are treated as if they were unbound.
|
||||||
|
if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attributeName))
|
||||||
|
{
|
||||||
|
if (attributeValueChunk == null)
|
||||||
|
{
|
||||||
|
// Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
|
||||||
|
// logged an error if it was a bound attribute; so we can skip.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to capture the tag helper's property value accessor so we can retrieve it later
|
||||||
|
// if there are more tag helpers that need the value.
|
||||||
|
string valueAccessor = null;
|
||||||
|
|
||||||
|
foreach (var associatedDescriptor in associatedDescriptors)
|
||||||
|
{
|
||||||
|
var associatedAttributeDescriptor = associatedDescriptor.Attributes.First(
|
||||||
|
attributeDescriptor => attributeDescriptor.IsNameMatch(attributeName));
|
||||||
|
var tagHelperVariableName = GetVariableName(associatedDescriptor);
|
||||||
|
|
||||||
|
valueAccessor = RenderBoundAttribute(
|
||||||
|
attributeName,
|
||||||
|
attributeValueChunk,
|
||||||
|
tagHelperVariableName,
|
||||||
|
valueAccessor,
|
||||||
|
associatedAttributeDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderUnboundAttribute(attributeName, attributeValueChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string RenderBoundAttribute(
|
||||||
|
string attributeName,
|
||||||
|
Chunk attributeValueChunk,
|
||||||
|
string tagHelperVariableName,
|
||||||
|
string previousValueAccessor,
|
||||||
|
TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
var currentValueAccessor = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}.{1}",
|
||||||
|
tagHelperVariableName,
|
||||||
|
attributeDescriptor.PropertyName);
|
||||||
|
|
||||||
|
if (attributeDescriptor.IsIndexer)
|
||||||
|
{
|
||||||
|
var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
|
||||||
|
currentValueAccessor += $"[\"{dictionaryKey}\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this attribute value has not been seen before, need to record its value.
|
||||||
|
if (previousValueAccessor == null)
|
||||||
|
{
|
||||||
|
// Bufferable attributes are attributes that can have Razor code inside of them. Such
|
||||||
|
// attributes have string values and may be calculated using a temporary TextWriter or other
|
||||||
|
// buffer.
|
||||||
|
var bufferableAttribute = attributeDescriptor.IsStringProperty;
|
||||||
|
|
||||||
|
RenderNewAttributeValueAssignment(
|
||||||
|
attributeDescriptor,
|
||||||
|
bufferableAttribute,
|
||||||
|
attributeValueChunk,
|
||||||
|
currentValueAccessor);
|
||||||
|
|
||||||
|
if (_designTimeMode)
|
||||||
|
{
|
||||||
|
// Execution contexts are a runtime feature.
|
||||||
|
return currentValueAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to inform the context of the attribute value.
|
||||||
|
_writer
|
||||||
|
.WriteStartInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(currentValueAccessor)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
|
||||||
|
return currentValueAccessor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The attribute value has already been determined and accessor was passed to us as
|
||||||
|
// previousValueAccessor, we don't want to evaluate the value twice so lets just use the
|
||||||
|
// previousValueLocation.
|
||||||
|
_writer
|
||||||
|
.WriteStartAssignment(currentValueAccessor)
|
||||||
|
.Write(previousValueAccessor)
|
||||||
|
.WriteLine(";");
|
||||||
|
|
||||||
|
return previousValueAccessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render assignment of attribute value to the value accessor.
|
||||||
|
private void RenderNewAttributeValueAssignment(
|
||||||
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
bool bufferableAttribute,
|
||||||
|
Chunk attributeValueChunk,
|
||||||
|
string valueAccessor)
|
||||||
|
{
|
||||||
|
// Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
|
||||||
|
// may be more than just a plain text value, it may also contain Razor code which is why we attempt
|
||||||
|
// to retrieve a plain text value here.
|
||||||
|
string textValue;
|
||||||
|
var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);
|
||||||
|
|
||||||
|
if (bufferableAttribute)
|
||||||
|
{
|
||||||
|
if (!isPlainTextValue)
|
||||||
|
{
|
||||||
|
// If we haven't recorded a value and we need to buffer an attribute value and the value is not
|
||||||
|
// plain text then we need to prepare the value prior to setting it below.
|
||||||
|
BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteStartAssignment(valueAccessor);
|
||||||
|
|
||||||
|
if (isPlainTextValue)
|
||||||
|
{
|
||||||
|
// If the attribute is bufferable but has a plain text value that means the value
|
||||||
|
// is a string which needs to be surrounded in quotes.
|
||||||
|
RenderQuotedAttributeValue(textValue, attributeDescriptor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The value contains more than plain text e.g. stringAttribute ="Time: @DateTime.Now".
|
||||||
|
RenderBufferedAttributeValue(attributeDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteLine(";");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Write out simple assignment for non-string property value. Try to keep the whole
|
||||||
|
// statement together and the #line pragma correct to make debugging possible.
|
||||||
|
using (var lineMapper = new CSharpLineMappingWriter(
|
||||||
|
_writer,
|
||||||
|
attributeValueChunk.Association.Start,
|
||||||
|
_context.SourceFile))
|
||||||
|
{
|
||||||
|
// Place the assignment LHS to align RHS with original attribute value's indentation.
|
||||||
|
// Unfortunately originalIndent is incorrect if original line contains tabs. Unable to
|
||||||
|
// use a CSharpPaddingBuilder because the Association has no Previous node; lost the
|
||||||
|
// original Span sequence when the parse tree was rewritten.
|
||||||
|
var originalIndent = attributeValueChunk.Start.CharacterIndex;
|
||||||
|
var generatedLength = valueAccessor.Length + " = ".Length;
|
||||||
|
var newIndent = originalIndent - generatedLength;
|
||||||
|
if (newIndent > 0)
|
||||||
|
{
|
||||||
|
_writer.Indent(newIndent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteStartAssignment(valueAccessor);
|
||||||
|
lineMapper.MarkLineMappingStart();
|
||||||
|
|
||||||
|
// Write out bare expression for this attribute value. Property is not a string.
|
||||||
|
// So quoting or buffering are not helpful.
|
||||||
|
RenderRawAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue);
|
||||||
|
|
||||||
|
// End the assignment to the attribute.
|
||||||
|
lineMapper.MarkLineMappingEnd();
|
||||||
|
_writer.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderUnboundAttribute(string attributeName, Chunk attributeValueChunk)
|
||||||
|
{
|
||||||
|
// Render children to provide IntelliSense at design time. No need for the execution context logic, it's
|
||||||
|
// a runtime feature.
|
||||||
|
if (_designTimeMode)
|
||||||
|
{
|
||||||
|
if (attributeValueChunk != null)
|
||||||
|
{
|
||||||
|
_bodyVisitor.Accept(attributeValueChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a minimized attribute there is no value
|
||||||
|
if (attributeValueChunk == null)
|
||||||
|
{
|
||||||
|
_writer
|
||||||
|
.WriteStartInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddMinimizedHtmlAttributeMethodName)
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string textValue = null;
|
||||||
|
var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);
|
||||||
|
|
||||||
|
if (isPlainTextValue)
|
||||||
|
{
|
||||||
|
// If it's a plain text value then we need to surround the value with quotes.
|
||||||
|
_writer
|
||||||
|
.WriteStartInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName)
|
||||||
|
.WriteStringLiteral(textValue)
|
||||||
|
.WriteEndMethodInvocation(endLine: false)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
else if (IsDynamicAttributeValue(attributeValueChunk))
|
||||||
|
{
|
||||||
|
// Dynamic attribute value should be run through the conditional attribute removal system. It's
|
||||||
|
// unbound and contains C#.
|
||||||
|
|
||||||
|
// TagHelper attribute rendering is buffered by default. We do not want to write to the current
|
||||||
|
// writer.
|
||||||
|
var currentTargetWriter = _context.TargetWriterName;
|
||||||
|
var currentWriteAttributeMethodName = _context.Host.GeneratedClassContext.WriteAttributeValueMethodName;
|
||||||
|
_context.TargetWriterName = null;
|
||||||
|
|
||||||
|
Debug.Assert(attributeValueChunk is ParentChunk);
|
||||||
|
var children = ((ParentChunk)attributeValueChunk).Children;
|
||||||
|
var attributeCount = children.Count(c => c is DynamicCodeAttributeChunk || c is LiteralCodeAttributeChunk);
|
||||||
|
|
||||||
|
_writer
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.BeginAddHtmlAttributeValuesMethodName)
|
||||||
|
.Write(ExecutionContextVariableName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(attributeCount.ToString(CultureInfo.InvariantCulture))
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
|
||||||
|
_attributeCodeVisitor.Accept(attributeValueChunk);
|
||||||
|
|
||||||
|
_writer.WriteMethodInvocation(
|
||||||
|
_tagHelperContext.EndAddHtmlAttributeValuesMethodName,
|
||||||
|
ExecutionContextVariableName);
|
||||||
|
|
||||||
|
_context.TargetWriterName = currentTargetWriter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HTML attributes are always strings. This attribute contains C# but is not dynamic. This occurs
|
||||||
|
// when the attribute is a data-* attribute.
|
||||||
|
|
||||||
|
// Attribute value is not plain text, must be buffered to determine its final value.
|
||||||
|
BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: true);
|
||||||
|
|
||||||
|
_writer
|
||||||
|
.WriteStartInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName);
|
||||||
|
|
||||||
|
RenderBufferedAttributeValueAccessor(_writer);
|
||||||
|
|
||||||
|
_writer
|
||||||
|
.WriteEndMethodInvocation(endLine: false)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderEndTagHelpersScope()
|
||||||
|
{
|
||||||
|
_writer.WriteStartAssignment(ExecutionContextVariableName)
|
||||||
|
.WriteInstanceMethodInvocation(ScopeManagerVariableName,
|
||||||
|
_tagHelperContext.ScopeManagerEndMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderWriteTagHelperMethodCall(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
_writer
|
||||||
|
.WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false)
|
||||||
|
.Write("await ");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_context.TargetWriterName))
|
||||||
|
{
|
||||||
|
_writer
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperToAsyncMethodName)
|
||||||
|
.Write(_context.TargetWriterName)
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_writer.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperAsyncMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer
|
||||||
|
.Write(ExecutionContextVariableName)
|
||||||
|
.WriteEndMethodInvocation()
|
||||||
|
.WriteEndInstrumentationContext(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderRunTagHelpers()
|
||||||
|
{
|
||||||
|
_writer.Write(ExecutionContextVariableName)
|
||||||
|
.Write(".")
|
||||||
|
.WriteStartAssignment(_tagHelperContext.ExecutionContextOutputPropertyName)
|
||||||
|
.Write("await ")
|
||||||
|
.WriteStartInstanceMethodInvocation(RunnerVariableName,
|
||||||
|
_tagHelperContext.RunnerRunAsyncMethodName);
|
||||||
|
|
||||||
|
_writer.Write(ExecutionContextVariableName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
// Pass complexValue: false because variable.ToString() replaces any original complexity in the expression.
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
RenderBufferedAttributeValueAccessor(writer);
|
||||||
|
},
|
||||||
|
complexValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderRawAttributeValue(
|
||||||
|
Chunk attributeValueChunk,
|
||||||
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
bool isPlainTextValue)
|
||||||
|
{
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
var visitor =
|
||||||
|
new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
|
||||||
|
visitor.Accept(attributeValueChunk);
|
||||||
|
},
|
||||||
|
complexValue: !isPlainTextValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
writer.WriteStringLiteral(value);
|
||||||
|
},
|
||||||
|
complexValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a buffered writing scope for the HTML attribute value.
|
||||||
|
private void BuildBufferedWritingScope(Chunk htmlAttributeChunk, bool htmlEncodeValues)
|
||||||
|
{
|
||||||
|
// We're building a writing scope around the provided chunks which captures everything written from the
|
||||||
|
// page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to
|
||||||
|
// ensure we capture all content that's written, directly or indirectly.
|
||||||
|
var oldWriter = _context.TargetWriterName;
|
||||||
|
_context.TargetWriterName = null;
|
||||||
|
|
||||||
|
// Need to disable instrumentation inside of writing scopes, the instrumentation will not detect
|
||||||
|
// content written inside writing scopes.
|
||||||
|
var oldInstrumentation = _context.Host.EnableInstrumentation;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_context.Host.EnableInstrumentation = false;
|
||||||
|
|
||||||
|
// Scopes are a runtime feature.
|
||||||
|
if (!_designTimeMode)
|
||||||
|
{
|
||||||
|
_writer.WriteMethodInvocation(_tagHelperContext.StartTagHelperWritingScopeMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor;
|
||||||
|
visitor.Accept(htmlAttributeChunk);
|
||||||
|
|
||||||
|
// Scopes are a runtime feature.
|
||||||
|
if (!_designTimeMode)
|
||||||
|
{
|
||||||
|
_writer.WriteStartAssignment(StringValueBufferVariableName)
|
||||||
|
.WriteMethodInvocation(_tagHelperContext.EndTagHelperWritingScopeMethodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Reset instrumentation back to what it was, leaving the writing scope.
|
||||||
|
_context.Host.EnableInstrumentation = oldInstrumentation;
|
||||||
|
|
||||||
|
// Reset the writer/buffer back to what it was, leaving the writing scope.
|
||||||
|
_context.TargetWriterName = oldWriter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
Action<CSharpCodeWriter> valueRenderer,
|
||||||
|
bool complexValue)
|
||||||
|
{
|
||||||
|
AttributeValueCodeRenderer.RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
_writer,
|
||||||
|
_context,
|
||||||
|
valueRenderer,
|
||||||
|
complexValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBufferedAttributeValueAccessor(CSharpCodeWriter writer)
|
||||||
|
{
|
||||||
|
if (_designTimeMode)
|
||||||
|
{
|
||||||
|
// There is no value buffer in design time mode but we still want to write out a value. We write a
|
||||||
|
// value to ensure the tag helper's property type is string.
|
||||||
|
writer.Write("string.Empty");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteInstanceMethodInvocation(
|
||||||
|
StringValueBufferVariableName,
|
||||||
|
_tagHelperContext.TagHelperContentGetContentMethodName,
|
||||||
|
endLine: false,
|
||||||
|
parameters: new string[] { _tagHelperContext.HtmlEncoderPropertyName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDynamicAttributeValue(Chunk attributeValueChunk)
|
||||||
|
{
|
||||||
|
var parentChunk = attributeValueChunk as ParentChunk;
|
||||||
|
if (parentChunk != null)
|
||||||
|
{
|
||||||
|
return parentChunk.Children.Any(child => child is DynamicCodeAttributeChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPlainTextValue(Chunk chunk, out string plainText)
|
||||||
|
{
|
||||||
|
var parentChunk = chunk as ParentChunk;
|
||||||
|
|
||||||
|
plainText = null;
|
||||||
|
|
||||||
|
if (parentChunk == null || parentChunk.Children.Count != 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var literalChildChunk = parentChunk.Children[0] as LiteralChunk;
|
||||||
|
|
||||||
|
if (literalChildChunk == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText = literalChildChunk.Text;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CSharpCodeVisitor which does not HTML encode values. Used when rendering bound string attribute values.
|
||||||
|
private class CSharpLiteralCodeVisitor : CSharpCodeVisitor
|
||||||
|
{
|
||||||
|
public CSharpLiteralCodeVisitor(
|
||||||
|
CSharpTagHelperCodeRenderer tagHelperRenderer,
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
CodeGeneratorContext context)
|
||||||
|
: base(writer, context)
|
||||||
|
{
|
||||||
|
// Ensure that no matter how this class is used, we don't create numerous CSharpTagHelperCodeRenderer
|
||||||
|
// instances.
|
||||||
|
TagHelperRenderer = tagHelperRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string WriteMethodName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Context.Host.GeneratedClassContext.WriteLiteralMethodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string WriteToMethodName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Context.Host.GeneratedClassContext.WriteLiteralToMethodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TagHelperAttributeCodeVisitor : CSharpCodeVisitor
|
||||||
|
{
|
||||||
|
public TagHelperAttributeCodeVisitor(
|
||||||
|
CSharpCodeWriter writer,
|
||||||
|
CodeGeneratorContext context)
|
||||||
|
: base(writer, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string WriteAttributeValueMethodName =>
|
||||||
|
Context.Host.GeneratedClassContext.GeneratedTagHelperContext.AddHtmlAttributeValueMethodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public abstract class CodeGenerator
|
||||||
|
{
|
||||||
|
private readonly CodeGeneratorContext _context;
|
||||||
|
|
||||||
|
public CodeGenerator(CodeGeneratorContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CodeGeneratorContext Context
|
||||||
|
{
|
||||||
|
get { return _context; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract CodeGeneratorResult Generate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// 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.Razor.Chunks.Generators;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Context object with information used to generate a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public class CodeGeneratorContext : ChunkGeneratorContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="CodeGeneratorContext"/> object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generatorContext">A <see cref="ChunkGeneratorContext"/> to copy information from.</param>
|
||||||
|
/// <param name="errorSink">
|
||||||
|
/// The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
||||||
|
/// when parsing the current Razor document.
|
||||||
|
/// </param>
|
||||||
|
public CodeGeneratorContext(ChunkGeneratorContext generatorContext, ErrorSink errorSink)
|
||||||
|
: base(generatorContext)
|
||||||
|
{
|
||||||
|
ErrorSink = errorSink;
|
||||||
|
ExpressionRenderingMode = ExpressionRenderingMode.WriteToOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing.
|
||||||
|
internal CodeGeneratorContext(
|
||||||
|
RazorEngineHost host,
|
||||||
|
string className,
|
||||||
|
string rootNamespace,
|
||||||
|
string sourceFile,
|
||||||
|
bool shouldGenerateLinePragmas,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
: base(host, className, rootNamespace, sourceFile, shouldGenerateLinePragmas)
|
||||||
|
{
|
||||||
|
ErrorSink = errorSink;
|
||||||
|
ExpressionRenderingMode = ExpressionRenderingMode.WriteToOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current C# rendering mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="ExpressionRenderingMode.WriteToOutput"/> forces C# generation to write
|
||||||
|
/// <see cref="Chunks.Chunk"/>s to the output page, i.e. WriteLiteral("Hello World").
|
||||||
|
/// <see cref="ExpressionRenderingMode.InjectCode"/> writes <see cref="Chunks.Chunk"/> values in their
|
||||||
|
/// rawest form, i.g. "Hello World".
|
||||||
|
/// </remarks>
|
||||||
|
public ExpressionRenderingMode ExpressionRenderingMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The C# writer to write <see cref="Chunks.Chunk"/> information to.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If <see cref="TargetWriterName"/> is <c>null</c> values will be written using a default write method
|
||||||
|
/// i.e. WriteLiteral("Hello World").
|
||||||
|
/// If <see cref="TargetWriterName"/> is not <c>null</c> values will be written to the given
|
||||||
|
/// <see cref="TargetWriterName"/>, i.e. WriteLiteralTo(myWriter, "Hello World").
|
||||||
|
/// </remarks>
|
||||||
|
public string TargetWriterName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <c>SHA1</c> based checksum for the file whose location is defined by
|
||||||
|
/// <see cref="ChunkGeneratorContext.SourceFile"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string Checksum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to aggregate <see cref="RazorError"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public ErrorSink ErrorSink { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CodeGeneratorResult
|
||||||
|
{
|
||||||
|
public CodeGeneratorResult(string code, IList<LineMapping> designTimeLineMappings)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
DesignTimeLineMappings = designTimeLineMappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Code { get; private set; }
|
||||||
|
public IList<LineMapping> DesignTimeLineMappings { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
// 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.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class CodeWriter : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly char[] NewLineCharacters = new char[] { '\r', '\n' };
|
||||||
|
private readonly StringWriter _writer = new StringWriter();
|
||||||
|
private bool _newLine;
|
||||||
|
private string _cache = string.Empty;
|
||||||
|
private bool _dirty = false;
|
||||||
|
|
||||||
|
private int _absoluteIndex;
|
||||||
|
private int _currentLineIndex;
|
||||||
|
private int _currentLineCharacterIndex;
|
||||||
|
|
||||||
|
public string LastWrite { get; private set; }
|
||||||
|
|
||||||
|
public int CurrentIndent { get; private set; }
|
||||||
|
|
||||||
|
public string NewLine
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _writer.NewLine;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_writer.NewLine = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter ResetIndent()
|
||||||
|
{
|
||||||
|
return SetIndent(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter IncreaseIndent(int size)
|
||||||
|
{
|
||||||
|
CurrentIndent += size;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter DecreaseIndent(int size)
|
||||||
|
{
|
||||||
|
CurrentIndent -= size;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter SetIndent(int size)
|
||||||
|
{
|
||||||
|
CurrentIndent = size;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter Indent(int size)
|
||||||
|
{
|
||||||
|
if (_newLine)
|
||||||
|
{
|
||||||
|
_writer.Write(new string(' ', size));
|
||||||
|
Flush();
|
||||||
|
|
||||||
|
_currentLineCharacterIndex += size;
|
||||||
|
_absoluteIndex += size;
|
||||||
|
|
||||||
|
_dirty = true;
|
||||||
|
_newLine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter Write(string data)
|
||||||
|
{
|
||||||
|
Indent(CurrentIndent);
|
||||||
|
|
||||||
|
_writer.Write(data);
|
||||||
|
Flush();
|
||||||
|
|
||||||
|
LastWrite = data;
|
||||||
|
_dirty = true;
|
||||||
|
_newLine = false;
|
||||||
|
|
||||||
|
if (data == null || data.Length == 0)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
_absoluteIndex += data.Length;
|
||||||
|
|
||||||
|
// The data string might contain a partial newline where the previously
|
||||||
|
// written string has part of the newline.
|
||||||
|
var i = 0;
|
||||||
|
int? trailingPartStart = null;
|
||||||
|
var builder = _writer.GetStringBuilder();
|
||||||
|
|
||||||
|
if (
|
||||||
|
// Check the last character of the previous write operation.
|
||||||
|
builder.Length - data.Length - 1 >= 0 &&
|
||||||
|
builder[builder.Length - data.Length - 1] == '\r' &&
|
||||||
|
|
||||||
|
// Check the first character of the current write operation.
|
||||||
|
builder[builder.Length - data.Length] == '\n')
|
||||||
|
{
|
||||||
|
// This is newline that's spread across two writes. Skip the first character of the
|
||||||
|
// current write operation.
|
||||||
|
//
|
||||||
|
// We don't need to increment our newline counter because we already did that when we
|
||||||
|
// saw the \r.
|
||||||
|
i += 1;
|
||||||
|
trailingPartStart = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate the string, stopping at each occurrence of a newline character. This lets us count the
|
||||||
|
// newline occurrences and keep the index of the last one.
|
||||||
|
while ((i = data.IndexOfAny(NewLineCharacters, i)) >= 0)
|
||||||
|
{
|
||||||
|
// Newline found.
|
||||||
|
_currentLineIndex++;
|
||||||
|
_currentLineCharacterIndex = 0;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// We might have stopped at a \r, so check if it's followed by \n and then advance the index to
|
||||||
|
// start the next search after it.
|
||||||
|
if (data.Length > i &&
|
||||||
|
data[i - 1] == '\r' &&
|
||||||
|
data[i] == '\n')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 'suffix' of the current line starts after this newline token.
|
||||||
|
trailingPartStart = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trailingPartStart == null)
|
||||||
|
{
|
||||||
|
// No newlines, just add the length of the data buffer
|
||||||
|
_currentLineCharacterIndex += data.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Newlines found, add the trailing part of 'data'
|
||||||
|
_currentLineCharacterIndex += (data.Length - trailingPartStart.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter WriteLine()
|
||||||
|
{
|
||||||
|
LastWrite = _writer.NewLine;
|
||||||
|
|
||||||
|
_writer.WriteLine();
|
||||||
|
Flush();
|
||||||
|
|
||||||
|
_currentLineIndex++;
|
||||||
|
_currentLineCharacterIndex = 0;
|
||||||
|
_absoluteIndex += _writer.NewLine.Length;
|
||||||
|
|
||||||
|
_dirty = true;
|
||||||
|
_newLine = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter WriteLine(string data)
|
||||||
|
{
|
||||||
|
return Write(data).WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter Flush()
|
||||||
|
{
|
||||||
|
_writer.Flush();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateCode()
|
||||||
|
{
|
||||||
|
if (_dirty)
|
||||||
|
{
|
||||||
|
_cache = _writer.ToString();
|
||||||
|
_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceLocation GetCurrentSourceLocation()
|
||||||
|
{
|
||||||
|
return new SourceLocation(_absoluteIndex, _currentLineIndex, _currentLineCharacterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public enum ExpressionRenderingMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that expressions should be written to the output stream
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// If @foo is rendered with WriteToOutput, the code generator would output the following code:
|
||||||
|
///
|
||||||
|
/// Write(foo);
|
||||||
|
/// </example>
|
||||||
|
WriteToOutput,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that expressions should simply be placed as-is in the code, and the context in which
|
||||||
|
/// the code exists will be used to render it
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// If @foo is rendered with InjectCode, the code generator would output the following code:
|
||||||
|
///
|
||||||
|
/// foo
|
||||||
|
/// </example>
|
||||||
|
InjectCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
// 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.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public struct GeneratedClassContext
|
||||||
|
{
|
||||||
|
public static readonly string DefaultWriteMethodName = "Write";
|
||||||
|
public static readonly string DefaultWriteLiteralMethodName = "WriteLiteral";
|
||||||
|
public static readonly string DefaultExecuteMethodName = "ExecuteAsync";
|
||||||
|
public static readonly string DefaultBeginWriteAttributeMethodName = "BeginWriteAttribute";
|
||||||
|
public static readonly string DefaultBeginWriteAttributeToMethodName = "BeginWriteAttributeTo";
|
||||||
|
public static readonly string DefaultEndWriteAttributeMethodName = "EndWriteAttribute";
|
||||||
|
public static readonly string DefaultEndWriteAttributeToMethodName = "EndWriteAttributeTo";
|
||||||
|
public static readonly string DefaultWriteAttributeValueMethodName = "WriteAttributeValue";
|
||||||
|
public static readonly string DefaultWriteAttributeValueToMethodName = "WriteAttributeValueTo";
|
||||||
|
|
||||||
|
public static readonly GeneratedClassContext Default =
|
||||||
|
new GeneratedClassContext(
|
||||||
|
DefaultExecuteMethodName,
|
||||||
|
DefaultWriteMethodName,
|
||||||
|
DefaultWriteLiteralMethodName,
|
||||||
|
new GeneratedTagHelperContext());
|
||||||
|
|
||||||
|
public GeneratedClassContext(
|
||||||
|
string executeMethodName,
|
||||||
|
string writeMethodName,
|
||||||
|
string writeLiteralMethodName,
|
||||||
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
if (generatedTagHelperContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generatedTagHelperContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(executeMethodName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
|
nameof(executeMethodName));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(writeMethodName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
|
nameof(writeMethodName));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(writeLiteralMethodName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
|
nameof(writeLiteralMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratedTagHelperContext = generatedTagHelperContext;
|
||||||
|
|
||||||
|
WriteMethodName = writeMethodName;
|
||||||
|
WriteLiteralMethodName = writeLiteralMethodName;
|
||||||
|
ExecuteMethodName = executeMethodName;
|
||||||
|
|
||||||
|
WriteToMethodName = null;
|
||||||
|
WriteLiteralToMethodName = null;
|
||||||
|
TemplateTypeName = null;
|
||||||
|
DefineSectionMethodName = null;
|
||||||
|
|
||||||
|
BeginWriteAttributeMethodName = DefaultBeginWriteAttributeMethodName;
|
||||||
|
BeginWriteAttributeToMethodName = DefaultBeginWriteAttributeToMethodName;
|
||||||
|
EndWriteAttributeMethodName = DefaultEndWriteAttributeMethodName;
|
||||||
|
EndWriteAttributeToMethodName = DefaultEndWriteAttributeToMethodName;
|
||||||
|
WriteAttributeValueMethodName = DefaultWriteAttributeValueMethodName;
|
||||||
|
WriteAttributeValueToMethodName = DefaultWriteAttributeValueToMethodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneratedClassContext(
|
||||||
|
string executeMethodName,
|
||||||
|
string writeMethodName,
|
||||||
|
string writeLiteralMethodName,
|
||||||
|
string writeToMethodName,
|
||||||
|
string writeLiteralToMethodName,
|
||||||
|
string templateTypeName,
|
||||||
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
generatedTagHelperContext)
|
||||||
|
{
|
||||||
|
WriteToMethodName = writeToMethodName;
|
||||||
|
WriteLiteralToMethodName = writeLiteralToMethodName;
|
||||||
|
TemplateTypeName = templateTypeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneratedClassContext(
|
||||||
|
string executeMethodName,
|
||||||
|
string writeMethodName,
|
||||||
|
string writeLiteralMethodName,
|
||||||
|
string writeToMethodName,
|
||||||
|
string writeLiteralToMethodName,
|
||||||
|
string templateTypeName,
|
||||||
|
string defineSectionMethodName,
|
||||||
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
writeToMethodName,
|
||||||
|
writeLiteralToMethodName,
|
||||||
|
templateTypeName,
|
||||||
|
generatedTagHelperContext)
|
||||||
|
{
|
||||||
|
DefineSectionMethodName = defineSectionMethodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneratedClassContext(
|
||||||
|
string executeMethodName,
|
||||||
|
string writeMethodName,
|
||||||
|
string writeLiteralMethodName,
|
||||||
|
string writeToMethodName,
|
||||||
|
string writeLiteralToMethodName,
|
||||||
|
string templateTypeName,
|
||||||
|
string defineSectionMethodName,
|
||||||
|
string beginContextMethodName,
|
||||||
|
string endContextMethodName,
|
||||||
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
writeToMethodName,
|
||||||
|
writeLiteralToMethodName,
|
||||||
|
templateTypeName,
|
||||||
|
defineSectionMethodName,
|
||||||
|
generatedTagHelperContext)
|
||||||
|
{
|
||||||
|
BeginContextMethodName = beginContextMethodName;
|
||||||
|
EndContextMethodName = endContextMethodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required Items
|
||||||
|
public string WriteMethodName { get; }
|
||||||
|
public string WriteLiteralMethodName { get; }
|
||||||
|
public string WriteToMethodName { get; }
|
||||||
|
public string WriteLiteralToMethodName { get; }
|
||||||
|
public string ExecuteMethodName { get; }
|
||||||
|
public GeneratedTagHelperContext GeneratedTagHelperContext { get; }
|
||||||
|
|
||||||
|
// Optional Items
|
||||||
|
public string BeginContextMethodName { get; set; }
|
||||||
|
public string EndContextMethodName { get; set; }
|
||||||
|
public string DefineSectionMethodName { get; set; }
|
||||||
|
public string TemplateTypeName { get; set; }
|
||||||
|
|
||||||
|
public string BeginWriteAttributeMethodName { get; set; }
|
||||||
|
public string BeginWriteAttributeToMethodName { get; set; }
|
||||||
|
public string EndWriteAttributeMethodName { get; set; }
|
||||||
|
public string EndWriteAttributeToMethodName { get; set; }
|
||||||
|
public string WriteAttributeValueMethodName { get; set; }
|
||||||
|
public string WriteAttributeValueToMethodName { get; set; }
|
||||||
|
|
||||||
|
public bool AllowSections
|
||||||
|
{
|
||||||
|
get { return !string.IsNullOrEmpty(DefineSectionMethodName); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowTemplates
|
||||||
|
{
|
||||||
|
get { return !string.IsNullOrEmpty(TemplateTypeName); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportsInstrumentation
|
||||||
|
{
|
||||||
|
get { return !string.IsNullOrEmpty(BeginContextMethodName) && !string.IsNullOrEmpty(EndContextMethodName); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (!(obj is GeneratedClassContext))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var other = (GeneratedClassContext)obj;
|
||||||
|
return string.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// Hash code should include only immutable properties.
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
|
||||||
|
hashCodeCombiner.Add(WriteMethodName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(WriteLiteralMethodName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(WriteToMethodName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(WriteLiteralToMethodName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(ExecuteMethodName, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
return hashCodeCombiner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(GeneratedClassContext left, GeneratedClassContext right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(GeneratedClassContext left, GeneratedClassContext right)
|
||||||
|
{
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains necessary information for the tag helper code generation process.
|
||||||
|
/// </summary>
|
||||||
|
public class GeneratedTagHelperContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="GeneratedTagHelperContext"/> with default values.
|
||||||
|
/// </summary>
|
||||||
|
public GeneratedTagHelperContext()
|
||||||
|
{
|
||||||
|
BeginAddHtmlAttributeValuesMethodName = "BeginAddHtmlAttributeValues";
|
||||||
|
EndAddHtmlAttributeValuesMethodName = "EndAddHtmlAttributeValues";
|
||||||
|
AddHtmlAttributeValueMethodName = "AddHtmlAttributeValue";
|
||||||
|
CreateTagHelperMethodName = "CreateTagHelper";
|
||||||
|
RunnerRunAsyncMethodName = "RunAsync";
|
||||||
|
ScopeManagerBeginMethodName = "Begin";
|
||||||
|
ScopeManagerEndMethodName = "End";
|
||||||
|
ExecutionContextAddMethodName = "Add";
|
||||||
|
ExecutionContextAddTagHelperAttributeMethodName = "AddTagHelperAttribute";
|
||||||
|
ExecutionContextAddMinimizedHtmlAttributeMethodName = "AddMinimizedHtmlAttribute";
|
||||||
|
ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute";
|
||||||
|
ExecutionContextOutputPropertyName = "Output";
|
||||||
|
FormatInvalidIndexerAssignmentMethodName = "FormatInvalidIndexerAssignment";
|
||||||
|
MarkAsHtmlEncodedMethodName = "Html.Raw";
|
||||||
|
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope";
|
||||||
|
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope";
|
||||||
|
RunnerTypeName = "TagHelperRunner";
|
||||||
|
ScopeManagerTypeName = "TagHelperScopeManager";
|
||||||
|
ExecutionContextTypeName = "TagHelperExecutionContext";
|
||||||
|
TagHelperContentTypeName = "TagHelperContent";
|
||||||
|
WriteTagHelperAsyncMethodName = "WriteTagHelperAsync";
|
||||||
|
WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync";
|
||||||
|
TagHelperContentGetContentMethodName = "GetContent";
|
||||||
|
HtmlEncoderPropertyName = "HtmlEncoder";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to begin the addition of unbound, complex tag helper attributes to
|
||||||
|
/// TagHelperExecutionContexts.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Method signature should be
|
||||||
|
/// <code>
|
||||||
|
/// public void BeginAddHtmlAttributeValues(
|
||||||
|
/// TagHelperExecutionContext executionContext,
|
||||||
|
/// string attributeName)
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public string BeginAddHtmlAttributeValuesMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Method name used to end addition of unbound, complex tag helper attributes to TagHelperExecutionContexts.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Method signature should be
|
||||||
|
/// <code>
|
||||||
|
/// public void EndAddHtmlAttributeValues(
|
||||||
|
/// TagHelperExecutionContext executionContext)
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public string EndAddHtmlAttributeValuesMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Method name used to add individual components of an unbound, complex tag helper attribute to
|
||||||
|
/// TagHelperExecutionContexts.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Method signature:
|
||||||
|
/// <code>
|
||||||
|
/// public void AddHtmlAttributeValues(
|
||||||
|
/// string prefix,
|
||||||
|
/// int prefixOffset,
|
||||||
|
/// string value,
|
||||||
|
/// int valueOffset,
|
||||||
|
/// int valueLength,
|
||||||
|
/// bool isLiteral)
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public string AddHtmlAttributeValueMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to create a tag helper.
|
||||||
|
/// </summary>
|
||||||
|
public string CreateTagHelperMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="RunnerTypeName"/> method used to run tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string RunnerRunAsyncMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to start a scope.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerBeginMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to end a scope.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerEndMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helper attributes.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddTagHelperAttributeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add minimized HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddMinimizedHtmlAttributeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddHtmlAttributeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The property accessor for the tag helper's output.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextOutputPropertyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to format an error message about using an indexer when the tag helper property
|
||||||
|
/// is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Method signature should be
|
||||||
|
/// <code>
|
||||||
|
/// public string FormatInvalidIndexerAssignment(
|
||||||
|
/// string attributeName, // Name of the HTML attribute associated with the indexer.
|
||||||
|
/// string tagHelperTypeName, // Full name of the tag helper type.
|
||||||
|
/// string propertyName) // Dictionary property in the tag helper.
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public string FormatInvalidIndexerAssignmentMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to wrap a <see cref="string"/> value and mark it as HTML-encoded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used together with <see cref="ExecutionContextAddHtmlAttributeMethodName"/>.</remarks>
|
||||||
|
public string MarkAsHtmlEncodedMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to start a new writing scope.
|
||||||
|
/// </summary>
|
||||||
|
public string StartTagHelperWritingScopeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to end a writing scope.
|
||||||
|
/// </summary>
|
||||||
|
public string EndTagHelperWritingScopeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type used to run tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string RunnerTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type used to create scoped <see cref="ExecutionContextTypeName"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type describing a specific tag helper scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Contains information about in-scope tag helpers, HTML attributes, and the tag helpers' output.
|
||||||
|
/// </remarks>
|
||||||
|
public string ExecutionContextTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type containing tag helper content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Contains the data returned by EndTagHelperWriteScope().
|
||||||
|
/// </remarks>
|
||||||
|
public string TagHelperContentTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to write <see cref="ExecutionContextTypeName"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string WriteTagHelperAsyncMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to write <see cref="ExecutionContextTypeName"/> to a specified
|
||||||
|
/// <see cref="System.IO.TextWriter"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string WriteTagHelperToAsyncMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the property containing the <c>IHtmlEncoder</c>.
|
||||||
|
/// </summary>
|
||||||
|
public string HtmlEncoderPropertyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to convert a <c>TagHelperContent</c> into a <see cref="string"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string TagHelperContentGetContentMethodName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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 Microsoft.AspNet.Razor.Chunks;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The results of parsing and generating code for a Razor document.
|
||||||
|
/// </summary>
|
||||||
|
public class GeneratorResults : ParserResults
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="GeneratorResults"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parserResults">The results of parsing a document.</param>
|
||||||
|
/// <param name="codeGeneratorResult">The results of generating code for the document.</param>
|
||||||
|
/// <param name="chunkTree">A <see cref="ChunkTree"/> for the document.</param>
|
||||||
|
public GeneratorResults(ParserResults parserResults,
|
||||||
|
CodeGeneratorResult codeGeneratorResult,
|
||||||
|
ChunkTree chunkTree)
|
||||||
|
: this(parserResults.Document,
|
||||||
|
parserResults.TagHelperDescriptors,
|
||||||
|
parserResults.ErrorSink,
|
||||||
|
codeGeneratorResult,
|
||||||
|
chunkTree)
|
||||||
|
{
|
||||||
|
if (parserResults == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(parserResults));
|
||||||
|
}
|
||||||
|
if (codeGeneratorResult == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(codeGeneratorResult));
|
||||||
|
}
|
||||||
|
if (chunkTree == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(chunkTree));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="GeneratorResults"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="document">The <see cref="Block"/> for the syntax tree.</param>
|
||||||
|
/// <param name="tagHelperDescriptors">
|
||||||
|
/// The <see cref="TagHelperDescriptor"/>s that apply to the current Razor document.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="errorSink">
|
||||||
|
/// The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered when parsing the
|
||||||
|
/// current Razor document.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="codeGeneratorResult">The results of generating code for the document.</param>
|
||||||
|
/// <param name="chunkTree">A <see cref="ChunkTree"/> for the document.</param>
|
||||||
|
public GeneratorResults(Block document,
|
||||||
|
IEnumerable<TagHelperDescriptor> tagHelperDescriptors,
|
||||||
|
ErrorSink errorSink,
|
||||||
|
CodeGeneratorResult codeGeneratorResult,
|
||||||
|
ChunkTree chunkTree)
|
||||||
|
: base(document, tagHelperDescriptors, errorSink)
|
||||||
|
{
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(document));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagHelperDescriptors == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagHelperDescriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorSink == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorSink));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeGeneratorResult == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(codeGeneratorResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkTree == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(chunkTree));
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratedCode = codeGeneratorResult.Code;
|
||||||
|
DesignTimeLineMappings = codeGeneratorResult.DesignTimeLineMappings;
|
||||||
|
ChunkTree = chunkTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The generated code for the document.
|
||||||
|
/// </summary>
|
||||||
|
public string GeneratedCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="LineMapping"/>s used to project code from a file during design time.
|
||||||
|
/// </summary>
|
||||||
|
public IList<LineMapping> DesignTimeLineMappings { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Chunks.ChunkTree"/> for the document.
|
||||||
|
/// </summary>
|
||||||
|
public ChunkTree ChunkTree { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class LineMapping
|
||||||
|
{
|
||||||
|
public LineMapping(MappingLocation documentLocation, MappingLocation generatedLocation)
|
||||||
|
{
|
||||||
|
DocumentLocation = documentLocation;
|
||||||
|
GeneratedLocation = generatedLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingLocation DocumentLocation { get; }
|
||||||
|
|
||||||
|
public MappingLocation GeneratedLocation { get; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as LineMapping;
|
||||||
|
if (ReferenceEquals(other, null))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DocumentLocation.Equals(other.DocumentLocation) &&
|
||||||
|
GeneratedLocation.Equals(other.GeneratedLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
hashCodeCombiner.Add(DocumentLocation);
|
||||||
|
hashCodeCombiner.Add(GeneratedLocation);
|
||||||
|
|
||||||
|
return hashCodeCombiner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(LineMapping left, LineMapping right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(left, right))
|
||||||
|
{
|
||||||
|
// Exact equality e.g. both objects are null.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(left, null))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(LineMapping left, LineMapping right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(left, right))
|
||||||
|
{
|
||||||
|
// Exact equality e.g. both objects are null.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(left, null))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentUICulture, "{0} -> {1}", DocumentLocation, GeneratedLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class LineMappingManager
|
||||||
|
{
|
||||||
|
public LineMappingManager()
|
||||||
|
{
|
||||||
|
Mappings = new List<LineMapping>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LineMapping> Mappings { get; }
|
||||||
|
|
||||||
|
public void AddMapping(MappingLocation documentLocation, MappingLocation generatedLocation)
|
||||||
|
{
|
||||||
|
Mappings.Add(new LineMapping(documentLocation, generatedLocation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||||
|
{
|
||||||
|
public class MappingLocation
|
||||||
|
{
|
||||||
|
public MappingLocation()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingLocation(SourceLocation location, int contentLength)
|
||||||
|
{
|
||||||
|
ContentLength = contentLength;
|
||||||
|
AbsoluteIndex = location.AbsoluteIndex;
|
||||||
|
LineIndex = location.LineIndex;
|
||||||
|
CharacterIndex = location.CharacterIndex;
|
||||||
|
FilePath = location.FilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ContentLength { get; }
|
||||||
|
|
||||||
|
public int AbsoluteIndex { get; }
|
||||||
|
|
||||||
|
public int LineIndex { get; }
|
||||||
|
|
||||||
|
public int CharacterIndex { get; }
|
||||||
|
|
||||||
|
public string FilePath { get; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as MappingLocation;
|
||||||
|
if (ReferenceEquals(other, null))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
|
||||||
|
AbsoluteIndex == other.AbsoluteIndex &&
|
||||||
|
ContentLength == other.ContentLength &&
|
||||||
|
LineIndex == other.LineIndex &&
|
||||||
|
CharacterIndex == other.CharacterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
hashCodeCombiner.Add(FilePath, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(AbsoluteIndex);
|
||||||
|
hashCodeCombiner.Add(ContentLength);
|
||||||
|
hashCodeCombiner.Add(LineIndex);
|
||||||
|
hashCodeCombiner.Add(CharacterIndex);
|
||||||
|
|
||||||
|
return hashCodeCombiner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
CultureInfo.CurrentCulture, "({0}:{1},{2} [{3}] {4})",
|
||||||
|
AbsoluteIndex,
|
||||||
|
LineIndex,
|
||||||
|
CharacterIndex,
|
||||||
|
ContentLength,
|
||||||
|
FilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(MappingLocation left, MappingLocation right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(left, right))
|
||||||
|
{
|
||||||
|
// Exact equality e.g. both objects are null.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(left, null))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(MappingLocation left, MappingLocation right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(left, right))
|
||||||
|
{
|
||||||
|
// Exact equality e.g. both objects are null.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(left, null))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue