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!
This commit is contained in:
parent
830983a477
commit
05392cbf35
|
|
@ -85,9 +85,6 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
"ConfigureServices(...)"));
|
"ConfigureServices(...)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>();
|
|
||||||
middlewarePipelineBuilder.ApplicationBuilder = app.New();
|
|
||||||
|
|
||||||
var routes = new RouteBuilder(app)
|
var routes = new RouteBuilder(app)
|
||||||
{
|
{
|
||||||
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
|
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
|
||||||
|
|
|
||||||
|
|
@ -222,13 +222,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
//
|
//
|
||||||
services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
|
services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
|
||||||
services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
|
services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
|
||||||
|
|
||||||
//
|
|
||||||
// Middleware pipeline filter related
|
|
||||||
//
|
|
||||||
services.TryAddSingleton<MiddlewareFilterConfigurationProvider>();
|
|
||||||
// This maintains a cache of middleware pipelines, so it needs to be a singleton
|
|
||||||
services.TryAddSingleton<MiddlewareFilterBuilder>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureDefaultServices(IServiceCollection services)
|
private static void ConfigureDefaultServices(IServiceCollection services)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Executes a middleware pipeline provided the by the <see cref="MiddlewareFilterAttribute.ConfigurationType"/>.
|
|
||||||
/// The middleware pipeline will be treated as an async resource filter.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
|
||||||
public class MiddlewareFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Instantiates a new instance of <see cref="MiddlewareFilterAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configurationType">A type which configures a middleware pipeline.</param>
|
|
||||||
public MiddlewareFilterAttribute(Type configurationType)
|
|
||||||
{
|
|
||||||
if (configurationType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(configurationType));
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurationType = configurationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type ConfigurationType { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Order { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsReusable { get; } = true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
if (serviceProvider == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(serviceProvider));
|
|
||||||
}
|
|
||||||
|
|
||||||
var middlewarePipelineService = serviceProvider.GetRequiredService<MiddlewareFilterBuilder>();
|
|
||||||
var pipeline = middlewarePipelineService.GetPipeline(ConfigurationType);
|
|
||||||
|
|
||||||
return new MiddlewareFilter(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A feature in <see cref="Microsoft.AspNetCore.Http.HttpContext.Features"/> 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.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMiddlewareFilterFeature
|
|
||||||
{
|
|
||||||
ResourceExecutingContext ResourceExecutingContext { get; }
|
|
||||||
|
|
||||||
ResourceExecutionDelegate ResourceExecutionDelegate { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A filter which executes a user configured middleware pipeline.
|
|
||||||
/// </summary>
|
|
||||||
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<IMiddlewareFilterFeature>(feature);
|
|
||||||
|
|
||||||
return _middlewarePipeline(httpContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Builds a middleware pipeline after receiving the pipeline from a pipeline provider
|
|
||||||
/// </summary>
|
|
||||||
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<Type, Lazy<RequestDelegate>> _pipelinesCache
|
|
||||||
= new ConcurrentDictionary<Type, Lazy<RequestDelegate>>();
|
|
||||||
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<RequestDelegate>(() => 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<IMiddlewareFilterFeature>();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public class MiddlewareFilterConfigurationProvider
|
|
||||||
{
|
|
||||||
public Action<IApplicationBuilder> 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<IApplicationBuilder> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1210,102 +1210,6 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormattersAreRequired"), p0, p1, p2);
|
return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormattersAreRequired"), p0, p1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Having multiple overloads of method '{0}' is not supported.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddewareFilter_ConfigureMethodOverload
|
|
||||||
{
|
|
||||||
get { return GetString("MiddewareFilter_ConfigureMethodOverload"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Having multiple overloads of method '{0}' is not supported.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMiddewareFilter_ConfigureMethodOverload(object p0)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_ConfigureMethodOverload"), p0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A public method named '{0}' could not be found in the '{1}' type.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddewareFilter_NoConfigureMethod
|
|
||||||
{
|
|
||||||
get { return GetString("MiddewareFilter_NoConfigureMethod"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A public method named '{0}' could not be found in the '{1}' type.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMiddewareFilter_NoConfigureMethod(object p0, object p1)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_NoConfigureMethod"), p0, p1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not find '{0}' in the feature list.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddlewareFilterBuilder_NoMiddlewareFeature
|
|
||||||
{
|
|
||||||
get { return GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not find '{0}' in the feature list.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMiddlewareFilterBuilder_NoMiddlewareFeature(object p0)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"), p0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// '{0}' property cannot be null.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddlewareFilterBuilder_NullApplicationBuilder
|
|
||||||
{
|
|
||||||
get { return GetString("MiddlewareFilterBuilder_NullApplicationBuilder"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// '{0}' property cannot be null.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMiddlewareFilterBuilder_NullApplicationBuilder(object p0)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NullApplicationBuilder"), p0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The '{0}' method in the type '{1}' must have a return type of '{2}'.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddlewareFilter_InvalidConfigureReturnType
|
|
||||||
{
|
|
||||||
get { return GetString("MiddlewareFilter_InvalidConfigureReturnType"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The '{0}' method in the type '{1}' must have a return type of '{2}'.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMiddlewareFilter_InvalidConfigureReturnType(object p0, object p1, object p2)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilter_InvalidConfigureReturnType"), p0, p1, p2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MiddlewareFilter_ServiceResolutionFail
|
|
||||||
{
|
|
||||||
get { return GetString("MiddlewareFilter_ServiceResolutionFail"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.
|
|
||||||
/// </summary>
|
|
||||||
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -352,22 +352,4 @@
|
||||||
<data name="OutputFormattersAreRequired" xml:space="preserve">
|
<data name="OutputFormattersAreRequired" xml:space="preserve">
|
||||||
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to format a response.</value>
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to format a response.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MiddewareFilter_ConfigureMethodOverload" xml:space="preserve">
|
|
||||||
<value>Multiple overloads of method '{0}' are not supported.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MiddewareFilter_NoConfigureMethod" xml:space="preserve">
|
|
||||||
<value>A public method named '{0}' could not be found in the '{1}' type.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MiddlewareFilterBuilder_NoMiddlewareFeature" xml:space="preserve">
|
|
||||||
<value>Could not find '{0}' in the feature list.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MiddlewareFilterBuilder_NullApplicationBuilder" xml:space="preserve">
|
|
||||||
<value>The '{0}' property cannot be null.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MiddlewareFilter_InvalidConfigureReturnType" xml:space="preserve">
|
|
||||||
<value>The '{0}' method in the type '{1}' must have a return type of '{2}'.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MiddlewareFilter_ServiceResolutionFail" xml:space="preserve">
|
|
||||||
<value>Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains necessary information for the view component <see cref="AspNetCore.Razor.TagHelpers.TagHelper"/> code generation process.
|
||||||
|
/// </summary>
|
||||||
|
public class GeneratedViewComponentTagHelperContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="GeneratedViewComponentTagHelperContext"/> with default values.
|
||||||
|
/// </summary>
|
||||||
|
public GeneratedViewComponentTagHelperContext()
|
||||||
|
{
|
||||||
|
ContextualizeMethodName = "Contextualize";
|
||||||
|
InvokeAsyncMethodName = "InvokeAsync";
|
||||||
|
IViewComponentHelperTypeName = "Microsoft.AspNetCore.Mvc.IViewComponentHelper";
|
||||||
|
IViewContextAwareTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware";
|
||||||
|
ViewContextTypeName = "Microsoft.AspNetCore.Mvc.Rendering.ViewContext";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the Contextualize method called by an instance of the IViewContextAware type.
|
||||||
|
/// </summary>
|
||||||
|
public string ContextualizeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the InvokeAsync method called by an IViewComponentHelper.
|
||||||
|
/// </summary>
|
||||||
|
public string InvokeAsyncMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the IViewComponentHelper type used to invoke view components.
|
||||||
|
/// </summary>
|
||||||
|
public string IViewComponentHelperTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the IViewContextAware type used to contextualize the view context.
|
||||||
|
/// </summary>
|
||||||
|
public string IViewContextAwareTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the ViewContext type for view execution.
|
||||||
|
/// </summary>
|
||||||
|
public string ViewContextTypeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<CSharpCodeWriter>
|
||||||
|
{
|
||||||
|
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<TagHelperDescriptor> Decorate(IEnumerable<TagHelperDescriptor> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<CSharpCodeWriter>
|
||||||
|
{
|
||||||
|
private readonly GeneratedViewComponentTagHelperContext _context;
|
||||||
|
private readonly HashSet<string> _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<string>(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<string, string>(
|
||||||
|
$"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<string, string>()
|
||||||
|
{
|
||||||
|
{ 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(")]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Directives;
|
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;
|
||||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||||
|
|
||||||
|
|
@ -12,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
public class MvcCSharpCodeGenerator : CSharpCodeGenerator
|
public class MvcCSharpCodeGenerator : CSharpCodeGenerator
|
||||||
{
|
{
|
||||||
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
||||||
|
private readonly TagHelperChunkDecorator _tagHelperChunkDecorator;
|
||||||
private readonly string _defaultModel;
|
private readonly string _defaultModel;
|
||||||
private readonly string _injectAttribute;
|
private readonly string _injectAttribute;
|
||||||
|
|
||||||
|
|
@ -45,6 +49,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
_tagHelperAttributeContext = tagHelperAttributeContext;
|
_tagHelperAttributeContext = tagHelperAttributeContext;
|
||||||
_defaultModel = defaultModel;
|
_defaultModel = defaultModel;
|
||||||
_injectAttribute = injectAttribute;
|
_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)
|
protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
|
||||||
|
|
@ -62,6 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return base.BuildClassDeclaration(writer);
|
return base.BuildClassDeclaration(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void BuildAfterExecuteContent(CSharpCodeWriter writer, IList<Chunk> chunks)
|
||||||
|
{
|
||||||
|
new ViewComponentTagHelperChunkVisitor(writer, Context).Accept(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
|
protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
|
||||||
CSharpCodeWriter writer,
|
CSharpCodeWriter writer,
|
||||||
CodeGeneratorContext context)
|
CodeGeneratorContext context)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A library of methods used to generate <see cref="TagHelperDescriptor"/>s for view components.
|
|
||||||
/// </summary>
|
|
||||||
public static class ViewComponentTagHelperDescriptorConventions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The key in a <see cref="TagHelperDescriptor.PropertyBag"/> containing
|
|
||||||
/// the short name of a view component.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly string ViewComponentNameKey = "ViewComponentName";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether a <see cref="TagHelperDescriptor"/> represents a view component.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="descriptor">The <see cref="TagHelperDescriptor"/> to check.</param>
|
|
||||||
/// <returns>Whether a <see cref="TagHelperDescriptor"/> represents a view component.</returns>
|
|
||||||
public static bool IsViewComponentDescriptor(TagHelperDescriptor descriptor) =>
|
|
||||||
descriptor != null && descriptor.PropertyBag.ContainsKey(ViewComponentNameKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides methods to create tag helper representations of view components.
|
|
||||||
/// </summary>
|
|
||||||
public class ViewComponentTagHelperDescriptorFactory
|
|
||||||
{
|
|
||||||
private readonly IViewComponentDescriptorProvider _descriptorProvider;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="ViewComponentTagHelperDescriptorFactory"/>,
|
|
||||||
/// then creates <see cref="TagHelperDescriptor"/>s for <see cref="ViewComponent"/>s
|
|
||||||
/// in the given <see cref="IViewComponentDescriptorProvider"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="descriptorProvider">The provider of <see cref="ViewComponentDescriptor"/>s.</param>
|
|
||||||
public ViewComponentTagHelperDescriptorFactory(IViewComponentDescriptorProvider descriptorProvider)
|
|
||||||
{
|
|
||||||
if (descriptorProvider == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(descriptorProvider));
|
|
||||||
}
|
|
||||||
|
|
||||||
_descriptorProvider = descriptorProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates <see cref="TagHelperDescriptor"/> representations of <see cref="ViewComponent"/>s
|
|
||||||
/// in an <see href="Assembly"/> represented by the given <paramref name="assemblyName"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyName">The name of the assembly containing
|
|
||||||
/// the <see cref="ViewComponent"/>s to translate.</param>
|
|
||||||
/// <returns>A <see cref="IEnumerable{TagHelperDescriptor}"/>,
|
|
||||||
/// one for each <see cref="ViewComponent"/>.</returns>
|
|
||||||
public IEnumerable<TagHelperDescriptor> 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<TagHelperAttributeDescriptor>();
|
|
||||||
|
|
||||||
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)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -98,8 +98,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
/// attribute to that formatted <see cref="string"/>.
|
/// attribute to that formatted <see cref="string"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Not used if the provided (see <see cref="InputTypeName"/>) or calculated "type" attribute value is
|
/// Used only the calculated "type" attribute is "text" (the most common value) e.g.
|
||||||
/// <c>checkbox</c>, <c>password</c>, or <c>radio</c>. That is, <see cref="Format"/> is used when calling
|
/// <see cref="InputTypeName"/> is "String". That is, <see cref="Format"/> is used when calling
|
||||||
/// <see cref="IHtmlGenerator.GenerateTextBox"/>.
|
/// <see cref="IHtmlGenerator.GenerateTextBox"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[HtmlAttributeName(FormatAttributeName)]
|
[HtmlAttributeName(FormatAttributeName)]
|
||||||
|
|
@ -110,9 +110,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Passed through to the generated HTML in all cases. Also used to determine the <see cref="IHtmlGenerator"/>
|
/// Passed through to the generated HTML in all cases. Also used to determine the <see cref="IHtmlGenerator"/>
|
||||||
/// helper to call and the default <see cref="Format"/> value. A default <see cref="Format"/> is not calculated
|
/// helper to call and the default <see cref="Format"/> value (when calling
|
||||||
/// if the provided (see <see cref="InputTypeName"/>) or calculated "type" attribute value is <c>checkbox</c>,
|
/// <see cref="IHtmlGenerator.GenerateTextBox"/>).
|
||||||
/// <c>hidden</c>, <c>password</c>, or <c>radio</c>.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[HtmlAttributeName("type")]
|
[HtmlAttributeName("type")]
|
||||||
public string InputTypeName { get; set; }
|
public string InputTypeName { get; set; }
|
||||||
|
|
@ -190,14 +189,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
TagBuilder tagBuilder;
|
TagBuilder tagBuilder;
|
||||||
switch (inputType)
|
switch (inputType)
|
||||||
{
|
{
|
||||||
case "hidden":
|
|
||||||
tagBuilder = GenerateHidden(modelExplorer);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "checkbox":
|
case "checkbox":
|
||||||
GenerateCheckBox(modelExplorer, output);
|
GenerateCheckBox(modelExplorer, output);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case "hidden":
|
||||||
|
tagBuilder = Generator.GenerateHidden(
|
||||||
|
ViewContext,
|
||||||
|
modelExplorer,
|
||||||
|
For.Name,
|
||||||
|
value: For.Model,
|
||||||
|
useViewData: false,
|
||||||
|
htmlAttributes: null);
|
||||||
|
break;
|
||||||
|
|
||||||
case "password":
|
case "password":
|
||||||
tagBuilder = Generator.GeneratePassword(
|
tagBuilder = Generator.GeneratePassword(
|
||||||
ViewContext,
|
ViewContext,
|
||||||
|
|
@ -359,34 +364,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
htmlAttributes: htmlAttributes);
|
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<string, object>
|
|
||||||
{
|
|
||||||
{ "type", "hidden" }
|
|
||||||
};
|
|
||||||
|
|
||||||
return Generator.GenerateTextBox(
|
|
||||||
ViewContext,
|
|
||||||
modelExplorer,
|
|
||||||
For.Name,
|
|
||||||
value: value,
|
|
||||||
format: Format,
|
|
||||||
htmlAttributes: htmlAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a fall-back format based on the metadata.
|
// Get a fall-back format based on the metadata.
|
||||||
private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
|
private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
|
||||||
|
|
@ -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<MiddlewareFilterBuilder>();
|
|
||||||
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<MiddlewareFilter>(filter);
|
|
||||||
Assert.NotNull(middlewareFilter);
|
|
||||||
Assert.Equal(1, configureCallCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Pipeline1
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> ConfigurePipeline { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
ConfigurePipeline(appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Pipeline2
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> ConfigurePipeline { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
ConfigurePipeline(appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<InvalidOperationException>(() => 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<IFilterMetadata>());
|
|
||||||
context.Exception = new InvalidOperationException("Error!!!");
|
|
||||||
return Task.FromResult(context);
|
|
||||||
};
|
|
||||||
var features = new FeatureCollection();
|
|
||||||
features.Set<IMiddlewareFilterFeature>(middlewareFilterFeature);
|
|
||||||
var httpContext = new DefaultHttpContext(features);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var pipeline = pipelineBuilderService.GetPipeline(typeof(Pipeline1));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(pipeline);
|
|
||||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => pipeline(httpContext));
|
|
||||||
Assert.Equal("Error!!!", exception.Message);
|
|
||||||
}
|
|
||||||
private class Pipeline1
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> ConfigurePipeline { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
ConfigurePipeline(appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Pipeline2
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> ConfigurePipeline { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
ConfigurePipeline(appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<ILoggerFactory>();
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddSingleton(loggerFactory);
|
|
||||||
services.AddSingleton(Mock.Of<IHostingEnvironment>());
|
|
||||||
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<InvalidOperationException>(() =>
|
|
||||||
{
|
|
||||||
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<InvalidOperationException>(() =>
|
|
||||||
{
|
|
||||||
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<IApplicationBuilder>();
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<IMiddlewareFilterFeature>();
|
|
||||||
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<InvalidOperationException>(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<InvalidOperationException>(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<IFilterMetadata>(),
|
|
||||||
new List<IValueProviderFactory>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResourceExecutionDelegate GetResourceExecutionDelegate(HttpContext httpContext)
|
|
||||||
{
|
|
||||||
return new ResourceExecutionDelegate(
|
|
||||||
() => Task.FromResult(new ResourceExecutedContext(new ActionContext(), new List<IFilterMetadata>())));
|
|
||||||
}
|
|
||||||
|
|
||||||
private TestControllerActionInvoker CreateInvoker(
|
|
||||||
IFilterMetadata[] filters,
|
|
||||||
bool actionThrows = false)
|
|
||||||
{
|
|
||||||
var actionDescriptor = new ControllerActionDescriptor()
|
|
||||||
{
|
|
||||||
FilterDescriptors = new List<FilterDescriptor>(),
|
|
||||||
Parameters = new List<ParameterDescriptor>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
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<IOptions<MvcOptions>>();
|
|
||||||
optionsAccessor
|
|
||||||
.SetupGet(o => o.Value)
|
|
||||||
.Returns(options);
|
|
||||||
|
|
||||||
var actionContext = new ActionContext(httpContext, new RouteData(), actionDescriptor);
|
|
||||||
|
|
||||||
var filterProvider = new Mock<IFilterProvider>(MockBehavior.Strict);
|
|
||||||
filterProvider
|
|
||||||
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
|
||||||
.Callback<FilterProviderContext>(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<FilterProviderContext>()))
|
|
||||||
.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<ControllerActionInvoker>(),
|
|
||||||
diagnosticSource,
|
|
||||||
actionContext,
|
|
||||||
new List<IValueProviderFactory>(),
|
|
||||||
maxAllowedErrorsInModelState: 200);
|
|
||||||
return invoker;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Pipeline1
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> ConfigurePipeline { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
ConfigurePipeline(appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Pipeline2
|
|
||||||
{
|
|
||||||
public static Action<IApplicationBuilder> 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<ILoggerFactory>(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<IFilterProvider>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestControllerActionInvoker : ControllerActionInvoker
|
|
||||||
{
|
|
||||||
public TestControllerActionInvoker(
|
|
||||||
IFilterProvider[] filterProviders,
|
|
||||||
MockControllerFactory controllerFactory,
|
|
||||||
IControllerArgumentBinder argumentBinder,
|
|
||||||
ILogger logger,
|
|
||||||
DiagnosticSource diagnosticSource,
|
|
||||||
ActionContext actionContext,
|
|
||||||
IReadOnlyList<IValueProviderFactory> 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<string, object> _actionParameters;
|
|
||||||
public TestControllerArgumentBinder(IDictionary<string, object> actionParameters)
|
|
||||||
{
|
|
||||||
_actionParameters = actionParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task BindArgumentsAsync(
|
|
||||||
ControllerContext controllerContext,
|
|
||||||
object controller,
|
|
||||||
IDictionary<string, object> 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -536,23 +536,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
|
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
|
||||||
Assert.Equal("Data:10", await response.Content.ReadAsStringAsync());
|
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<Chunk> GetTestChunks(bool visitedTagHelperChunks)
|
||||||
|
{
|
||||||
|
return new List<Chunk>
|
||||||
|
{
|
||||||
|
GetTagHelperChunk("Baz"),
|
||||||
|
GetNestedViewComponentTagHelperChunk("Foo", visitedTagHelperChunks),
|
||||||
|
GetViewComponentTagHelperChunk("Bar", visitedTagHelperChunks),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperChunk GetTagHelperChunk(string name)
|
||||||
|
{
|
||||||
|
var tagHelperChunk = new TagHelperChunk(
|
||||||
|
name.ToLowerInvariant(),
|
||||||
|
TagMode.SelfClosing,
|
||||||
|
new List<TagHelperAttributeTracker>(),
|
||||||
|
new List<TagHelperDescriptor>
|
||||||
|
{
|
||||||
|
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<TagHelperAttributeTracker>(),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
tagHelperDescriptor
|
||||||
|
});
|
||||||
|
|
||||||
|
return tagHelperChunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Span>(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<TagHelperChunk>(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<ParentChunk>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<IList<Chunk>>
|
||||||
|
{
|
||||||
|
oneInstanceChunks,
|
||||||
|
twoInstanceChunks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CodeGenerationData))]
|
||||||
|
public void Accept_CorrectlyGeneratesCode(IList<Chunk> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<string, IEnumerable<TagHelperDescriptor>>
|
|
||||||
{
|
|
||||||
{ assemblyOne, new [] { provider.GetTagHelperDescriptorOne() } },
|
|
||||||
{ assemblyTwo, new [] { provider.GetTagHelperDescriptorTwo() } },
|
|
||||||
{ assemblyNone, Enumerable.Empty<TagHelperDescriptor>() }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(AssemblyData))]
|
|
||||||
public void CreateDescriptors_ReturnsCorrectDescriptors(
|
|
||||||
string assemblyName,
|
|
||||||
IEnumerable<TagHelperDescriptor> 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<string, int> 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<TagHelperAttributeDescriptor>
|
|
||||||
{
|
|
||||||
new TagHelperAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "foo",
|
|
||||||
PropertyName = "foo",
|
|
||||||
TypeName = typeof(string).FullName
|
|
||||||
},
|
|
||||||
new TagHelperAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "bar",
|
|
||||||
PropertyName = "bar",
|
|
||||||
TypeName = typeof(string).FullName
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
|
|
||||||
{
|
|
||||||
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<TagHelperAttributeDescriptor>
|
|
||||||
{
|
|
||||||
new TagHelperAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "test-enum",
|
|
||||||
PropertyName = "testEnum",
|
|
||||||
TypeName = typeof(TestEnum).FullName,
|
|
||||||
IsEnum = true
|
|
||||||
},
|
|
||||||
|
|
||||||
new TagHelperAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "test-dictionary",
|
|
||||||
PropertyName = "testDictionary",
|
|
||||||
TypeName = typeof(Dictionary<string, int>).FullName
|
|
||||||
},
|
|
||||||
|
|
||||||
new TagHelperAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "baz",
|
|
||||||
PropertyName = "baz",
|
|
||||||
TypeName = typeof(int).FullName
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
|
|
||||||
{
|
|
||||||
new TagHelperRequiredAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "test-enum"
|
|
||||||
},
|
|
||||||
|
|
||||||
new TagHelperRequiredAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "test-dictionary"
|
|
||||||
},
|
|
||||||
|
|
||||||
new TagHelperRequiredAttributeDescriptor
|
|
||||||
{
|
|
||||||
Name = "baz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
descriptor.PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "Two");
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ViewComponentDescriptor> GetViewComponents()
|
|
||||||
{
|
|
||||||
return new List<ViewComponentDescriptor>
|
|
||||||
{
|
|
||||||
_viewComponentDescriptorOne,
|
|
||||||
_viewComponentDescriptorTwo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -246,75 +246,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Assert.Equal(expectedTagName, output.TagName);
|
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<object, object>(),
|
|
||||||
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]
|
[Fact]
|
||||||
public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters()
|
public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters()
|
||||||
{
|
{
|
||||||
|
|
@ -352,8 +283,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||||
var tagHelper = GetTagHelper(htmlGenerator.Object, model: false, propertyName: nameof(Model.IsACar));
|
var tagHelper = GetTagHelper(htmlGenerator.Object, model: false, propertyName: nameof(Model.IsACar));
|
||||||
tagHelper.Format = "somewhat-less-null"; // ignored
|
|
||||||
|
|
||||||
var tagBuilder = new TagBuilder("input")
|
var tagBuilder = new TagBuilder("input")
|
||||||
{
|
{
|
||||||
Attributes =
|
Attributes =
|
||||||
|
|
@ -394,19 +323,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(null, "hidden", null, null)]
|
[InlineData(null, "hidden", null)]
|
||||||
[InlineData(null, "Hidden", "not-null", "somewhat-less-null")]
|
[InlineData(null, "Hidden", "not-null")]
|
||||||
[InlineData(null, "HIDden", null, "somewhat-less-null")]
|
[InlineData(null, "HIDden", null)]
|
||||||
[InlineData(null, "HIDDEN", "not-null", null)]
|
[InlineData(null, "HIDDEN", "not-null")]
|
||||||
[InlineData("hiddeninput", null, null, null)]
|
[InlineData("hiddeninput", null, null)]
|
||||||
[InlineData("HiddenInput", null, "not-null", null)]
|
[InlineData("HiddenInput", null, "not-null")]
|
||||||
[InlineData("hidDENinPUT", null, null, "somewhat-less-null")]
|
[InlineData("hidDENinPUT", null, null)]
|
||||||
[InlineData("HIDDENINPUT", null, "not-null", "somewhat-less-null")]
|
[InlineData("HIDDENINPUT", null, "not-null")]
|
||||||
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParametersForHidden(
|
public async Task ProcessAsync_CallsGenerateHidden_WithExpectedParameters(
|
||||||
string dataTypeName,
|
string dataTypeName,
|
||||||
string inputTypeName,
|
string inputTypeName,
|
||||||
string model,
|
string model)
|
||||||
string format)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contextAttributes = new TagHelperAttributeList
|
var contextAttributes = new TagHelperAttributeList
|
||||||
|
|
@ -461,7 +389,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
model,
|
model,
|
||||||
nameof(Model.Text),
|
nameof(Model.Text),
|
||||||
metadataProvider: metadataProvider);
|
metadataProvider: metadataProvider);
|
||||||
tagHelper.Format = format;
|
|
||||||
tagHelper.InputTypeName = inputTypeName;
|
tagHelper.InputTypeName = inputTypeName;
|
||||||
|
|
||||||
var tagBuilder = new TagBuilder("input")
|
var tagBuilder = new TagBuilder("input")
|
||||||
|
|
@ -472,13 +399,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
htmlGenerator
|
htmlGenerator
|
||||||
.Setup(mock => mock.GenerateTextBox(
|
.Setup(mock => mock.GenerateHidden(
|
||||||
tagHelper.ViewContext,
|
tagHelper.ViewContext,
|
||||||
tagHelper.For.ModelExplorer,
|
tagHelper.For.ModelExplorer,
|
||||||
tagHelper.For.Name,
|
tagHelper.For.Name,
|
||||||
model, // value
|
model, // value
|
||||||
format,
|
false, // useViewData
|
||||||
new Dictionary<string, object> { { "type", "hidden" } })) // htmlAttributes
|
null)) // htmlAttributes
|
||||||
.Returns(tagBuilder)
|
.Returns(tagBuilder)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
|
|
@ -563,7 +490,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
model,
|
model,
|
||||||
nameof(Model.Text),
|
nameof(Model.Text),
|
||||||
metadataProvider: metadataProvider);
|
metadataProvider: metadataProvider);
|
||||||
tagHelper.Format = "somewhat-less-null"; // ignored
|
|
||||||
tagHelper.InputTypeName = inputTypeName;
|
tagHelper.InputTypeName = inputTypeName;
|
||||||
|
|
||||||
var tagBuilder = new TagBuilder("input")
|
var tagBuilder = new TagBuilder("input")
|
||||||
|
|
@ -655,7 +581,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||||
var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text));
|
var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text));
|
||||||
tagHelper.Format = "somewhat-less-null"; // ignored
|
|
||||||
tagHelper.InputTypeName = inputTypeName;
|
tagHelper.InputTypeName = inputTypeName;
|
||||||
tagHelper.Value = value;
|
tagHelper.Value = value;
|
||||||
|
|
||||||
|
|
@ -692,33 +617,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(null, null, null, "somewhat-less-null")]
|
[InlineData(null, null, null)]
|
||||||
[InlineData(null, null, "not-null", null)]
|
[InlineData(null, null, "not-null")]
|
||||||
[InlineData(null, "string", null, null)]
|
[InlineData(null, "string", null)]
|
||||||
[InlineData(null, "String", "not-null", null)]
|
[InlineData(null, "String", "not-null")]
|
||||||
[InlineData(null, "STRing", null, "somewhat-less-null")]
|
[InlineData(null, "STRing", null)]
|
||||||
[InlineData(null, "STRING", "not-null", null)]
|
[InlineData(null, "STRING", "not-null")]
|
||||||
[InlineData(null, "text", null, null)]
|
[InlineData(null, "text", null)]
|
||||||
[InlineData(null, "Text", "not-null", "somewhat-less-null")]
|
[InlineData(null, "Text", "not-null")]
|
||||||
[InlineData(null, "TExt", null, null)]
|
[InlineData(null, "TExt", null)]
|
||||||
[InlineData(null, "TEXT", "not-null", null)]
|
[InlineData(null, "TEXT", "not-null")]
|
||||||
[InlineData("string", null, null, null)]
|
[InlineData("string", null, null)]
|
||||||
[InlineData("String", null, "not-null", null)]
|
[InlineData("String", null, "not-null")]
|
||||||
[InlineData("STRing", null, null, null)]
|
[InlineData("STRing", null, null)]
|
||||||
[InlineData("STRING", null, "not-null", null)]
|
[InlineData("STRING", null, "not-null")]
|
||||||
[InlineData("text", null, null, null)]
|
[InlineData("text", null, null)]
|
||||||
[InlineData("Text", null, "not-null", null)]
|
[InlineData("Text", null, "not-null")]
|
||||||
[InlineData("TExt", null, null, null)]
|
[InlineData("TExt", null, null)]
|
||||||
[InlineData("TEXT", null, "not-null", null)]
|
[InlineData("TEXT", null, "not-null")]
|
||||||
[InlineData("custom-datatype", null, null, null)]
|
[InlineData("custom-datatype", null, null)]
|
||||||
[InlineData(null, "unknown-input-type", "not-null", null)]
|
[InlineData(null, "unknown-input-type", "not-null")]
|
||||||
[InlineData("Image", null, "not-null", "somewhat-less-null")]
|
[InlineData("Image", null, "not-null")]
|
||||||
[InlineData(null, "image", "not-null", null)]
|
[InlineData(null, "image", "not-null")]
|
||||||
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParameters(
|
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParameters(
|
||||||
string dataTypeName,
|
string dataTypeName,
|
||||||
string inputTypeName,
|
string inputTypeName,
|
||||||
string model,
|
string model)
|
||||||
string format)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contextAttributes = new TagHelperAttributeList
|
var contextAttributes = new TagHelperAttributeList
|
||||||
|
|
@ -773,7 +697,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
model,
|
model,
|
||||||
nameof(Model.Text),
|
nameof(Model.Text),
|
||||||
metadataProvider: metadataProvider);
|
metadataProvider: metadataProvider);
|
||||||
tagHelper.Format = format;
|
|
||||||
tagHelper.InputTypeName = inputTypeName;
|
tagHelper.InputTypeName = inputTypeName;
|
||||||
|
|
||||||
var tagBuilder = new TagBuilder("input")
|
var tagBuilder = new TagBuilder("input")
|
||||||
|
|
@ -788,9 +711,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
tagHelper.ViewContext,
|
tagHelper.ViewContext,
|
||||||
tagHelper.For.ModelExplorer,
|
tagHelper.For.ModelExplorer,
|
||||||
tagHelper.For.Name,
|
tagHelper.For.Name,
|
||||||
model, // value
|
model, // value
|
||||||
format,
|
null, // format
|
||||||
It.Is<Dictionary<string, object>>(m => m.ContainsKey("type")))) // htmlAttributes
|
It.Is<Dictionary<string, object>>(m => m.ContainsKey("type")))) // htmlAttributes
|
||||||
.Returns(tagBuilder)
|
.Returns(tagBuilder)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Microsoft.AspNetCore.Localization;
|
|
||||||
|
|
||||||
namespace FiltersWebSite
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the culture information for a request via values in the route data.
|
|
||||||
/// </summary>
|
|
||||||
public class RouteDataRequestCultureProvider : RequestCultureProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The key that contains the culture name.
|
|
||||||
/// Defaults to "culture".
|
|
||||||
/// </summary>
|
|
||||||
public string RouteDataStringKey { get; set; } = "culture";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key that contains the UI culture name. If not specified or no value is found,
|
|
||||||
/// <see cref="RouteDataStringKey"/> will be used.
|
|
||||||
/// Defaults to "ui-culture".
|
|
||||||
/// </summary>
|
|
||||||
public string UIRouteDataStringKey { get; set; } = "ui-culture";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<ProviderCultureResult> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue