diff --git a/src/Microsoft.AspNet.Razor.Runtime/AssemblyInfo.cs b/src/Microsoft.AspNet.Razor.Runtime/AssemblyInfo.cs
new file mode 100644
index 0000000000..d36bab93fd
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Runtime.Test")]
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/NotNullArgument.cs b/src/Microsoft.AspNet.Razor.Runtime/NotNullArgument.cs
new file mode 100644
index 0000000000..4b9755cce9
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/NotNullArgument.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Razor.Runtime
+{
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
+ internal sealed class NotNullAttribute : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..5a0fccdfdb
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
@@ -0,0 +1,82 @@
+//
+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);
+
+ ///
+ /// Invalid tag helper directive look up text '{0}'. The correct look up text formats are:
+ /// "assemblyName"
+ /// "typeName, assemblyName"
+ ///
+ internal static string TagHelperDescriptorResolver_InvalidTagHelperLookupText
+ {
+ get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"); }
+ }
+
+ ///
+ /// Invalid tag helper directive look up text '{0}'. The correct look up text formats are:
+ /// "assemblyName"
+ /// "typeName, assemblyName"
+ ///
+ internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"), p0);
+ }
+
+ ///
+ /// Cannot resolve TagHelper containing assembly '{0}'. Error: '{1}'.
+ ///
+ internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly
+ {
+ get { return GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"); }
+ }
+
+ ///
+ /// Cannot resolve TagHelper containing assembly '{0}'. Error: '{1}'.
+ ///
+ internal static string FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"), p0, p1);
+ }
+
+ ///
+ /// Tag helper directive assembly name cannot be null or empty.
+ ///
+ internal static string TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull
+ {
+ get { return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull"); }
+ }
+
+ ///
+ /// Tag helper directive assembly name cannot be null or empty.
+ ///
+ internal static string FormatTagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull()
+ {
+ return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull");
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
new file mode 100644
index 0000000000..8a7d783249
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Invalid tag helper directive look up text '{0}'. The correct look up text formats are:
+"assemblyName"
+"typeName, assemblyName"
+
+
+ Cannot resolve TagHelper containing assembly '{0}'. Error: '{1}'.
+
+
+ Tag helper directive assembly name cannot be null or empty.
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs
new file mode 100644
index 0000000000..badb7227e0
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
+{
+ public interface ITagHelper
+ {
+ // TODO: Will be implemented in https://github.com/aspnet/razor/issues/154
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
new file mode 100644
index 0000000000..c7813265ca
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNet.Razor.TagHelpers;
+
+namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
+{
+ ///
+ /// Factory for s from s.
+ ///
+ public static class TagHelperDescriptorFactory
+ {
+ private const string TagHelperNameEnding = "TagHelper";
+
+ // TODO: Investigate if we should cache TagHelperDescriptors for types:
+ // https://github.com/aspnet/Razor/issues/165
+
+ ///
+ /// Creates a from the given .
+ ///
+ /// The type to create a from.
+ /// A that describes the given .
+ public static TagHelperDescriptor CreateDescriptor(Type type)
+ {
+ var tagName = GetTagName(type);
+ var typeName = type.FullName;
+ var attributeDescriptors = GetAttributeDescriptors(type);
+ var contentBehavior = GetContentBehavior(type);
+
+ return new TagHelperDescriptor(tagName,
+ typeName,
+ contentBehavior,
+ attributeDescriptors);
+ }
+
+ // TODO: Make this method support TagNameAttribute tag names: https://github.com/aspnet/Razor/issues/120
+ private static string GetTagName(Type tagHelperType)
+ {
+ var name = tagHelperType.Name;
+
+ if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
+ {
+ name = name.Substring(0, name.Length - TagHelperNameEnding.Length);
+ }
+
+ return name;
+ }
+
+ private static IEnumerable GetAttributeDescriptors(Type type)
+ {
+ var typeInfo = type.GetTypeInfo();
+ var properties = typeInfo.DeclaredProperties.Where(IsValidProperty);
+ var attributeDescriptors = properties.Select(ToAttributeDescriptor);
+
+ return attributeDescriptors;
+ }
+
+ // TODO: Make the HTML attribute name support names from a AttributeNameAttribute:
+ // https://github.com/aspnet/Razor/issues/121
+ private static TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property)
+ {
+ return new TagHelperAttributeDescriptor(property.Name, property);
+ }
+
+ // TODO: Make the content behavior pull from a ContentBehaviorAttribute: https://github.com/aspnet/Razor/issues/122
+ private static ContentBehavior GetContentBehavior(Type type)
+ {
+ return ContentBehavior.None;
+ }
+
+ private static bool IsValidProperty(PropertyInfo property)
+ {
+ return property.GetMethod != null &&
+ property.GetMethod.IsPublic &&
+ property.SetMethod != null &&
+ property.SetMethod.IsPublic;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs
new file mode 100644
index 0000000000..3cd8a5c2c9
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNet.Razor.TagHelpers;
+
+namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
+{
+ ///
+ /// Used to resolve s.
+ ///
+ public class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
+ {
+ private readonly TagHelperTypeResolver _typeResolver;
+
+ // internal for testing
+ internal TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver)
+ {
+ _typeResolver = typeResolver;
+ }
+
+ ///
+ /// Instantiates a new instance of the class.
+ ///
+ public TagHelperDescriptorResolver()
+ : this(new TagHelperTypeResolver())
+ {
+ }
+
+ ///
+ public IEnumerable Resolve(string lookupText)
+ {
+ var lookupStrings = lookupText?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ // Ensure that we have valid lookupStrings to work with. Valid formats are:
+ // "assemblyName"
+ // "typeName, assemblyName"
+ if (string.IsNullOrEmpty(lookupText) ||
+ (lookupStrings.Length != 1 && lookupStrings.Length != 2))
+ {
+ throw new ArgumentException(
+ Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
+ nameof(lookupText));
+ }
+
+ var tagHelperTypes = ResolveTagHelperTypes(lookupStrings);
+
+ // Convert types to TagHelperDescriptors
+ var descriptors = tagHelperTypes.Select(TagHelperDescriptorFactory.CreateDescriptor);
+
+ return descriptors;
+ }
+
+ private IEnumerable ResolveTagHelperTypes(string[] lookupStrings)
+ {
+ // Grab the assembly name from the lookup text strings. Due to our supported lookupText formats it will
+ // always be the last element provided.
+ var assemblyName = lookupStrings.Last().Trim();
+
+ // Resolve valid tag helper types from the assembly.
+ var types = _typeResolver.Resolve(assemblyName);
+
+ // Check if the lookupText specifies a type to search for.
+ if (lookupStrings.Length == 2)
+ {
+ // The user provided a type name retrieve the value and trim it.
+ var typeName = lookupStrings[0].Trim();
+
+ types = types.Where(type =>
+ string.Equals(type.Namespace + "." + type.Name, typeName, StringComparison.Ordinal));
+ }
+
+ return types;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperTypeResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperTypeResolver.cs
new file mode 100644
index 0000000000..ca20041aa1
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperTypeResolver.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
+{
+ ///
+ /// Class that locates valid s within an assembly.
+ ///
+ public class TagHelperTypeResolver
+ {
+ private static readonly TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
+
+ ///
+ /// Instantiates a new instance of the class.
+ ///
+ public TagHelperTypeResolver()
+ {
+ }
+
+ ///
+ /// Loads an using the given and resolves
+ /// all valid s.
+ ///
+ /// The name of an to search.
+ /// An of valid s.
+ public IEnumerable Resolve(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(
+ Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
+ nameof(name));
+ }
+
+ var assemblyName = new AssemblyName(name);
+ var libraryTypes = GetLibraryDefinedTypes(assemblyName);
+ var validTagHelpers = libraryTypes.Where(IsTagHelper);
+
+ // Convert from TypeInfo[] to Type[]
+ return validTagHelpers.Select(type => type.AsType());
+ }
+
+ // Internal for testing, don't want to be loading assemblies during a test.
+ internal virtual IEnumerable GetLibraryDefinedTypes(AssemblyName assemblyName)
+ {
+ try
+ {
+ var assembly = Assembly.Load(assemblyName);
+
+ return assembly.DefinedTypes;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(assemblyName.Name,
+ ex.Message));
+ }
+ }
+
+ private static bool IsTagHelper(TypeInfo typeInfo)
+ {
+ return typeInfo.IsPublic &&
+ !typeInfo.IsAbstract &&
+ !typeInfo.IsGenericType &&
+ !typeInfo.IsNested &&
+ ITagHelperTypeInfo.IsAssignableFrom(typeInfo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperRegistrationVisitor.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperRegistrationVisitor.cs
index b59342e39d..3681f85f01 100644
--- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperRegistrationVisitor.cs
+++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperRegistrationVisitor.cs
@@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
throw new InvalidOperationException(
RazorResources.FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver(
SyntaxConstants.CSharp.AddTagHelperKeyword,
- nameof(TagHelperDescriptorResolver),
+ nameof(ITagHelperDescriptorResolver),
nameof(RazorParser)));
}
diff --git a/src/Microsoft.AspNet.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Razor/Properties/AssemblyInfo.cs
index e433d6ddfa..e4276f6d3c 100644
--- a/src/Microsoft.AspNet.Razor/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.AspNet.Razor/Properties/AssemblyInfo.cs
@@ -3,4 +3,5 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Test")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Test")]
+[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Runtime.Test")]
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor/RazorEngineHost.cs b/src/Microsoft.AspNet.Razor/RazorEngineHost.cs
index ca38205e8c..8953e8d132 100644
--- a/src/Microsoft.AspNet.Razor/RazorEngineHost.cs
+++ b/src/Microsoft.AspNet.Razor/RazorEngineHost.cs
@@ -113,6 +113,11 @@ namespace Microsoft.AspNet.Razor
///
public virtual RazorCodeLanguage CodeLanguage { get; protected set; }
+ ///
+ /// The used to resolve s.
+ ///
+ public virtual ITagHelperDescriptorResolver TagHelperDescriptorResolver { get; protected set; }
+
///
/// Boolean indicating if instrumentation code should be injected into the output page
///
@@ -161,12 +166,6 @@ namespace Microsoft.AspNet.Razor
return null;
}
- // TODO: Document this as part of https://github.com/aspnet/Razor/issues/99
- public virtual ITagHelperDescriptorResolver CreateTagHelperDescriptorResolver()
- {
- return new TagHelperDescriptorResolver();
- }
-
///
/// Gets an instance of the code parser and is provided an opportunity to decorate or replace it
///
diff --git a/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs b/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs
index 5bb72bba22..548107c142 100644
--- a/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs
+++ b/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs
@@ -271,11 +271,10 @@ namespace Microsoft.AspNet.Razor
{
var codeParser = Host.CodeLanguage.CreateCodeParser();
var markupParser = Host.CreateMarkupParser();
- var tagHelperDescriptorResolver = Host.CreateTagHelperDescriptorResolver();
return new RazorParser(Host.DecorateCodeParser(codeParser),
Host.DecorateMarkupParser(markupParser),
- tagHelperDescriptorResolver)
+ Host.TagHelperDescriptorResolver)
{
DesignTimeMode = Host.DesignTimeMode
};
diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs
index a479278f09..747242a8a0 100644
--- a/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs
+++ b/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs
@@ -5,10 +5,19 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Razor.TagHelpers
{
- // TODO: Document this class as part of https://github.com/aspnet/Razor/issues/99
-
+ ///
+ /// Contract used to resolve s.
+ ///
public interface ITagHelperDescriptorResolver
{
+ ///
+ /// Resolves s matching the given .
+ ///
+ ///
+ /// A used to find tag helper s.
+ ///
+ /// An of s matching
+ /// the given .
IEnumerable Resolve(string lookupText);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs
index 06b1fce0e5..bca5f415ce 100644
--- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs
+++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs
@@ -8,7 +8,7 @@ using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.TagHelpers
{
///
- /// Defines a an that is used to check equality between
+ /// An used to check equality between
/// two s.
///
public class TagHelperDescriptorComparer : IEqualityComparer
@@ -18,7 +18,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
///
public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer();
- private TagHelperDescriptorComparer()
+ // Internal for testing
+ internal TagHelperDescriptorComparer()
{
}
diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolver.cs
deleted file mode 100644
index 9b8f5901c7..0000000000
--- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolver.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Microsoft.AspNet.Razor.TagHelpers
-{
- // TODO: Implement this class as part of https://github.com/aspnet/Razor/issues/99
-
- public class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
- {
- public IEnumerable Resolve(string lookupText)
- {
- return Enumerable.Empty();
- }
- }
-}
\ No newline at end of file