From df81f8be57f59fcac7f963fe2b88836e6bc472a8 Mon Sep 17 00:00:00 2001 From: Crystal Qian Date: Wed, 31 Aug 2016 17:31:04 -0700 Subject: [PATCH] Added a view component tag helper descriptor factory (#5189) --- ...ComponentTagHelperDescriptorConventions.cs | 27 +++ ...ViewComponentTagHelperDescriptorFactory.cs | 118 +++++++++++ ...onentTagHelperDescriptorConventionsTest.cs | 63 ++++++ ...ComponentTagHelperDescriptorFactoryTest.cs | 193 ++++++++++++++++++ 4 files changed, 401 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs new file mode 100644 index 0000000000..36f42c9e01 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs @@ -0,0 +1,27 @@ +// 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.AspNetCore.Razor.Compilation.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host +{ + /// + /// A library of methods used to generate s for view components. + /// + public static class ViewComponentTagHelperDescriptorConventions + { + /// + /// The key in a containing + /// the short name of a view component. + /// + public static readonly string ViewComponentNameKey = "ViewComponentName"; + + /// + /// Indicates whether a represents a view component. + /// + /// The to check. + /// Whether a represents a view component. + public static bool IsViewComponentDescriptor(TagHelperDescriptor descriptor) => + descriptor != null && descriptor.PropertyBag.ContainsKey(ViewComponentNameKey); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs b/src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs new file mode 100644 index 0000000000..6ec113860c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs @@ -0,0 +1,118 @@ +// 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.AspNetCore.Mvc.Razor.Host; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; +using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Provides methods to create tag helper representations of view components. + /// + public class ViewComponentTagHelperDescriptorFactory + { + private readonly IViewComponentDescriptorProvider _descriptorProvider; + + /// + /// Creates a new , + /// then creates s for s + /// in the given . + /// + /// The provider of s. + public ViewComponentTagHelperDescriptorFactory(IViewComponentDescriptorProvider descriptorProvider) + { + if (descriptorProvider == null) + { + throw new ArgumentNullException(nameof(descriptorProvider)); + } + + _descriptorProvider = descriptorProvider; + } + + /// + /// Creates representations of s + /// in an represented by the given . + /// + /// The name of the assembly containing + /// the s to translate. + /// A , + /// one for each . + public IEnumerable CreateDescriptors(string assemblyName) + { + if (assemblyName == null) + { + throw new ArgumentNullException(nameof(assemblyName)); + } + + var viewComponentDescriptors = _descriptorProvider + .GetViewComponents() + .Where(viewComponent => string.Equals(assemblyName, viewComponent.TypeInfo.Assembly.GetName().Name, + StringComparison.Ordinal)); + + var tagHelperDescriptors = viewComponentDescriptors + .Select(viewComponentDescriptor => CreateDescriptor(viewComponentDescriptor)); + + return tagHelperDescriptors; + } + + private TagHelperDescriptor CreateDescriptor(ViewComponentDescriptor viewComponentDescriptor) + { + var assemblyName = viewComponentDescriptor.TypeInfo.Assembly.GetName().Name; + var tagName = GetTagName(viewComponentDescriptor); + var typeName = $"__Generated__{viewComponentDescriptor.ShortName}ViewComponentTagHelper"; + + var tagHelperDescriptor = new TagHelperDescriptor + { + TagName = tagName, + TypeName = typeName, + AssemblyName = assemblyName + }; + + SetAttributeDescriptors(viewComponentDescriptor, tagHelperDescriptor); + + tagHelperDescriptor.PropertyBag.Add( + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, viewComponentDescriptor.ShortName); + + return tagHelperDescriptor; + } + + private void SetAttributeDescriptors(ViewComponentDescriptor viewComponentDescriptor, + TagHelperDescriptor tagHelperDescriptor) + { + var methodParameters = viewComponentDescriptor.MethodInfo.GetParameters(); + var attributeDescriptors = new List(); + + foreach (var parameter in methodParameters) + { + var lowerKebabName = TagHelperDescriptorFactory.ToHtmlCase(parameter.Name); + var descriptor = new TagHelperAttributeDescriptor + { + Name = lowerKebabName, + PropertyName = parameter.Name, + TypeName = parameter.ParameterType.FullName + }; + + descriptor.IsEnum = parameter.ParameterType.GetTypeInfo().IsEnum; + descriptor.IsIndexer = false; + + attributeDescriptors.Add(descriptor); + } + + tagHelperDescriptor.Attributes = attributeDescriptors; + tagHelperDescriptor.RequiredAttributes = tagHelperDescriptor.Attributes.Select( + attribute => new TagHelperRequiredAttributeDescriptor + { + Name = attribute.Name + }); + } + + private string GetTagName(ViewComponentDescriptor descriptor) => + $"vc:{TagHelperDescriptorFactory.ToHtmlCase(descriptor.ShortName)}"; + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs new file mode 100644 index 0000000000..5f399fafe0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs @@ -0,0 +1,63 @@ +// 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.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test +{ + public class ViewComponentTagHelperDescriptorConventionsTest + { + [Fact] + public void IsViewComponentDescriptor_ReturnsFalseForInvalidDescriptor() + { + //Arrange + var tagHelperDescriptor = CreateTagHelperDescriptor(); + + // Act + var isViewComponentDescriptor = ViewComponentTagHelperDescriptorConventions + .IsViewComponentDescriptor(tagHelperDescriptor); + + // Assert + Assert.False(isViewComponentDescriptor); + } + + [Fact] + public void IsViewComponentDescriptor_ReturnsTrueForValidDescriptor() + { + // Arrange + var descriptor = CreateViewComponentTagHelperDescriptor(); + + // Act + var isViewComponentDescriptor = ViewComponentTagHelperDescriptorConventions + .IsViewComponentDescriptor(descriptor); + + // Assert + Assert.True(isViewComponentDescriptor); + } + + private static TagHelperDescriptor CreateTagHelperDescriptor() + { + var descriptor = new TagHelperDescriptor + { + TagName = "tag-name", + TypeName = "TypeName", + AssemblyName = "AssemblyName", + }; + + return descriptor; + } + + private static TagHelperDescriptor CreateViewComponentTagHelperDescriptor() + { + var descriptor = CreateTagHelperDescriptor(); + descriptor.PropertyBag.Add( + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, + "ViewComponentName"); + + return descriptor; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs new file mode 100644 index 0000000000..93cd3f1bb8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs @@ -0,0 +1,193 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Host; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Test.ViewComponentTagHelpers +{ + public class ViewComponentTagHelperDescriptorFactoryTest + { + public static TheoryData AssemblyData + { + get + { + var provider = new TestViewComponentDescriptorProvider(); + + var assemblyOne = "Microsoft.AspNetCore.Mvc.Razor"; + var assemblyTwo = "Microsoft.AspNetCore.Mvc.Razor.Test"; + var assemblyNone = string.Empty; + + return new TheoryData> + { + { assemblyOne, new [] { provider.GetTagHelperDescriptorOne() } }, + { assemblyTwo, new [] { provider.GetTagHelperDescriptorTwo() } }, + { assemblyNone, Enumerable.Empty() } + }; + } + } + + [Theory] + [MemberData(nameof(AssemblyData))] + public void CreateDescriptors_ReturnsCorrectDescriptors( + string assemblyName, + IEnumerable expectedDescriptors) + { + // Arrange + var provider = new TestViewComponentDescriptorProvider(); + var factory = new ViewComponentTagHelperDescriptorFactory(provider); + + // Act + var descriptors = factory.CreateDescriptors(assemblyName); + + // Assert + Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.Default); + } + + // Test invokes are needed for method creation in TestViewComponentDescriptorProvider. + public enum TestEnum + { + A = 1, + B = 2, + C = 3 + } + + public void TestInvokeOne(string foo, string bar) + { + } + + public void TestInvokeTwo(TestEnum testEnum, Dictionary testDictionary, int baz = 5) + { + } + + private class TestViewComponentDescriptorProvider : IViewComponentDescriptorProvider + { + private readonly ViewComponentDescriptor _viewComponentDescriptorOne = new ViewComponentDescriptor + { + DisplayName = "OneDisplayName", + FullName = "OneViewComponent", + ShortName = "One", + MethodInfo = typeof(ViewComponentTagHelperDescriptorFactoryTest) + .GetMethod(nameof(ViewComponentTagHelperDescriptorFactoryTest.TestInvokeOne)), + TypeInfo = typeof(ViewComponentTagHelperDescriptorFactory).GetTypeInfo() + }; + + private readonly ViewComponentDescriptor _viewComponentDescriptorTwo = new ViewComponentDescriptor + { + DisplayName = "TwoDisplayName", + FullName = "TwoViewComponent", + ShortName = "Two", + MethodInfo = typeof(ViewComponentTagHelperDescriptorFactoryTest) + .GetMethod(nameof(ViewComponentTagHelperDescriptorFactoryTest.TestInvokeTwo)), + TypeInfo = typeof(ViewComponentTagHelperDescriptorFactoryTest).GetTypeInfo() + }; + + public TagHelperDescriptor GetTagHelperDescriptorOne() + { + var descriptor = new TagHelperDescriptor + { + TagName = "vc:one", + TypeName = "__Generated__OneViewComponentTagHelper", + AssemblyName = "Microsoft.AspNetCore.Mvc.Razor", + Attributes = new List + { + new TagHelperAttributeDescriptor + { + Name = "foo", + PropertyName = "foo", + TypeName = typeof(string).FullName + }, + new TagHelperAttributeDescriptor + { + Name = "bar", + PropertyName = "bar", + TypeName = typeof(string).FullName + } + }, + RequiredAttributes = new List + { + new TagHelperRequiredAttributeDescriptor + { + Name = "foo" + }, + new TagHelperRequiredAttributeDescriptor + { + Name = "bar" + } + } + }; + + descriptor.PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "One"); + return descriptor; + } + + public TagHelperDescriptor GetTagHelperDescriptorTwo() + { + var descriptor = new TagHelperDescriptor + { + TagName = "vc:two", + TypeName = "__Generated__TwoViewComponentTagHelper", + AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Test", + Attributes = new List + { + new TagHelperAttributeDescriptor + { + Name = "test-enum", + PropertyName = "testEnum", + TypeName = typeof(TestEnum).FullName, + IsEnum = true + }, + + new TagHelperAttributeDescriptor + { + Name = "test-dictionary", + PropertyName = "testDictionary", + TypeName = typeof(Dictionary).FullName + }, + + new TagHelperAttributeDescriptor + { + Name = "baz", + PropertyName = "baz", + TypeName = typeof(int).FullName + } + }, + RequiredAttributes = new List + { + new TagHelperRequiredAttributeDescriptor + { + Name = "test-enum" + }, + + new TagHelperRequiredAttributeDescriptor + { + Name = "test-dictionary" + }, + + new TagHelperRequiredAttributeDescriptor + { + Name = "baz" + } + } + }; + + descriptor.PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "Two"); + return descriptor; + } + + public IEnumerable GetViewComponents() + { + return new List + { + _viewComponentDescriptorOne, + _viewComponentDescriptorTwo + }; + } + } + } +}