Add Visual Studio specific RC1 binaries.

- This is needed for Visual Studio RC1 backwards compatibility.
This commit is contained in:
N. Taylor Mullen 2016-04-01 16:54:59 -07:00
parent 4212b7e713
commit 197d6a579f
233 changed files with 29592 additions and 3 deletions

View File

@ -5,7 +5,9 @@
],
"packages": {
"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)

View File

@ -1,7 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C0D6505-79B3-49D0-B4C3-176F0F1836ED}"
EndProject
@ -17,6 +16,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.
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}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -53,5 +64,7 @@ Global
{D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F}
{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
EndGlobal

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>&lt;summary&gt;</c> documentation for the given <paramref name="id"/>.
/// </summary>
/// <param name="id">The id to lookup.</param>
/// <returns><c>&lt;summary&gt;</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>&lt;remarks&gt;</c> documentation for the given <paramref name="id"/>.
/// </summary>
/// <param name="id">The id to lookup.</param>
/// <returns><c>&lt;remarks&gt;</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

View File

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

View File

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

View File

@ -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()
{
}
}
}

View File

@ -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>
/// &lt;my-tag-helper&gt;&lt;/my-tag-helper&gt;
/// &lt;!-- OR --&gt;
/// &lt;my-tag-helper /&gt;
/// </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>
/// &lt;my-tag-helper&gt;
/// &lt;!-- OR --&gt;
/// &lt;my-tag-helper /&gt;
/// </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; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.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";
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.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";
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.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();
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.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";
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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