Reverting accidental changes

This commit is contained in:
Ajay Bhargav Baaskaran 2016-09-08 08:27:17 -07:00
parent 7a98ff1a64
commit 4677bf13fa
26 changed files with 2176 additions and 57 deletions

View File

@ -85,6 +85,9 @@ namespace Microsoft.AspNetCore.Builder
"ConfigureServices(...)"));
}
var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>();
middlewarePipelineBuilder.ApplicationBuilder = app.New();
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),

View File

@ -222,6 +222,13 @@ namespace Microsoft.Extensions.DependencyInjection
//
services.TryAddSingleton<MvcRouteHandler>(); // Only one 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)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,88 @@
// 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();
}
}
}

View File

@ -0,0 +1,117 @@
// 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);
}
}
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class MiddlewareFilterFeature : IMiddlewareFilterFeature
{
public ResourceExecutingContext ResourceExecutingContext { get; set; }
public ResourceExecutionDelegate ResourceExecutionDelegate { get; set; }
}
}

View File

@ -1210,6 +1210,102 @@ namespace Microsoft.AspNetCore.Mvc.Core
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)
{
var value = _resourceManager.GetString(name);

View File

@ -352,4 +352,22 @@
<data name="OutputFormattersAreRequired" xml:space="preserve">
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to format a response.</value>
</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>

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor.Host
{
/// <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);
}
}

View File

@ -0,0 +1,118 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Razor.Host;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor
{
/// <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)}";
}
}

View File

@ -98,8 +98,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// attribute to that formatted <see cref="string"/>.
/// </summary>
/// <remarks>
/// Used only the calculated "type" attribute is "text" (the most common value) e.g.
/// <see cref="InputTypeName"/> is "String". That is, <see cref="Format"/> is used when calling
/// Not used if the provided (see <see cref="InputTypeName"/>) or calculated "type" attribute value is
/// <c>checkbox</c>, <c>password</c>, or <c>radio</c>. That is, <see cref="Format"/> is used when calling
/// <see cref="IHtmlGenerator.GenerateTextBox"/>.
/// </remarks>
[HtmlAttributeName(FormatAttributeName)]
@ -110,8 +110,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// </summary>
/// <remarks>
/// 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 (when calling
/// <see cref="IHtmlGenerator.GenerateTextBox"/>).
/// helper to call and the default <see cref="Format"/> value. A default <see cref="Format"/> is not calculated
/// if the provided (see <see cref="InputTypeName"/>) or calculated "type" attribute value is <c>checkbox</c>,
/// <c>hidden</c>, <c>password</c>, or <c>radio</c>.
/// </remarks>
[HtmlAttributeName("type")]
public string InputTypeName { get; set; }
@ -189,20 +190,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
TagBuilder tagBuilder;
switch (inputType)
{
case "hidden":
tagBuilder = GenerateHidden(modelExplorer);
break;
case "checkbox":
GenerateCheckBox(modelExplorer, output);
return;
case "hidden":
tagBuilder = Generator.GenerateHidden(
ViewContext,
modelExplorer,
For.Name,
value: For.Model,
useViewData: false,
htmlAttributes: null);
break;
case "password":
tagBuilder = Generator.GeneratePassword(
ViewContext,
@ -364,6 +359,34 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
htmlAttributes: htmlAttributes);
}
// Imitate Generator.GenerateHidden() using Generator.GenerateTextBox(). This adds support for asp-format that
// is not available in Generator.GenerateHidden().
private TagBuilder GenerateHidden(ModelExplorer modelExplorer)
{
var value = For.Model;
var byteArrayValue = value as byte[];
if (byteArrayValue != null)
{
value = Convert.ToBase64String(byteArrayValue);
}
// In DefaultHtmlGenerator(), GenerateTextBox() calls GenerateInput() _almost_ identically to how
// GenerateHidden() does and the main switch inside GenerateInput() handles InputType.Text and
// InputType.Hidden identically. No behavior differences at all when a type HTML attribute already exists.
var htmlAttributes = new Dictionary<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.
private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
{

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Moq;
using Xunit;

View File

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

View File

@ -0,0 +1,157 @@
// 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);
}
}
}
}

View File

@ -0,0 +1,178 @@
// 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)
{
}
}
}
}

View File

@ -0,0 +1,516 @@
// 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");
}
}
}
}

View File

@ -536,5 +536,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
Assert.Equal("Data:10", await response.Content.ReadAsStringAsync());
}
[Theory]
[InlineData("en-US", "en-US")]
[InlineData("fr", "fr")]
[InlineData("ab-cd", "en-US")]
public async Task MiddlewareFilter_LocalizationMiddlewareRegistration_UsesRouteDataToFindCulture(
string culture,
string expected)
{
// Arrange & Act
var response = await Client.GetAsync($"http://localhost/{culture}/MiddlewareFilterTest/CultureFromRouteData");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(
$"CurrentCulture:{expected},CurrentUICulture:{expected}",
await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test
{
public class ViewComponentTagHelperDescriptorConventionsTest
{
[Fact]
public void IsViewComponentDescriptor_ReturnsFalseForInvalidDescriptor()
{
//Arrange
var tagHelperDescriptor = CreateTagHelperDescriptor();
// Act
var isViewComponentDescriptor = ViewComponentTagHelperDescriptorConventions
.IsViewComponentDescriptor(tagHelperDescriptor);
// Assert
Assert.False(isViewComponentDescriptor);
}
[Fact]
public void IsViewComponentDescriptor_ReturnsTrueForValidDescriptor()
{
// Arrange
var descriptor = CreateViewComponentTagHelperDescriptor();
// Act
var isViewComponentDescriptor = ViewComponentTagHelperDescriptorConventions
.IsViewComponentDescriptor(descriptor);
// Assert
Assert.True(isViewComponentDescriptor);
}
private static TagHelperDescriptor CreateTagHelperDescriptor()
{
var descriptor = new TagHelperDescriptor
{
TagName = "tag-name",
TypeName = "TypeName",
AssemblyName = "AssemblyName",
};
return descriptor;
}
private static TagHelperDescriptor CreateViewComponentTagHelperDescriptor()
{
var descriptor = CreateTagHelperDescriptor();
descriptor.PropertyBag.Add(
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey,
"ViewComponentName");
return descriptor;
}
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Razor.Host;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Test.ViewComponentTagHelpers
{
public class ViewComponentTagHelperDescriptorFactoryTest
{
public static TheoryData AssemblyData
{
get
{
var provider = new TestViewComponentDescriptorProvider();
var assemblyOne = "Microsoft.AspNetCore.Mvc.Razor";
var assemblyTwo = "Microsoft.AspNetCore.Mvc.Razor.Test";
var assemblyNone = string.Empty;
return new TheoryData<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
};
}
}
}
}

View File

@ -246,6 +246,75 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData(null, "datetime")]
[InlineData("hidden", "hidden")]
public void Process_GeneratesFormattedOutput(string specifiedType, string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", "DateTimeOffset" },
{ "name", "DateTimeOffset" },
{ "valid", "from validation attributes" },
{ "value", "datetime: 2011-08-31T05:30:45.0000000+03:00" },
};
var expectedTagName = "not-input";
var container = new Model
{
DateTimeOffset = new DateTimeOffset(2011, 8, 31, hour: 5, minute: 30, second: 45, offset: TimeSpan.FromHours(3))
};
var allAttributes = new TagHelperAttributeList
{
{ "type", specifiedType },
};
var context = new TagHelperContext(
allAttributes: allAttributes,
items: new Dictionary<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]
public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters()
{
@ -283,6 +352,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(htmlGenerator.Object, model: false, propertyName: nameof(Model.IsACar));
tagHelper.Format = "somewhat-less-null"; // ignored
var tagBuilder = new TagBuilder("input")
{
Attributes =
@ -323,18 +394,19 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Theory]
[InlineData(null, "hidden", null)]
[InlineData(null, "Hidden", "not-null")]
[InlineData(null, "HIDden", null)]
[InlineData(null, "HIDDEN", "not-null")]
[InlineData("hiddeninput", null, null)]
[InlineData("HiddenInput", null, "not-null")]
[InlineData("hidDENinPUT", null, null)]
[InlineData("HIDDENINPUT", null, "not-null")]
public async Task ProcessAsync_CallsGenerateHidden_WithExpectedParameters(
[InlineData(null, "hidden", null, null)]
[InlineData(null, "Hidden", "not-null", "somewhat-less-null")]
[InlineData(null, "HIDden", null, "somewhat-less-null")]
[InlineData(null, "HIDDEN", "not-null", null)]
[InlineData("hiddeninput", null, null, null)]
[InlineData("HiddenInput", null, "not-null", null)]
[InlineData("hidDENinPUT", null, null, "somewhat-less-null")]
[InlineData("HIDDENINPUT", null, "not-null", "somewhat-less-null")]
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParametersForHidden(
string dataTypeName,
string inputTypeName,
string model)
string model,
string format)
{
// Arrange
var contextAttributes = new TagHelperAttributeList
@ -389,6 +461,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = format;
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
@ -399,13 +472,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
},
};
htmlGenerator
.Setup(mock => mock.GenerateHidden(
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
model, // value
false, // useViewData
null)) // htmlAttributes
model, // value
format,
new Dictionary<string, object> { { "type", "hidden" } })) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
@ -490,6 +563,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = "somewhat-less-null"; // ignored
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
@ -581,6 +655,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text));
tagHelper.Format = "somewhat-less-null"; // ignored
tagHelper.InputTypeName = inputTypeName;
tagHelper.Value = value;
@ -617,32 +692,33 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Theory]
[InlineData(null, null, null)]
[InlineData(null, null, "not-null")]
[InlineData(null, "string", null)]
[InlineData(null, "String", "not-null")]
[InlineData(null, "STRing", null)]
[InlineData(null, "STRING", "not-null")]
[InlineData(null, "text", null)]
[InlineData(null, "Text", "not-null")]
[InlineData(null, "TExt", null)]
[InlineData(null, "TEXT", "not-null")]
[InlineData("string", null, null)]
[InlineData("String", null, "not-null")]
[InlineData("STRing", null, null)]
[InlineData("STRING", null, "not-null")]
[InlineData("text", null, null)]
[InlineData("Text", null, "not-null")]
[InlineData("TExt", null, null)]
[InlineData("TEXT", null, "not-null")]
[InlineData("custom-datatype", null, null)]
[InlineData(null, "unknown-input-type", "not-null")]
[InlineData("Image", null, "not-null")]
[InlineData(null, "image", "not-null")]
[InlineData(null, null, null, "somewhat-less-null")]
[InlineData(null, null, "not-null", null)]
[InlineData(null, "string", null, null)]
[InlineData(null, "String", "not-null", null)]
[InlineData(null, "STRing", null, "somewhat-less-null")]
[InlineData(null, "STRING", "not-null", null)]
[InlineData(null, "text", null, null)]
[InlineData(null, "Text", "not-null", "somewhat-less-null")]
[InlineData(null, "TExt", null, null)]
[InlineData(null, "TEXT", "not-null", null)]
[InlineData("string", null, null, null)]
[InlineData("String", null, "not-null", null)]
[InlineData("STRing", null, null, null)]
[InlineData("STRING", null, "not-null", null)]
[InlineData("text", null, null, null)]
[InlineData("Text", null, "not-null", null)]
[InlineData("TExt", null, null, null)]
[InlineData("TEXT", null, "not-null", null)]
[InlineData("custom-datatype", null, null, null)]
[InlineData(null, "unknown-input-type", "not-null", null)]
[InlineData("Image", null, "not-null", "somewhat-less-null")]
[InlineData(null, "image", "not-null", null)]
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParameters(
string dataTypeName,
string inputTypeName,
string model)
string model,
string format)
{
// Arrange
var contextAttributes = new TagHelperAttributeList
@ -697,6 +773,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = format;
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
@ -711,9 +788,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
model, // value
null, // format
It.Is<Dictionary<string, object>>(m => m.ContainsKey("type")))) // htmlAttributes
model, // value
format,
It.Is<Dictionary<string, object>>(m => m.ContainsKey("type")))) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();

View File

@ -0,0 +1,25 @@
// 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}");
}
}
}

View File

@ -0,0 +1,95 @@
// 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);
}
}
}
}

View File

@ -0,0 +1,32 @@
// 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);
}
}
}

View File

@ -0,0 +1,72 @@
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);
}
}
}