From 05392cbf3559c86c4d5080d2a57939bcb2dc3483 Mon Sep 17 00:00:00 2001 From: Crystal Qian Date: Wed, 7 Sep 2016 20:30:09 -0400 Subject: [PATCH] Added view component tag helper code generator. (#5195) This addresses #1051. There is one more pull request that needs to be completed/merged (for `CompositeTagHelperDescriptorResolver` and friends). After that, runtime should work! --- .../MvcApplicationBuilderExtensions.cs | 3 - .../MvcCoreServiceCollectionExtensions.cs | 7 - .../Filters/MiddlewareFilterAttribute.cs | 54 -- .../Internal/IMiddlewareFilterFeature.cs | 19 - .../Internal/MiddlewareFilter.cs | 46 -- .../Internal/MiddlewareFilterBuilder.cs | 88 --- .../MiddlewareFilterConfigurationProvider.cs | 117 ---- .../Internal/MiddlewareFilterFeature.cs | 14 - .../Properties/Resources.Designer.cs | 96 ---- .../Resources.resx | 18 - .../GeneratedViewComponentTagHelperContext.cs | 48 ++ .../Internal/TagHelperChunkDecorator.cs | 69 +++ .../ViewComponentTagHelperChunkVisitor.cs | 190 +++++++ .../MvcCSharpCodeGenerator.cs | 16 + ...ComponentTagHelperDescriptorConventions.cs | 27 - ...ViewComponentTagHelperDescriptorFactory.cs | 118 ---- .../InputTagHelper.cs | 51 +- .../MvcApplicationBuilderExtensionsTest.cs | 6 +- .../Filters/MiddlewareFilterAttributeTest.cs | 63 --- .../Internal/MiddlewareFilterBuilderTest.cs | 157 ------ ...ddlewareFilterConfigurationProviderTest.cs | 178 ------ .../Internal/MiddlewareFilterTest.cs | 516 ------------------ .../FiltersTest.cs | 18 - .../Internal/ChunkVisitorTestFactory.cs | 124 +++++ .../Internal/TagHelperChunkDecoratorTest.cs | 67 +++ .../ViewComponentTagHelperChunkVisitorTest.cs | 61 +++ .../GeneratedViewComponentTagHelperClasses.cs | 38 ++ ...onentTagHelperDescriptorConventionsTest.cs | 63 --- ...ComponentTagHelperDescriptorFactoryTest.cs | 193 ------- .../InputTagHelperTest.cs | 157 ++---- .../MiddlewareFilterTestController.cs | 25 - .../Filters/TestResourceFilter.cs | 95 ---- .../FiltersWebSite/LocalizationPipeline.cs | 32 -- .../RouteDataRequestCultureProvider.cs | 72 --- 34 files changed, 670 insertions(+), 2176 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/GeneratedViewComponentTagHelperContext.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/TagHelperChunkDecorator.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ViewComponentTagHelperChunkVisitor.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ChunkVisitorTestFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/TagHelperChunkDecoratorTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ViewComponentTagHelperChunkVisitorTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs delete mode 100644 test/WebSites/FiltersWebSite/Controllers/MiddlewareFilterTestController.cs delete mode 100644 test/WebSites/FiltersWebSite/Filters/TestResourceFilter.cs delete mode 100644 test/WebSites/FiltersWebSite/LocalizationPipeline.cs delete mode 100644 test/WebSites/FiltersWebSite/RouteDataRequestCultureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 2dcbcd8fe4..03451d62a1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -85,9 +85,6 @@ namespace Microsoft.AspNetCore.Builder "ConfigureServices(...)")); } - var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService(); - middlewarePipelineBuilder.ApplicationBuilder = app.New(); - var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService(), diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 22e6a5deb9..80f72cf352 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -222,13 +222,6 @@ namespace Microsoft.Extensions.DependencyInjection // services.TryAddSingleton(); // Only one per app services.TryAddTransient(); // Many per app - - // - // Middleware pipeline filter related - // - services.TryAddSingleton(); - // This maintains a cache of middleware pipelines, so it needs to be a singleton - services.TryAddSingleton(); } private static void ConfigureDefaultServices(IServiceCollection services) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs deleted file mode 100644 index f88fd1a4c0..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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.Filters; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Mvc -{ - /// - /// Executes a middleware pipeline provided the by the . - /// The middleware pipeline will be treated as an async resource filter. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class MiddlewareFilterAttribute : Attribute, IFilterFactory, IOrderedFilter - { - /// - /// Instantiates a new instance of . - /// - /// A type which configures a middleware pipeline. - public MiddlewareFilterAttribute(Type configurationType) - { - if (configurationType == null) - { - throw new ArgumentNullException(nameof(configurationType)); - } - - ConfigurationType = configurationType; - } - - public Type ConfigurationType { get; } - - /// - public int Order { get; set; } - - /// - public bool IsReusable { get; } = true; - - /// - public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) - { - if (serviceProvider == null) - { - throw new ArgumentNullException(nameof(serviceProvider)); - } - - var middlewarePipelineService = serviceProvider.GetRequiredService(); - var pipeline = middlewarePipelineService.GetPipeline(ConfigurationType); - - return new MiddlewareFilter(pipeline); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs deleted file mode 100644 index 43af488b8a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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.Mvc.Filters; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - /// - /// A feature in which is used to capture the - /// currently executing context of a resource filter. This feature is used in the final middleware - /// of a middleware filter's pipeline to keep the request flow through the rest of the MVC layers. - /// - public interface IMiddlewareFilterFeature - { - ResourceExecutingContext ResourceExecutingContext { get; } - - ResourceExecutionDelegate ResourceExecutionDelegate { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs deleted file mode 100644 index d346acc27e..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - /// - /// A filter which executes a user configured middleware pipeline. - /// - public class MiddlewareFilter : IAsyncResourceFilter - { - private readonly RequestDelegate _middlewarePipeline; - - public MiddlewareFilter(RequestDelegate middlewarePipeline) - { - if (middlewarePipeline == null) - { - throw new ArgumentNullException(nameof(middlewarePipeline)); - } - - _middlewarePipeline = middlewarePipeline; - } - - public Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) - { - var httpContext = context.HttpContext; - - // Capture the current context into the feature. This will later be used in the end middleware to continue - // the execution flow to later MVC layers. - // Example: - // this filter -> user-middleware1 -> user-middleware2 -> the-end-middleware -> resouce filters or model binding - var feature = new MiddlewareFilterFeature() - { - ResourceExecutionDelegate = next, - ResourceExecutingContext = context - }; - httpContext.Features.Set(feature); - - return _middlewarePipeline(httpContext); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs deleted file mode 100644 index 04a620620a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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.Concurrent; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Core; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - /// - /// Builds a middleware pipeline after receiving the pipeline from a pipeline provider - /// - public class MiddlewareFilterBuilder - { - // 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more - // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple - // threads but only one of the objects succeeds in creating a pipeline. - private readonly ConcurrentDictionary> _pipelinesCache - = new ConcurrentDictionary>(); - private readonly MiddlewareFilterConfigurationProvider _configurationProvider; - - public IApplicationBuilder ApplicationBuilder { get; set; } - - public MiddlewareFilterBuilder(MiddlewareFilterConfigurationProvider configurationProvider) - { - _configurationProvider = configurationProvider; - } - - public RequestDelegate GetPipeline(Type configurationType) - { - // Build the pipeline only once. This is similar to how middlewares registered in Startup are constructed. - - var requestDelegate = _pipelinesCache.GetOrAdd( - configurationType, - key => new Lazy(() => BuildPipeline(key))); - - return requestDelegate.Value; - } - - private RequestDelegate BuildPipeline(Type middlewarePipelineProviderType) - { - if (ApplicationBuilder == null) - { - throw new InvalidOperationException( - Resources.FormatMiddlewareFilterBuilder_NullApplicationBuilder(nameof(ApplicationBuilder))); - } - - var nestedAppBuilder = ApplicationBuilder.New(); - - // Get the 'Configure' method from the user provided type. - var configureDelegate = _configurationProvider.CreateConfigureDelegate(middlewarePipelineProviderType); - configureDelegate(nestedAppBuilder); - - // The middleware resource filter, after receiving the request executes the user configured middleware - // pipeline. Since we want execution of the request to continue to later MVC layers (resource filters - // or model binding), add a middleware at the end of the user provided pipeline which make sure to continue - // this flow. - // Example: - // middleware filter -> user-middleware1 -> user-middleware2 -> end-middleware -> resouce filters or model binding - nestedAppBuilder.Run(async (httpContext) => - { - var feature = httpContext.Features.Get(); - if (feature == null) - { - throw new InvalidOperationException( - Resources.FormatMiddlewareFilterBuilder_NoMiddlewareFeature(nameof(IMiddlewareFilterFeature))); - } - - var resourceExecutionDelegate = feature.ResourceExecutionDelegate; - - var resourceExecutedContext = await resourceExecutionDelegate(); - - // Ideally we want the experience of a middleware pipeline to behave the same as if it was registered, - // in Startup. In this scenario an exception thrown in a middelware later in the pipeline gets propagated - // back to earlier middleware. - // So check if a later resource filter threw an exception and propagate that back to the middleware pipeline. - if (!resourceExecutedContext.ExceptionHandled && resourceExecutedContext.Exception != null) - { - throw resourceExecutedContext.Exception; - } - }); - - return nestedAppBuilder.Build(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs deleted file mode 100644 index 49d8e52d95..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - /// - /// Calls into user provided 'Configure' methods for configuring a middleware pipeline. The semantics of finding - /// the 'Configure' methods is similar to the application Startup class. - /// - public class MiddlewareFilterConfigurationProvider - { - public Action CreateConfigureDelegate(Type configurationType) - { - if (configurationType == null) - { - throw new ArgumentNullException(nameof(configurationType)); - } - - var instance = Activator.CreateInstance(configurationType); - var configureDelegateBuilder = GetConfigureDelegateBuilder(configurationType); - return configureDelegateBuilder.Build(instance); - } - - private static ConfigureBuilder GetConfigureDelegateBuilder(Type startupType) - { - var configureMethod = FindMethod(startupType, typeof(void)); - return new ConfigureBuilder(configureMethod); - } - - private static MethodInfo FindMethod(Type startupType, Type returnType = null) - { - var methodName = "Configure"; - - var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - var selectedMethods = methods.Where(method => method.Name.Equals(methodName)).ToList(); - if (selectedMethods.Count > 1) - { - throw new InvalidOperationException( - Resources.FormatMiddewareFilter_ConfigureMethodOverload(methodName)); - } - - var methodInfo = selectedMethods.FirstOrDefault(); - if (methodInfo == null) - { - throw new InvalidOperationException( - Resources.FormatMiddewareFilter_NoConfigureMethod( - methodName, - startupType.FullName)); - } - - if (returnType != null && methodInfo.ReturnType != returnType) - { - throw new InvalidOperationException( - Resources.FormatMiddlewareFilter_InvalidConfigureReturnType( - methodInfo.Name, - startupType.FullName, - returnType.Name)); - } - return methodInfo; - } - - private class ConfigureBuilder - { - public ConfigureBuilder(MethodInfo configure) - { - MethodInfo = configure; - } - - public MethodInfo MethodInfo { get; } - - public Action Build(object instance) - { - return (applicationBuilder) => Invoke(instance, applicationBuilder); - } - - private void Invoke(object instance, IApplicationBuilder builder) - { - var serviceProvider = builder.ApplicationServices; - var parameterInfos = MethodInfo.GetParameters(); - var parameters = new object[parameterInfos.Length]; - for (var index = 0; index < parameterInfos.Length; index++) - { - var parameterInfo = parameterInfos[index]; - if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) - { - parameters[index] = builder; - } - else - { - try - { - parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); - } - catch (Exception ex) - { - throw new InvalidOperationException( - Resources.FormatMiddlewareFilter_ServiceResolutionFail( - parameterInfo.ParameterType.FullName, - parameterInfo.Name, - MethodInfo.Name, - MethodInfo.DeclaringType.FullName), - ex); - } - } - } - MethodInfo.Invoke(instance, parameters); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs deleted file mode 100644 index d33378d6f7..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.Mvc.Filters; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class MiddlewareFilterFeature : IMiddlewareFilterFeature - { - public ResourceExecutingContext ResourceExecutingContext { get; set; } - - public ResourceExecutionDelegate ResourceExecutionDelegate { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index b5cd370b82..418c37e1bf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1210,102 +1210,6 @@ namespace Microsoft.AspNetCore.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormattersAreRequired"), p0, p1, p2); } - /// - /// Having multiple overloads of method '{0}' is not supported. - /// - internal static string MiddewareFilter_ConfigureMethodOverload - { - get { return GetString("MiddewareFilter_ConfigureMethodOverload"); } - } - - /// - /// Having multiple overloads of method '{0}' is not supported. - /// - internal static string FormatMiddewareFilter_ConfigureMethodOverload(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_ConfigureMethodOverload"), p0); - } - - /// - /// A public method named '{0}' could not be found in the '{1}' type. - /// - internal static string MiddewareFilter_NoConfigureMethod - { - get { return GetString("MiddewareFilter_NoConfigureMethod"); } - } - - /// - /// A public method named '{0}' could not be found in the '{1}' type. - /// - internal static string FormatMiddewareFilter_NoConfigureMethod(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_NoConfigureMethod"), p0, p1); - } - - /// - /// Could not find '{0}' in the feature list. - /// - internal static string MiddlewareFilterBuilder_NoMiddlewareFeature - { - get { return GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"); } - } - - /// - /// Could not find '{0}' in the feature list. - /// - internal static string FormatMiddlewareFilterBuilder_NoMiddlewareFeature(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"), p0); - } - - /// - /// '{0}' property cannot be null. - /// - internal static string MiddlewareFilterBuilder_NullApplicationBuilder - { - get { return GetString("MiddlewareFilterBuilder_NullApplicationBuilder"); } - } - - /// - /// '{0}' property cannot be null. - /// - internal static string FormatMiddlewareFilterBuilder_NullApplicationBuilder(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NullApplicationBuilder"), p0); - } - - /// - /// The '{0}' method in the type '{1}' must have a return type of '{2}'. - /// - internal static string MiddlewareFilter_InvalidConfigureReturnType - { - get { return GetString("MiddlewareFilter_InvalidConfigureReturnType"); } - } - - /// - /// The '{0}' method in the type '{1}' must have a return type of '{2}'. - /// - internal static string FormatMiddlewareFilter_InvalidConfigureReturnType(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilter_InvalidConfigureReturnType"), p0, p1, p2); - } - - /// - /// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. - /// - internal static string MiddlewareFilter_ServiceResolutionFail - { - get { return GetString("MiddlewareFilter_ServiceResolutionFail"); } - } - - /// - /// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. - /// - internal static string FormatMiddlewareFilter_ServiceResolutionFail(object p0, object p1, object p2, object p3) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilter_ServiceResolutionFail"), p0, p1, p2, p3); - } - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 8bb5bc4735..732bba5387 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -352,22 +352,4 @@ '{0}.{1}' must not be empty. At least one '{2}' is required to format a response. - - Multiple overloads of method '{0}' are not supported. - - - A public method named '{0}' could not be found in the '{1}' type. - - - Could not find '{0}' in the feature list. - - - The '{0}' property cannot be null. - - - The '{0}' method in the type '{1}' must have a return type of '{2}'. - - - Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/GeneratedViewComponentTagHelperContext.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/GeneratedViewComponentTagHelperContext.cs new file mode 100644 index 0000000000..8008fe71e9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/GeneratedViewComponentTagHelperContext.cs @@ -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. + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal +{ + /// + /// Contains necessary information for the view component code generation process. + /// + public class GeneratedViewComponentTagHelperContext + { + /// + /// Instantiates a new instance of the with default values. + /// + public GeneratedViewComponentTagHelperContext() + { + ContextualizeMethodName = "Contextualize"; + InvokeAsyncMethodName = "InvokeAsync"; + IViewComponentHelperTypeName = "Microsoft.AspNetCore.Mvc.IViewComponentHelper"; + IViewContextAwareTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware"; + ViewContextTypeName = "Microsoft.AspNetCore.Mvc.Rendering.ViewContext"; + } + + /// + /// Name of the Contextualize method called by an instance of the IViewContextAware type. + /// + public string ContextualizeMethodName { get; set; } + + /// + /// Name of the InvokeAsync method called by an IViewComponentHelper. + /// + public string InvokeAsyncMethodName { get; set; } + + /// + /// Name of the IViewComponentHelper type used to invoke view components. + /// + public string IViewComponentHelperTypeName { get; set; } + + /// + /// Name of the IViewContextAware type used to contextualize the view context. + /// + public string IViewContextAwareTypeName { get; set; } + + /// + /// Name of the ViewContext type for view execution. + /// + public string ViewContextTypeName { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/TagHelperChunkDecorator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/TagHelperChunkDecorator.cs new file mode 100644 index 0000000000..7da6e28db8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/TagHelperChunkDecorator.cs @@ -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; +using Microsoft.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal +{ + public class TagHelperChunkDecorator : CodeVisitor + { + private readonly string _className; + private readonly string _namespaceName; + + public TagHelperChunkDecorator(CodeGeneratorContext context) + : base(new CSharpCodeWriter(), context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + _namespaceName = context.RootNamespace; + _className = context.ClassName; + } + + public override void Accept(Chunk chunk) + { + if (chunk == null) + { + throw new ArgumentNullException(nameof(chunk)); + } + + var tagHelperChunk = chunk as TagHelperChunk; + if (tagHelperChunk != null) + { + tagHelperChunk.Descriptors = Decorate(tagHelperChunk.Descriptors); + } + + base.Accept(chunk); + } + + protected override void Visit(ParentChunk parentChunk) + { + Accept(parentChunk.Children); + } + + private IEnumerable Decorate(IEnumerable descriptors) + { + foreach (var descriptor in descriptors) + { + if (ViewComponentTagHelperDescriptorConventions.IsViewComponentDescriptor(descriptor)) + { + var decoratedDescriptor = new TagHelperDescriptor(descriptor); + decoratedDescriptor.TypeName = $"{_namespaceName}.{_className}.{descriptor.TypeName}"; + + yield return decoratedDescriptor; + } + else + { + yield return descriptor; + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ViewComponentTagHelperChunkVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ViewComponentTagHelperChunkVisitor.cs new file mode 100644 index 0000000000..d5a70d86e0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ViewComponentTagHelperChunkVisitor.cs @@ -0,0 +1,190 @@ +// 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.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal +{ + public class ViewComponentTagHelperChunkVisitor : CodeVisitor + { + private readonly GeneratedViewComponentTagHelperContext _context; + private readonly HashSet _writtenViewComponents; + + private const string ViewComponentTagHelperVariable = "_viewComponentHelper"; + private const string ViewContextVariable = "ViewContext"; + + public ViewComponentTagHelperChunkVisitor(CSharpCodeWriter writer, CodeGeneratorContext context) + : base(writer, context) + { + _context = new GeneratedViewComponentTagHelperContext(); + _writtenViewComponents = new HashSet(StringComparer.Ordinal); + } + + public override void Accept(Chunk chunk) + { + if (chunk == null) + { + throw new ArgumentNullException(nameof(chunk)); + } + + var tagHelperChunk = chunk as TagHelperChunk; + if (tagHelperChunk != null) + { + Visit(tagHelperChunk); + } + + base.Accept(chunk); + } + + protected override void Visit(ParentChunk parentChunk) + { + Accept(parentChunk.Children); + } + + protected override void Visit(TagHelperChunk chunk) + { + foreach (var descriptor in chunk.Descriptors) + { + string shortName; + if (descriptor.PropertyBag.TryGetValue( + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, + out shortName)) + { + var typeName = $"__Generated__{shortName}ViewComponentTagHelper"; + + if (_writtenViewComponents.Add(typeName)) + { + WriteClass(descriptor); + } + } + } + } + + private void WriteClass(TagHelperDescriptor descriptor) + { + // Add target element. + BuildTargetElementString(descriptor); + + // Initialize declaration. + var tagHelperTypeName = typeof(TagHelper).FullName; + + var shortName = descriptor.PropertyBag[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + var className = $"__Generated__{shortName}ViewComponentTagHelper"; + + using (Writer.BuildClassDeclaration("public", className, new[] { tagHelperTypeName })) + { + // Add view component helper. + Writer.WriteVariableDeclaration( + $"private readonly global::{_context.IViewComponentHelperTypeName}", + ViewComponentTagHelperVariable, + value: null); + + // Add constructor. + BuildConstructorString(className); + + // Add attributes. + BuildAttributeDeclarations(descriptor); + + // Add process method. + BuildProcessMethodString(descriptor); + } + } + + private void BuildConstructorString(string className) + { + var viewComponentHelperVariable = "viewComponentHelper"; + + var helperPair = new KeyValuePair( + $"global::{_context.IViewComponentHelperTypeName}", + viewComponentHelperVariable); + + using (Writer.BuildConstructor( "public", className, new[] { helperPair })) + { + Writer.WriteStartAssignment(ViewComponentTagHelperVariable) + .Write(viewComponentHelperVariable) + .WriteLine(";"); + } + } + + private void BuildAttributeDeclarations(TagHelperDescriptor descriptor) + { + Writer.Write("[") + .Write(typeof(HtmlAttributeNotBoundAttribute).FullName) + .WriteParameterSeparator() + .Write($"global::{_context.ViewContextTypeName}") + .WriteLine("]"); + + Writer.WriteAutoPropertyDeclaration( + "public", + $"global::{_context.ViewContextTypeName}", + ViewContextVariable); + + foreach (var attribute in descriptor.Attributes) + { + Writer.WriteAutoPropertyDeclaration("public", attribute.TypeName, attribute.PropertyName); + } + } + + private void BuildProcessMethodString(TagHelperDescriptor descriptor) + { + var contextVariable = "context"; + var outputVariable = "output"; + + using (Writer.BuildMethodDeclaration( + $"public override async", + $"global::{typeof(Task).FullName}", + nameof(ITagHelper.ProcessAsync), + new Dictionary() + { + { typeof(TagHelperContext).FullName, contextVariable }, + { typeof(TagHelperOutput).FullName, outputVariable } + })) + { + Writer.WriteInstanceMethodInvocation( + $"((global::{_context.IViewContextAwareTypeName}){ViewComponentTagHelperVariable})", + _context.ContextualizeMethodName, + new [] { ViewContextVariable }); + + var methodParameters = GetMethodParameters(descriptor); + var viewContentVariable = "viewContent"; + Writer.Write("var ") + .WriteStartAssignment(viewContentVariable) + .WriteInstanceMethodInvocation($"await {ViewComponentTagHelperVariable}", _context.InvokeAsyncMethodName, methodParameters); + Writer.WriteStartAssignment($"{outputVariable}.{nameof(TagHelperOutput.TagName)}") + .WriteLine("null;"); + Writer.WriteInstanceMethodInvocation( + $"{outputVariable}.{nameof(TagHelperOutput.Content)}", + nameof(TagHelperContent.SetHtmlContent), + new [] { viewContentVariable }); + } + } + + private string[] GetMethodParameters(TagHelperDescriptor descriptor) + { + var propertyNames = descriptor.Attributes.Select(attribute => attribute.PropertyName); + var joinedPropertyNames = string.Join(", ", propertyNames); + var parametersString = $" new {{ { joinedPropertyNames } }}"; + + var viewComponentName = descriptor.PropertyBag[ + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + var methodParameters = new [] { $"\"{viewComponentName}\"", parametersString }; + return methodParameters; + } + + private void BuildTargetElementString(TagHelperDescriptor descriptor) + { + Writer.Write("[") + .WriteStartMethodInvocation(typeof(HtmlTargetElementAttribute).FullName) + .WriteStringLiteral(descriptor.FullTagName) + .WriteLine(")]"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs index 22b02ff682..966b409ff8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs @@ -2,8 +2,11 @@ // 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.IO; using Microsoft.AspNetCore.Mvc.Razor.Directives; +using Microsoft.AspNetCore.Mvc.Razor.Host.Internal; +using Microsoft.AspNetCore.Razor.Chunks; using Microsoft.AspNetCore.Razor.CodeGenerators; using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors; @@ -12,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor public class MvcCSharpCodeGenerator : CSharpCodeGenerator { private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext; + private readonly TagHelperChunkDecorator _tagHelperChunkDecorator; private readonly string _defaultModel; private readonly string _injectAttribute; @@ -45,6 +49,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor _tagHelperAttributeContext = tagHelperAttributeContext; _defaultModel = defaultModel; _injectAttribute = injectAttribute; + _tagHelperChunkDecorator = new TagHelperChunkDecorator(Context); + } + + public override CodeGeneratorResult Generate() + { + _tagHelperChunkDecorator.Accept(Context.ChunkTreeBuilder.Root.Children); + return base.Generate(); } protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer) @@ -62,6 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor return base.BuildClassDeclaration(writer); } + protected override void BuildAfterExecuteContent(CSharpCodeWriter writer, IList chunks) + { + new ViewComponentTagHelperChunkVisitor(writer, Context).Accept(chunks); + } + protected override CSharpCodeVisitor CreateCSharpCodeVisitor( CSharpCodeWriter writer, CodeGeneratorContext context) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs deleted file mode 100644 index 36f42c9e01..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/ViewComponentTagHelperDescriptorConventions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 deleted file mode 100644 index 6ec113860c..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor/ViewComponentTagHelperDescriptorFactory.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs index 1864c457b7..48a457e495 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs @@ -98,8 +98,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// attribute to that formatted . /// /// - /// Not used if the provided (see ) or calculated "type" attribute value is - /// checkbox, password, or radio. That is, is used when calling + /// Used only the calculated "type" attribute is "text" (the most common value) e.g. + /// is "String". That is, is used when calling /// . /// [HtmlAttributeName(FormatAttributeName)] @@ -110,9 +110,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// /// /// Passed through to the generated HTML in all cases. Also used to determine the - /// helper to call and the default value. A default is not calculated - /// if the provided (see ) or calculated "type" attribute value is checkbox, - /// hidden, password, or radio. + /// helper to call and the default value (when calling + /// ). /// [HtmlAttributeName("type")] public string InputTypeName { get; set; } @@ -190,14 +189,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers TagBuilder tagBuilder; switch (inputType) { - case "hidden": - tagBuilder = GenerateHidden(modelExplorer); - break; - case "checkbox": GenerateCheckBox(modelExplorer, output); return; + case "hidden": + tagBuilder = Generator.GenerateHidden( + ViewContext, + modelExplorer, + For.Name, + value: For.Model, + useViewData: false, + htmlAttributes: null); + break; + case "password": tagBuilder = Generator.GeneratePassword( ViewContext, @@ -359,34 +364,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers htmlAttributes: htmlAttributes); } - // Imitate Generator.GenerateHidden() using Generator.GenerateTextBox(). This adds support for asp-format that - // is not available in Generator.GenerateHidden(). - private TagBuilder GenerateHidden(ModelExplorer modelExplorer) - { - var value = For.Model; - var byteArrayValue = value as byte[]; - if (byteArrayValue != null) - { - value = Convert.ToBase64String(byteArrayValue); - } - - // In DefaultHtmlGenerator(), GenerateTextBox() calls GenerateInput() _almost_ identically to how - // GenerateHidden() does and the main switch inside GenerateInput() handles InputType.Text and - // InputType.Hidden identically. No behavior differences at all when a type HTML attribute already exists. - var htmlAttributes = new Dictionary - { - { "type", "hidden" } - }; - - return Generator.GenerateTextBox( - ViewContext, - modelExplorer, - For.Name, - value: value, - format: Format, - htmlAttributes: htmlAttributes); - } - // Get a fall-back format based on the metadata. private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index 8470b7ea25..c4016e9ef2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -1,7 +1,7 @@ -// 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.AspNetCore.Builder; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs deleted file mode 100644 index bedfd14f4a..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.Builder; -using Microsoft.AspNetCore.Builder.Internal; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class MiddlewareFilterAttributeTest - { - [Fact] - public void CreatesMiddlewareFilter_WithConfiguredPipeline() - { - // Arrange - var middlewareFilterAttribute = new MiddlewareFilterAttribute(typeof(Pipeline1)); - var services = new ServiceCollection(); - services.AddSingleton(new MiddlewareFilterBuilder(new MiddlewareFilterConfigurationProvider())); - var serviceProvider = services.BuildServiceProvider(); - var filterBuilderService = serviceProvider.GetRequiredService(); - filterBuilderService.ApplicationBuilder = new ApplicationBuilder(serviceProvider); - var configureCallCount = 0; - Pipeline1.ConfigurePipeline = (ab) => - { - configureCallCount++; - ab.Use((httpCtxt, next) => - { - return next(); - }); - }; - - // Act - var filter = middlewareFilterAttribute.CreateInstance(serviceProvider); - - // Assert - var middlewareFilter = Assert.IsType(filter); - Assert.NotNull(middlewareFilter); - Assert.Equal(1, configureCallCount); - } - - private class Pipeline1 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - - private class Pipeline2 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs deleted file mode 100644 index 9238a6e437..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs +++ /dev/null @@ -1,157 +0,0 @@ -// 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.AspNetCore.Builder; -using Microsoft.AspNetCore.Builder.Internal; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class MiddlewareFilterBuilderTest - { - [Fact] - public void GetPipeline_CallsInto_Configure() - { - // Arrange - var services = new ServiceCollection(); - var appBuilder = new ApplicationBuilder(services.BuildServiceProvider()); - var pipelineBuilderService = new MiddlewareFilterBuilder(new MiddlewareFilterConfigurationProvider()); - pipelineBuilderService.ApplicationBuilder = appBuilder; - var configureCount = 0; - Pipeline1.ConfigurePipeline = (ab) => - { - configureCount++; - }; - - // Act - var pipeline = pipelineBuilderService.GetPipeline(typeof(Pipeline1)); - - // Assert - Assert.NotNull(pipeline); - Assert.Equal(1, configureCount); - } - - [Fact] - public void GetPipeline_CallsIntoConfigure_OnlyOnce_ForTheSamePipelineType() - { - // Arrange - var services = new ServiceCollection(); - var appBuilder = new ApplicationBuilder(services.BuildServiceProvider()); - var pipelineBuilderService = new MiddlewareFilterBuilder(new MiddlewareFilterConfigurationProvider()); - pipelineBuilderService.ApplicationBuilder = appBuilder; - var configureCount = 0; - Pipeline1.ConfigurePipeline = (ab) => - { - configureCount++; - }; - - // Act - var pipeline1 = pipelineBuilderService.GetPipeline(typeof(Pipeline1)); - - // Assert - Assert.NotNull(pipeline1); - Assert.Equal(1, configureCount); - - // Act - var pipeline2 = pipelineBuilderService.GetPipeline(typeof(Pipeline1)); - - // Assert - Assert.NotNull(pipeline2); - Assert.Same(pipeline1, pipeline2); - Assert.Equal(1, configureCount); - } - - [Fact] - public async Task EndMiddleware_ThrowsException_WhenMiddleFeature_NotAvailable() - { - // Arrange - var services = new ServiceCollection(); - var appBuilder = new ApplicationBuilder(services.BuildServiceProvider()); - var pipelineBuilderService = new MiddlewareFilterBuilder(new MiddlewareFilterConfigurationProvider()); - pipelineBuilderService.ApplicationBuilder = appBuilder; - Pipeline1.ConfigurePipeline = (ab) => - { - ab.Use((httpContext, next) => - { - return next(); - }); - }; - - // Act - var pipeline = pipelineBuilderService.GetPipeline(typeof(Pipeline1)); - - // Assert - Assert.NotNull(pipeline); - var exception = await Assert.ThrowsAsync(() => pipeline(new DefaultHttpContext())); - Assert.Equal( - "Could not find 'IMiddlewareFilterFeature' in the feature list.", - exception.Message); - } - - [Fact] - public async Task EndMiddleware_PropagatesBackException_ToEarlierMiddleware() - { - // Arrange - var services = new ServiceCollection(); - var appBuilder = new ApplicationBuilder(services.BuildServiceProvider()); - var pipelineBuilderService = new MiddlewareFilterBuilder(new MiddlewareFilterConfigurationProvider()); - pipelineBuilderService.ApplicationBuilder = appBuilder; - Pipeline1.ConfigurePipeline = (ab) => - { - ab.Use((httpCtxt, next) => - { - return next(); - }); - }; - var middlewareFilterFeature = new MiddlewareFilterFeature(); - middlewareFilterFeature.ResourceExecutionDelegate = () => - { - var context = new ResourceExecutedContext( - new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor(), new ModelStateDictionary()), - new List()); - context.Exception = new InvalidOperationException("Error!!!"); - return Task.FromResult(context); - }; - var features = new FeatureCollection(); - features.Set(middlewareFilterFeature); - var httpContext = new DefaultHttpContext(features); - - // Act - var pipeline = pipelineBuilderService.GetPipeline(typeof(Pipeline1)); - - // Assert - Assert.NotNull(pipeline); - var exception = await Assert.ThrowsAsync(() => pipeline(httpContext)); - Assert.Equal("Error!!!", exception.Message); - } - private class Pipeline1 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - - private class Pipeline2 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs deleted file mode 100644 index d5e2d77287..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs +++ /dev/null @@ -1,178 +0,0 @@ -// 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 Castle.Core.Logging; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class MiddlewareFilterConfigurationProviderTest - { - [Fact] - public void ValidConfigure_DoesNotThrow() - { - // Arrange - var provider = new MiddlewareFilterConfigurationProvider(); - - // Act - var configureDelegate = provider.CreateConfigureDelegate(typeof(ValidConfigure_WithNoEnvironment)); - - // Assert - Assert.NotNull(configureDelegate); - } - - [Fact] - public void ValidConfigure_AndAdditionalServices_DoesNotThrow() - { - // Arrange - var loggerFactory = Mock.Of(); - var services = new ServiceCollection(); - services.AddSingleton(loggerFactory); - services.AddSingleton(Mock.Of()); - var applicationBuilder = GetApplicationBuilder(services); - var provider = new MiddlewareFilterConfigurationProvider(); - - // Act - var configureDelegate = provider.CreateConfigureDelegate(typeof(ValidConfigure_WithNoEnvironment_AdditionalServices)); - - // Assert - Assert.NotNull(configureDelegate); - } - - [Fact] - public void InvalidType_NoConfigure_Throws() - { - // Arrange - var type = typeof(InvalidType_NoConfigure); - var provider = new MiddlewareFilterConfigurationProvider(); - var expected = $"A public method named 'Configure' could not be found in the '{type.FullName}' type."; - - // Act & Assert - var exception = Assert.Throws(() => - { - provider.CreateConfigureDelegate(type); - }); - Assert.Equal(expected, exception.Message); - } - - [Fact] - public void InvalidType_NoPublicConfigure_Throws() - { - // Arrange - var type = typeof(InvalidType_NoPublic_Configure); - var provider = new MiddlewareFilterConfigurationProvider(); - var expected = $"A public method named 'Configure' could not be found in the '{type.FullName}' type."; - - // Act & Assert - var exception = Assert.Throws(() => - { - provider.CreateConfigureDelegate(type); - }); - Assert.Equal(expected, exception.Message); - } - - private IApplicationBuilder GetApplicationBuilder(ServiceCollection services = null) - { - if (services == null) - { - services = new ServiceCollection(); - } - var serviceProvider = services.BuildServiceProvider(); - - var applicationBuilder = new Mock(); - applicationBuilder - .SetupGet(a => a.ApplicationServices) - .Returns(serviceProvider); - - return applicationBuilder.Object; - } - - private class ValidConfigure_WithNoEnvironment - { - public void Configure(IApplicationBuilder appBuilder) { } - } - - private class ValidConfigure_WithNoEnvironment_AdditionalServices - { - public void Configure( - IApplicationBuilder appBuilder, - IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory) - { - if (hostingEnvironment == null) - { - throw new ArgumentNullException(nameof(hostingEnvironment)); - } - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - } - } - - private class ValidConfigure_WithEnvironment - { - public void ConfigureProduction(IApplicationBuilder appBuilder) { } - } - - private class ValidConfigure_WithEnvironment_AdditionalServices - { - public void ConfigureProduction( - IApplicationBuilder appBuilder, - IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory) - { - if (hostingEnvironment == null) - { - throw new ArgumentNullException(nameof(hostingEnvironment)); - } - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - } - } - - private class MultipleConfigureWithEnvironments - { - public void ConfigureDevelopment(IApplicationBuilder appBuilder) - { - - } - - public void ConfigureProduction(IApplicationBuilder appBuilder) - { - - } - } - - private class InvalidConfigure_NoParameters - { - public void Configure() - { - - } - } - - private class InvalidType_NoConfigure - { - public void Foo(IApplicationBuilder appBuilder) - { - - } - } - - private class InvalidType_NoPublic_Configure - { - private void Configure(IApplicationBuilder appBuilder) - { - - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs deleted file mode 100644 index 68b63d1153..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs +++ /dev/null @@ -1,516 +0,0 @@ -// 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 System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Builder.Internal; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class MiddlewareFilterTest - { - private readonly TestController _controller = new TestController(); - - [Fact] - public async Task MiddlewareFilter_SetsMiddlewareFilterFeature_OnExecution() - { - // Arrange - RequestDelegate requestDelegate = (context) => Task.FromResult(true); - var middlwareFilter = new MiddlewareFilter(requestDelegate); - var httpContext = new DefaultHttpContext(); - var resourceExecutingContext = GetResourceExecutingContext(httpContext); - var resourceExecutionDelegate = GetResourceExecutionDelegate(httpContext); - - // Act - await middlwareFilter.OnResourceExecutionAsync(resourceExecutingContext, resourceExecutionDelegate); - - // Assert - var feature = resourceExecutingContext.HttpContext.Features.Get(); - Assert.NotNull(feature); - Assert.Same(resourceExecutingContext, feature.ResourceExecutingContext); - Assert.Same(resourceExecutionDelegate, feature.ResourceExecutionDelegate); - } - - [Fact] - public async Task OnMiddlewareShortCircuit_DoesNotExecute_RestOfFilterPipeline() - { - // Arrange - var expectedHeader = "h1"; - Pipeline1.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use((httpContext, next) => - { - httpContext.Response.Headers.Add(expectedHeader, ""); - return Task.FromResult(true); // short circuit the request - }); - }; - var resourceFilter1 = new TestResourceFilter(TestResourceFilterAction.Passthrough); - var middlewareResourceFilter = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline1))); - var exceptionThrowingResourceFilter = new TestResourceFilter(TestResourceFilterAction.ThrowException); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1, - middlewareResourceFilter, - exceptionThrowingResourceFilter, - }, - actionThrows: true); // The action won't run - - // Act - await invoker.InvokeAsync(); - - // Assert - var resourceExecutedContext = resourceFilter1.ResourceExecutedContext; - Assert.True(resourceExecutedContext.HttpContext.Response.Headers.ContainsKey(expectedHeader)); - Assert.True(resourceExecutedContext.Canceled); - Assert.False(invoker.ControllerFactory.CreateCalled); - } - - // Example: Middleware filters are applied at Global, Controller & Action level - [Fact] - public async Task Multiple_MiddlewareFilters_ConcatsTheMiddlewarePipelines() - { - // Arrange - var expectedHeader = "h1"; - var expectedHeaderValue = "pipeline1-pipeline2"; - Pipeline1.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use((httpContext, next) => - { - httpContext.Response.Headers["h1"] = "pipeline1"; - return next(); - }); - }; - Pipeline2.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use((httpContext, next) => - { - httpContext.Response.Headers["h1"] = httpContext.Response.Headers["h1"] + "-pipeline2"; - return Task.FromResult(true); // short circuits the request - }); - }; - var resourceFilter1 = new TestResourceFilter(TestResourceFilterAction.Passthrough); - var middlewareResourceFilter1 = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline1))); - var middlewareResourceFilter2 = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline2))); - var exceptionThrowingResourceFilter = new TestResourceFilter(TestResourceFilterAction.ThrowException); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1, // This filter will pass through - middlewareResourceFilter1, // This filter will pass through - middlewareResourceFilter2, // This filter will short circuit - exceptionThrowingResourceFilter, // This shouldn't run - }, - actionThrows: true); // The action won't run - - // Act - await invoker.InvokeAsync(); - - // Assert - var resourceExecutedContext = resourceFilter1.ResourceExecutedContext; - var response = resourceExecutedContext.HttpContext.Response; - Assert.True(response.Headers.ContainsKey(expectedHeader)); - Assert.Equal(expectedHeaderValue, response.Headers[expectedHeader]); - Assert.True(resourceExecutedContext.Canceled); - Assert.False(invoker.ControllerFactory.CreateCalled); - } - - [Fact] - public async Task UnhandledException_InMiddleware_PropagatesBackToInvoker() - { - // Arrange - var expectedMessage = "Error!!!"; - Pipeline1.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use((httpContext, next) => - { - throw new InvalidOperationException(expectedMessage); - }); - }; - var resourceFilter1 = new TestResourceFilter(TestResourceFilterAction.Passthrough); - var middlewareResourceFilter = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline1))); - var exceptionThrowingResourceFilter = new TestResourceFilter(TestResourceFilterAction.ThrowException); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1, - middlewareResourceFilter, - exceptionThrowingResourceFilter, // This shouldn't run - }, - actionThrows: true); // The action won't run - - // Act - var exception = await Assert.ThrowsAsync(async () => await invoker.InvokeAsync()); - - // Assert - Assert.Equal(expectedMessage, exception.Message); - } - - [Fact] - public async Task ExceptionThrownInMiddleware_CanBeHandled_ByEarlierMiddleware() - { - // Arrange - var expectedMessage = "Error!!!"; - Pipeline1.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use(async (httpContext, next) => - { - try - { - await next(); - } - catch - { - httpContext.Response.StatusCode = 500; - httpContext.Response.Headers.Add("Error", "Error!!!!"); - } - }); - }; - Pipeline2.ConfigurePipeline = (appBuilder) => - { - appBuilder.Use((httpContext, next) => - { - throw new InvalidOperationException(expectedMessage); - }); - }; - var resourceFilter1 = new TestResourceFilter(TestResourceFilterAction.Passthrough); - var middlewareResourceFilter1 = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline1))); - var middlewareResourceFilter2 = new MiddlewareFilter(GetMiddlewarePipeline(typeof(Pipeline2))); - var exceptionThrowingResourceFilter = new TestResourceFilter(TestResourceFilterAction.ThrowException); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1, - middlewareResourceFilter1, - middlewareResourceFilter2, - exceptionThrowingResourceFilter, // This shouldn't run - }, - actionThrows: true); // The action won't run - - // Act - var exception = await Assert.ThrowsAsync(async () => await invoker.InvokeAsync()); - - // Assert - var resourceExecutedContext = resourceFilter1.ResourceExecutedContext; - var response = resourceExecutedContext.HttpContext.Response; - Assert.Equal(500, response.StatusCode); - Assert.True(response.Headers.ContainsKey("Error")); - Assert.False(invoker.ControllerFactory.CreateCalled); - } - - private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext) - { - return new ResourceExecutingContext( - new ActionContext(httpContext, new RouteData(), new ActionDescriptor(), new ModelStateDictionary()), - new List(), - new List()); - } - - private ResourceExecutionDelegate GetResourceExecutionDelegate(HttpContext httpContext) - { - return new ResourceExecutionDelegate( - () => Task.FromResult(new ResourceExecutedContext(new ActionContext(), new List()))); - } - - private TestControllerActionInvoker CreateInvoker( - IFilterMetadata[] filters, - bool actionThrows = false) - { - var actionDescriptor = new ControllerActionDescriptor() - { - FilterDescriptors = new List(), - Parameters = new List(), - }; - - if (actionThrows) - { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ThrowingActionMethod)); - } - else - { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); - } - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); - - return CreateInvoker(filters, actionDescriptor, _controller); - } - - private TestControllerActionInvoker CreateInvoker( - IFilterMetadata[] filters, - ControllerActionDescriptor actionDescriptor, - object controller) - { - var httpContext = GetHttpContext(); - httpContext.Response.Body = new MemoryStream(); - - var options = new MvcOptions(); - var optionsAccessor = new Mock>(); - optionsAccessor - .SetupGet(o => o.Value) - .Returns(options); - - var actionContext = new ActionContext(httpContext, new RouteData(), actionDescriptor); - - var filterProvider = new Mock(MockBehavior.Strict); - filterProvider - .Setup(fp => fp.OnProvidersExecuting(It.IsAny())) - .Callback(context => - { - foreach (var filterMetadata in filters) - { - context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action)) - { - Filter = filterMetadata, - }); - } - }); - - filterProvider - .Setup(fp => fp.OnProvidersExecuted(It.IsAny())) - .Verifiable(); - - filterProvider - .SetupGet(fp => fp.Order) - .Returns(-1000); - - var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); - diagnosticSource.SubscribeWithAdapter(new TestDiagnosticListener()); - - var invoker = new TestControllerActionInvoker( - new[] { filterProvider.Object }, - new MockControllerFactory(controller ?? this), - new TestControllerArgumentBinder(actionParameters: null), - new NullLoggerFactory().CreateLogger(), - diagnosticSource, - actionContext, - new List(), - maxAllowedErrorsInModelState: 200); - return invoker; - } - - private class Pipeline1 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - - private class Pipeline2 - { - public static Action ConfigurePipeline { get; set; } - - public void Configure(IApplicationBuilder appBuilder) - { - ConfigurePipeline(appBuilder); - } - } - - private static HttpContext GetHttpContext() - { - var services = CreateServices(); - - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = services.BuildServiceProvider(); - - return httpContext; - } - - private RequestDelegate GetMiddlewarePipeline(Type middlewarePipelineProviderType) - { - var applicationServices = new ServiceCollection(); - var applicationBuilder = new ApplicationBuilder(applicationServices.BuildServiceProvider()); - var middlewareFilterBuilderService = new MiddlewareFilterBuilder( - new MiddlewareFilterConfigurationProvider()); - middlewareFilterBuilderService.ApplicationBuilder = applicationBuilder; - return middlewareFilterBuilderService.GetPipeline(middlewarePipelineProviderType); - } - - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - - services.AddSingleton(NullLoggerFactory.Instance); - return services; - } - - private class MockControllerFactory : IControllerFactory - { - private object _controller; - - public MockControllerFactory(object controller) - { - _controller = controller; - } - - public bool CreateCalled { get; private set; } - - public bool ReleaseCalled { get; private set; } - - public ControllerContext ControllerContext { get; private set; } - - public object CreateController(ControllerContext context) - { - ControllerContext = context; - CreateCalled = true; - return _controller; - } - - public void ReleaseController(ControllerContext context, object controller) - { - Assert.NotNull(controller); - Assert.Same(_controller, controller); - ReleaseCalled = true; - } - - public void Verify() - { - if (CreateCalled && !ReleaseCalled) - { - Assert.False(true, "ReleaseController should have been called."); - } - } - } - - private static ControllerActionInvokerCache CreateFilterCache(IFilterProvider[] filterProviders = null) - { - var services = new ServiceCollection().BuildServiceProvider(); - var descriptorProvider = new ActionDescriptorCollectionProvider(services); - return new ControllerActionInvokerCache( - descriptorProvider, - filterProviders.AsEnumerable() ?? new List()); - } - - private class TestControllerActionInvoker : ControllerActionInvoker - { - public TestControllerActionInvoker( - IFilterProvider[] filterProviders, - MockControllerFactory controllerFactory, - IControllerArgumentBinder argumentBinder, - ILogger logger, - DiagnosticSource diagnosticSource, - ActionContext actionContext, - IReadOnlyList valueProviderFactories, - int maxAllowedErrorsInModelState) - : base( - CreateFilterCache(filterProviders), - controllerFactory, - argumentBinder, - logger, - diagnosticSource, - actionContext, - valueProviderFactories, - maxAllowedErrorsInModelState) - { - ControllerFactory = controllerFactory; - } - - public MockControllerFactory ControllerFactory { get; } - - public async override Task InvokeAsync() - { - await base.InvokeAsync(); - - // Make sure that the controller was disposed in every test that creates ones. - ControllerFactory.Verify(); - } - } - - private class TestControllerArgumentBinder : IControllerArgumentBinder - { - private readonly IDictionary _actionParameters; - public TestControllerArgumentBinder(IDictionary actionParameters) - { - _actionParameters = actionParameters; - } - - public Task BindArgumentsAsync( - ControllerContext controllerContext, - object controller, - IDictionary arguments) - { - foreach (var entry in _actionParameters) - { - arguments.Add(entry.Key, entry.Value); - } - - return TaskCache.CompletedTask; - } - } - - private sealed class TestController - { - } - - - private enum TestResourceFilterAction - { - ShortCircuit, - ThrowException, - Passthrough - } - - private class TestResourceFilter : IAsyncResourceFilter - { - private readonly TestResourceFilterAction _action; - public TestResourceFilter(TestResourceFilterAction action) - { - _action = action; - } - - public ResourceExecutedContext ResourceExecutedContext { get; private set; } - - public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) - { - if (_action == TestResourceFilterAction.ThrowException) - { - throw new NotImplementedException("This filter should not have been run!"); - - } - else if (_action == TestResourceFilterAction.Passthrough) - { - ResourceExecutedContext = await next(); - } - else - { - context.Result = new TestActionResult(); - } - } - } - - public class TestActionResult : IActionResult - { - public Task ExecuteResultAsync(ActionContext context) - { - context.HttpContext.Response.StatusCode = 200; - return context.HttpContext.Response.WriteAsync("Shortcircuited"); - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs index bbc00f9c4c..ecb7c631fb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs @@ -536,23 +536,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType); Assert.Equal("Data:10", await response.Content.ReadAsStringAsync()); } - - [Theory] - [InlineData("en-US", "en-US")] - [InlineData("fr", "fr")] - [InlineData("ab-cd", "en-US")] - public async Task MiddlewareFilter_LocalizationMiddlewareRegistration_UsesRouteDataToFindCulture( - string culture, - string expected) - { - // Arrange & Act - var response = await Client.GetAsync($"http://localhost/{culture}/MiddlewareFilterTest/CultureFromRouteData"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal( - $"CurrentCulture:{expected},CurrentUICulture:{expected}", - await response.Content.ReadAsStringAsync()); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ChunkVisitorTestFactory.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ChunkVisitorTestFactory.cs new file mode 100644 index 0000000000..a9e0b09a8f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ChunkVisitorTestFactory.cs @@ -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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor; +using Microsoft.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.Chunks.Generators; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host +{ + public static class ChunkVisitorTestFactory + { + private static string _testClass = "TestClass"; + private static string _testNamespace = "TestNamespace"; + private static string _testFile = "TestFile"; + + public static CodeGeneratorContext CreateCodeGeneratorContext() + { + var language = new CSharpRazorCodeLanguage(); + var host = new RazorEngineHost(language); + var chunkGeneratorContext = new ChunkGeneratorContext + ( + host, + _testClass, + _testNamespace, + _testFile, + shouldGenerateLinePragmas: false + ); + + var codeGeneratorContext = new CodeGeneratorContext( + chunkGeneratorContext, + errorSink: new ErrorSink()); + return codeGeneratorContext; + } + + public static IList GetTestChunks(bool visitedTagHelperChunks) + { + return new List + { + GetTagHelperChunk("Baz"), + GetNestedViewComponentTagHelperChunk("Foo", visitedTagHelperChunks), + GetViewComponentTagHelperChunk("Bar", visitedTagHelperChunks), + }; + } + + private static TagHelperChunk GetTagHelperChunk(string name) + { + var tagHelperChunk = new TagHelperChunk( + name.ToLowerInvariant(), + TagMode.SelfClosing, + new List(), + new List + { + new TagHelperDescriptor + { + AssemblyName = $"{name}Assembly", + TagName = name.ToLowerInvariant(), + TypeName = $"{name}Type", + } + }); + + return tagHelperChunk; + } + + private static ParentChunk GetNestedViewComponentTagHelperChunk(string name, bool visitedTagHelperChunks) + { + var parentChunk = new ParentChunk(); + var tagHelperChunk = GetViewComponentTagHelperChunk(name, visitedTagHelperChunks); + parentChunk.Children.Add(tagHelperChunk); + return parentChunk; + } + + private static TagHelperChunk GetViewComponentTagHelperChunk(string name, bool visitedTagHelperChunks) + { + var typeName = visitedTagHelperChunks ? $"{_testNamespace}.{_testClass}.{name}Type" : $"{name}Type"; + + var attribute = new TagHelperAttributeDescriptor + { + Name = "attribute", + PropertyName = "Attribute", + TypeName = typeof(string).FullName + }; + + var requiredAttribute = new TagHelperRequiredAttributeDescriptor + { + Name = "Attribute" + }; + + var tagHelperDescriptor = new TagHelperDescriptor + { + AssemblyName = $"{name}Assembly", + TagName = name.ToLowerInvariant(), + TypeName = typeName, + Attributes = new[] + { + attribute + }, + RequiredAttributes = new[] + { + requiredAttribute + } + }; + + tagHelperDescriptor.PropertyBag.Add( + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, + name); + + var tagHelperChunk = new TagHelperChunk( + $"vc:{name.ToLowerInvariant()}", + TagMode.SelfClosing, + new List(), + new[] + { + tagHelperDescriptor + }); + + return tagHelperChunk; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/TagHelperChunkDecoratorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/TagHelperChunkDecoratorTest.cs new file mode 100644 index 0000000000..84914a0ff1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/TagHelperChunkDecoratorTest.cs @@ -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.Linq; +using Microsoft.AspNetCore.Mvc.Razor.Host.Internal; +using Microsoft.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.Parser.SyntaxTree; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test +{ + public class TagHelperChunkDecoratorTest + { + [Fact] + public void Accept_CorrectlyDecoratesViewComponentChunks() + { + // Arrange + var codeGeneratorContext = ChunkVisitorTestFactory.CreateCodeGeneratorContext(); + var syntaxTreeNode = new Mock(new SpanBuilder()); + foreach (var chunk in ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: false)) + { + codeGeneratorContext.ChunkTreeBuilder.AddChunk(chunk, syntaxTreeNode.Object); + } + + var tagHelperChunkDecorator = new TagHelperChunkDecorator(codeGeneratorContext); + var expectedChunks = ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: true); + + // Act + var resultChunks = codeGeneratorContext.ChunkTreeBuilder.Root.Children; + tagHelperChunkDecorator.Accept(resultChunks); + + // Assert + // Test the normal tag helper chunk, Baz. + Assert.Equal(expectedChunks.Count, resultChunks.Count); + + var expectedTagHelperChunk = (TagHelperChunk)expectedChunks[0]; + var resultTagHelperChunk = Assert.IsType(resultChunks[0]); + + Assert.Equal( + expectedTagHelperChunk.Descriptors.First().TypeName, + Assert.Single(resultTagHelperChunk.Descriptors).TypeName); + + // Test the parent chunk with view component tag helper inside, Foo. + var expectedParentChunk = (ParentChunk)expectedChunks[1]; + var resultParentChunk = Assert.IsType(resultChunks[1]); + Assert.Single(resultParentChunk.Children); + + expectedTagHelperChunk = (TagHelperChunk)expectedParentChunk.Children.First(); + resultTagHelperChunk = (TagHelperChunk)resultParentChunk.Children.First(); + + Assert.Equal(expectedTagHelperChunk.Descriptors.First().TypeName, + resultTagHelperChunk.Descriptors.First().TypeName, + StringComparer.Ordinal); + + // Test the view component tag helper, Bar. + expectedTagHelperChunk = expectedChunks.ElementAt(2) as TagHelperChunk; + resultTagHelperChunk = resultChunks.ElementAt(2) as TagHelperChunk; + Assert.NotNull(resultTagHelperChunk); + + Assert.Equal(expectedTagHelperChunk.Descriptors.First().TypeName, + resultTagHelperChunk.Descriptors.First().TypeName, + StringComparer.Ordinal); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ViewComponentTagHelperChunkVisitorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ViewComponentTagHelperChunkVisitorTest.cs new file mode 100644 index 0000000000..771e6780a0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/Internal/ViewComponentTagHelperChunkVisitorTest.cs @@ -0,0 +1,61 @@ +// 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; +using Microsoft.AspNetCore.Mvc.Razor.Host.Internal; +using Microsoft.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test.Internal +{ + public class ViewComponentTagHelperChunkVisitorTest + { + public static TheoryData CodeGenerationData + { + get + { + var oneInstanceChunks = ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: true); + var twoInstanceChunks = ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: true); + twoInstanceChunks.Add(twoInstanceChunks[twoInstanceChunks.Count - 1]); + + return new TheoryData> + { + oneInstanceChunks, + twoInstanceChunks + }; + } + } + + [Theory] + [MemberData(nameof(CodeGenerationData))] + public void Accept_CorrectlyGeneratesCode(IList chunks) + { + // Arrange + var writer = new CSharpCodeWriter(); + var context = ChunkVisitorTestFactory.CreateCodeGeneratorContext(); + var chunkVisitor = new ViewComponentTagHelperChunkVisitor(writer, context); + + var assembly = typeof(ViewComponentTagHelperChunkVisitorTest).GetTypeInfo().Assembly; + var path = "TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs"; + var expectedOutput = ResourceFile.ReadResource(assembly, path, sourceFile: true); + + // Act + chunkVisitor.Accept(chunks); + var resultOutput = writer.GenerateCode(); + +#if GENERATE_BASELINES + if (!string.Equals(expectedOutput, resultOutput, StringComparison.Ordinal)) + { + ResourceFile.UpdateFile(assembly, path, expectedOutput, resultOutput); + expectedOutput = ResourceFile.ReadResource(assembly, path, sourceFile: true); + } +#endif + + // Assert + Assert.Equal(expectedOutput, resultOutput, StringComparer.Ordinal); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs new file mode 100644 index 0000000000..9ff6e7d227 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs @@ -0,0 +1,38 @@ +[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("foo")] +public class __Generated__FooViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper +{ + private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _viewComponentHelper = null; + public __Generated__FooViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper viewComponentHelper) + { + _viewComponentHelper = viewComponentHelper; + } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext] + public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; } + public System.String Attribute { get; set; } + public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) + { + ((global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)_viewComponentHelper).Contextualize(ViewContext); + var viewContent = await _viewComponentHelper.InvokeAsync("Foo", new { Attribute }); + output.TagName = null; + output.Content.SetHtmlContent(viewContent); + } +} +[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("bar")] +public class __Generated__BarViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper +{ + private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _viewComponentHelper = null; + public __Generated__BarViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper viewComponentHelper) + { + _viewComponentHelper = viewComponentHelper; + } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext] + public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; } + public System.String Attribute { get; set; } + public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) + { + ((global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)_viewComponentHelper).Contextualize(ViewContext); + var viewContent = await _viewComponentHelper.InvokeAsync("Bar", new { Attribute }); + output.TagName = null; + output.Content.SetHtmlContent(viewContent); + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs deleted file mode 100644 index 5f399fafe0..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Host.Test/ViewComponentTagHelperDescriptorConventionsTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 deleted file mode 100644 index 93cd3f1bb8..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs +++ /dev/null @@ -1,193 +0,0 @@ -// 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 - }; - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs index be2ee35a4f..cabe292766 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -246,75 +246,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } - [Theory] - [InlineData(null, "datetime")] - [InlineData("hidden", "hidden")] - public void Process_GeneratesFormattedOutput(string specifiedType, string expectedType) - { - // Arrange - var expectedAttributes = new TagHelperAttributeList - { - { "type", expectedType }, - { "id", "DateTimeOffset" }, - { "name", "DateTimeOffset" }, - { "valid", "from validation attributes" }, - { "value", "datetime: 2011-08-31T05:30:45.0000000+03:00" }, - }; - var expectedTagName = "not-input"; - var container = new Model - { - DateTimeOffset = new DateTimeOffset(2011, 8, 31, hour: 5, minute: 30, second: 45, offset: TimeSpan.FromHours(3)) - }; - - var allAttributes = new TagHelperAttributeList - { - { "type", specifiedType }, - }; - var context = new TagHelperContext( - allAttributes: allAttributes, - items: new Dictionary(), - uniqueId: "test"); - var output = new TagHelperOutput( - expectedTagName, - new TagHelperAttributeList(), - getChildContentAsync: (useCachedResult, encoder) => - { - throw new Exception("getChildContentAsync should not be called."); - }) - { - TagMode = TagMode.StartTagOnly, - }; - - var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider()) - { - ValidationAttributes = - { - { "valid", "from validation attributes" }, - } - }; - - var tagHelper = GetTagHelper( - htmlGenerator, - container, - typeof(Model), - model: container.DateTimeOffset, - propertyName: nameof(Model.DateTimeOffset), - expressionName: nameof(Model.DateTimeOffset)); - tagHelper.Format = "datetime: {0:o}"; - tagHelper.InputTypeName = specifiedType; - - // Act - tagHelper.Process(context, output); - - // Assert - Assert.Equal(expectedAttributes, output.Attributes); - Assert.Empty(output.PreContent.GetContent()); - Assert.Empty(output.Content.GetContent()); - Assert.Empty(output.PostContent.GetContent()); - Assert.Equal(TagMode.StartTagOnly, output.TagMode); - Assert.Equal(expectedTagName, output.TagName); - } - [Fact] public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters() { @@ -352,8 +283,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var htmlGenerator = new Mock(MockBehavior.Strict); var tagHelper = GetTagHelper(htmlGenerator.Object, model: false, propertyName: nameof(Model.IsACar)); - tagHelper.Format = "somewhat-less-null"; // ignored - var tagBuilder = new TagBuilder("input") { Attributes = @@ -394,19 +323,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Theory] - [InlineData(null, "hidden", null, null)] - [InlineData(null, "Hidden", "not-null", "somewhat-less-null")] - [InlineData(null, "HIDden", null, "somewhat-less-null")] - [InlineData(null, "HIDDEN", "not-null", null)] - [InlineData("hiddeninput", null, null, null)] - [InlineData("HiddenInput", null, "not-null", null)] - [InlineData("hidDENinPUT", null, null, "somewhat-less-null")] - [InlineData("HIDDENINPUT", null, "not-null", "somewhat-less-null")] - public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParametersForHidden( + [InlineData(null, "hidden", null)] + [InlineData(null, "Hidden", "not-null")] + [InlineData(null, "HIDden", null)] + [InlineData(null, "HIDDEN", "not-null")] + [InlineData("hiddeninput", null, null)] + [InlineData("HiddenInput", null, "not-null")] + [InlineData("hidDENinPUT", null, null)] + [InlineData("HIDDENINPUT", null, "not-null")] + public async Task ProcessAsync_CallsGenerateHidden_WithExpectedParameters( string dataTypeName, string inputTypeName, - string model, - string format) + string model) { // Arrange var contextAttributes = new TagHelperAttributeList @@ -461,7 +389,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers model, nameof(Model.Text), metadataProvider: metadataProvider); - tagHelper.Format = format; tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input") @@ -472,13 +399,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers }, }; htmlGenerator - .Setup(mock => mock.GenerateTextBox( + .Setup(mock => mock.GenerateHidden( tagHelper.ViewContext, tagHelper.For.ModelExplorer, tagHelper.For.Name, - model, // value - format, - new Dictionary { { "type", "hidden" } })) // htmlAttributes + model, // value + false, // useViewData + null)) // htmlAttributes .Returns(tagBuilder) .Verifiable(); @@ -563,7 +490,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers model, nameof(Model.Text), metadataProvider: metadataProvider); - tagHelper.Format = "somewhat-less-null"; // ignored tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input") @@ -655,7 +581,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var htmlGenerator = new Mock(MockBehavior.Strict); var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text)); - tagHelper.Format = "somewhat-less-null"; // ignored tagHelper.InputTypeName = inputTypeName; tagHelper.Value = value; @@ -692,33 +617,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Theory] - [InlineData(null, null, null, "somewhat-less-null")] - [InlineData(null, null, "not-null", null)] - [InlineData(null, "string", null, null)] - [InlineData(null, "String", "not-null", null)] - [InlineData(null, "STRing", null, "somewhat-less-null")] - [InlineData(null, "STRING", "not-null", null)] - [InlineData(null, "text", null, null)] - [InlineData(null, "Text", "not-null", "somewhat-less-null")] - [InlineData(null, "TExt", null, null)] - [InlineData(null, "TEXT", "not-null", null)] - [InlineData("string", null, null, null)] - [InlineData("String", null, "not-null", null)] - [InlineData("STRing", null, null, null)] - [InlineData("STRING", null, "not-null", null)] - [InlineData("text", null, null, null)] - [InlineData("Text", null, "not-null", null)] - [InlineData("TExt", null, null, null)] - [InlineData("TEXT", null, "not-null", null)] - [InlineData("custom-datatype", null, null, null)] - [InlineData(null, "unknown-input-type", "not-null", null)] - [InlineData("Image", null, "not-null", "somewhat-less-null")] - [InlineData(null, "image", "not-null", null)] + [InlineData(null, null, null)] + [InlineData(null, null, "not-null")] + [InlineData(null, "string", null)] + [InlineData(null, "String", "not-null")] + [InlineData(null, "STRing", null)] + [InlineData(null, "STRING", "not-null")] + [InlineData(null, "text", null)] + [InlineData(null, "Text", "not-null")] + [InlineData(null, "TExt", null)] + [InlineData(null, "TEXT", "not-null")] + [InlineData("string", null, null)] + [InlineData("String", null, "not-null")] + [InlineData("STRing", null, null)] + [InlineData("STRING", null, "not-null")] + [InlineData("text", null, null)] + [InlineData("Text", null, "not-null")] + [InlineData("TExt", null, null)] + [InlineData("TEXT", null, "not-null")] + [InlineData("custom-datatype", null, null)] + [InlineData(null, "unknown-input-type", "not-null")] + [InlineData("Image", null, "not-null")] + [InlineData(null, "image", "not-null")] public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParameters( string dataTypeName, string inputTypeName, - string model, - string format) + string model) { // Arrange var contextAttributes = new TagHelperAttributeList @@ -773,7 +697,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers model, nameof(Model.Text), metadataProvider: metadataProvider); - tagHelper.Format = format; tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input") @@ -788,9 +711,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.ViewContext, tagHelper.For.ModelExplorer, tagHelper.For.Name, - model, // value - format, - It.Is>(m => m.ContainsKey("type")))) // htmlAttributes + model, // value + null, // format + It.Is>(m => m.ContainsKey("type")))) // htmlAttributes .Returns(tagBuilder) .Verifiable(); diff --git a/test/WebSites/FiltersWebSite/Controllers/MiddlewareFilterTestController.cs b/test/WebSites/FiltersWebSite/Controllers/MiddlewareFilterTestController.cs deleted file mode 100644 index 0b00c23b1c..0000000000 --- a/test/WebSites/FiltersWebSite/Controllers/MiddlewareFilterTestController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace FiltersWebSite -{ - public class MiddlewareFilterTestController : Controller - { - [Route("{culture}/[controller]/[action]")] - [MiddlewareFilter(typeof(LocalizationPipeline))] - public IActionResult CultureFromRouteData() - { - return Content($"CurrentCulture:{CultureInfo.CurrentCulture.Name},CurrentUICulture:{CultureInfo.CurrentUICulture.Name}"); - } - } -} diff --git a/test/WebSites/FiltersWebSite/Filters/TestResourceFilter.cs b/test/WebSites/FiltersWebSite/Filters/TestResourceFilter.cs deleted file mode 100644 index 6d7f8e0715..0000000000 --- a/test/WebSites/FiltersWebSite/Filters/TestResourceFilter.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace FiltersWebSite -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class TestSyncResourceFilter : Attribute, IResourceFilter, IOrderedFilter - { - public enum Action - { - PassThrough, - ThrowException, - Shortcircuit - } - - public readonly string ExceptionMessage = $"Error!! in {nameof(TestSyncResourceFilter)}"; - - public readonly string ShortcircuitMessage = $"Shortcircuited by {nameof(TestSyncResourceFilter)}"; - - public Action FilterAction { get; set; } - - public int Order { get; set; } - - public void OnResourceExecuted(ResourceExecutedContext context) - { - } - - public void OnResourceExecuting(ResourceExecutingContext context) - { - if (FilterAction == Action.PassThrough) - { - return; - } - else if (FilterAction == Action.ThrowException) - { - throw new InvalidOperationException(ExceptionMessage); - } - else - { - context.Result = new ContentResult() - { - Content = ShortcircuitMessage, - StatusCode = 400, - ContentType = "text/abcd" - }; - } - } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class TestAsyncResourceFilter : Attribute, IAsyncResourceFilter, IOrderedFilter - { - public enum Action - { - PassThrough, - ThrowException, - Shortcircuit - } - - public readonly string ExceptionMessage = $"Error!! in {nameof(TestAsyncResourceFilter)}"; - - public readonly string ShortcircuitMessage = $"Shortcircuited by {nameof(TestAsyncResourceFilter)}"; - - public Action FilterAction { get; set; } - - public int Order { get; set; } - - public Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) - { - if (FilterAction == Action.PassThrough) - { - return next(); - } - else if (FilterAction == Action.ThrowException) - { - throw new InvalidOperationException(ExceptionMessage); - } - else - { - context.Result = new ContentResult() - { - Content = ShortcircuitMessage, - StatusCode = 400, - ContentType = "text/abcd" - }; - return Task.FromResult(true); - } - } - } -} diff --git a/test/WebSites/FiltersWebSite/LocalizationPipeline.cs b/test/WebSites/FiltersWebSite/LocalizationPipeline.cs deleted file mode 100644 index d61e355c66..0000000000 --- a/test/WebSites/FiltersWebSite/LocalizationPipeline.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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.AspNetCore.Builder; -using Microsoft.AspNetCore.Localization; -using Microsoft.AspNetCore.Mvc.Localization; - -namespace FiltersWebSite -{ - public class LocalizationPipeline - { - public void Configure(IApplicationBuilder applicationBuilder) - { - var supportedCultures = new[] - { - new CultureInfo("en-US"), - new CultureInfo("fr") - }; - - var options = new RequestLocalizationOptions() - { - DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"), - SupportedCultures = supportedCultures, - SupportedUICultures = supportedCultures - }; - options.RequestCultureProviders = new[] { new RouteDataRequestCultureProvider() { Options = options } }; - - applicationBuilder.UseRequestLocalization(options); - } - } -} diff --git a/test/WebSites/FiltersWebSite/RouteDataRequestCultureProvider.cs b/test/WebSites/FiltersWebSite/RouteDataRequestCultureProvider.cs deleted file mode 100644 index 0f005d5498..0000000000 --- a/test/WebSites/FiltersWebSite/RouteDataRequestCultureProvider.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Localization; - -namespace FiltersWebSite -{ - /// - /// Determines the culture information for a request via values in the route data. - /// - public class RouteDataRequestCultureProvider : RequestCultureProvider - { - /// - /// The key that contains the culture name. - /// Defaults to "culture". - /// - public string RouteDataStringKey { get; set; } = "culture"; - - /// - /// The key that contains the UI culture name. If not specified or no value is found, - /// will be used. - /// Defaults to "ui-culture". - /// - public string UIRouteDataStringKey { get; set; } = "ui-culture"; - - /// - public override Task DetermineProviderCultureResult(HttpContext httpContext) - { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - string culture = null; - string uiCulture = null; - - if (!string.IsNullOrWhiteSpace(RouteDataStringKey)) - { - culture = httpContext.GetRouteValue(RouteDataStringKey) as string; - } - - if (!string.IsNullOrWhiteSpace(UIRouteDataStringKey)) - { - uiCulture = httpContext.GetRouteValue(UIRouteDataStringKey) as string; - } - - if (culture == null && uiCulture == null) - { - // No values specified for either so no match - return Task.FromResult((ProviderCultureResult)null); - } - - if (culture != null && uiCulture == null) - { - // Value for culture but not for UI culture so default to culture value for both - uiCulture = culture; - } - - if (culture == null && uiCulture != null) - { - // Value for UI culture but not for culture so default to UI culture value for both - culture = uiCulture; - } - - var providerResultCulture = new ProviderCultureResult(culture, uiCulture); - - return Task.FromResult(providerResultCulture); - } - } - -}