refactor of action descriptor pipeline

(cherry picked from commit 634f756e7fd303fc3022563fcd8fb9b1cb47fba2)
This commit is contained in:
Ryan Nowak 2014-06-10 14:16:13 -07:00
parent 172dcd179b
commit e6ba1f23a2
23 changed files with 852 additions and 261 deletions

View File

@ -1,68 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public class ReflectedRouteConstraintsActionDescriptorProvider : IActionDescriptorProvider
{
public int Order
{
get { return ReflectedActionDescriptorProvider.DefaultOrder + 100; }
}
public void Invoke([NotNull]ActionDescriptorProviderContext context, Action callNext)
{
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Iterate all the Reflected Action Descriptor providers and add area or other route constraints
foreach (var actionDescriptor in context.Results.OfType<ReflectedActionDescriptor>())
{
var routeConstraints = actionDescriptor
.ControllerDescriptor
.ControllerTypeInfo
.GetCustomAttributes<RouteConstraintAttribute>()
.ToArray();
foreach (var routeConstraint in routeConstraints)
{
if (routeConstraint.BlockNonAttributedActions)
{
removalConstraints.Add(routeConstraint.RouteKey);
}
// Skip duplicates
if (!HasConstraint(actionDescriptor, routeConstraint.RouteKey))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
routeConstraint.RouteKey, routeConstraint.RouteValue));
}
}
}
foreach (var actionDescriptor in context.Results.OfType<ReflectedActionDescriptor>())
{
foreach (var key in removalConstraints)
{
if (!HasConstraint(actionDescriptor, key))
{
actionDescriptor.RouteConstraints.Add(
new RouteDataActionConstraint(key, RouteKeyHandling.DenyKey));
}
}
}
callNext();
}
private bool HasConstraint(ActionDescriptor actionDescript, string routeKey)
{
return actionDescript.RouteConstraints.Any(
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -1,15 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public class DefaultControllerDescriptorFactory : IControllerDescriptorFactory
{
public ControllerDescriptor CreateControllerDescriptor(TypeInfo typeInfo)
{
return new ControllerDescriptor(typeInfo);
}
}
}

View File

@ -1,37 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public class DefaultParameterDescriptorFactory : IParameterDescriptorFactory
{
public ParameterDescriptor GetDescriptor(ParameterInfo parameter)
{
var isFromBody = IsFromBody(parameter);
return new ParameterDescriptor
{
Name = parameter.Name,
IsOptional = parameter.IsOptional || parameter.HasDefaultValue,
ParameterBindingInfo = isFromBody ? null : GetParameterBindingInfo(parameter),
BodyParameterInfo = isFromBody ? GetBodyParameterInfo(parameter) : null
};
}
public virtual bool IsFromBody(ParameterInfo parameter)
{
return parameter.GetCustomAttribute<FromBodyAttribute>() != null;
}
private ParameterBindingInfo GetParameterBindingInfo(ParameterInfo parameter)
{
return new ParameterBindingInfo(parameter.Name, parameter.ParameterType);
}
private BodyParameterInfo GetBodyParameterInfo(ParameterInfo parameter)
{
return new BodyParameterInfo(parameter.ParameterType);
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public interface IControllerDescriptorFactory
{
ControllerDescriptor CreateControllerDescriptor(TypeInfo type);
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public interface IParameterDescriptorFactory
{
ParameterDescriptor GetDescriptor(ParameterInfo parameter);
}
}

View File

@ -27,6 +27,10 @@
<Compile Include="ActionDescriptor.cs" />
<Compile Include="ActionDescriptorProviderContext.cs" />
<Compile Include="ActionDescriptorsCollection.cs" />
<Compile Include="ReflectedModelBuilder\IReflectedApplicationModelConvention.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedActionModel.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedControllerModel.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedApplicationModel.cs" />
<Compile Include="ActionInvokerFactory.cs" />
<Compile Include="ActionInvokerProviderContext.cs" />
<Compile Include="ActionMethodSelectorAttribute.cs" />
@ -61,7 +65,6 @@
<Compile Include="AntiForgery\ITokenValidator.cs" />
<Compile Include="AntiForgery\TokenProvider.cs" />
<Compile Include="Areas\AreaAttribute.cs" />
<Compile Include="Areas\ReflectedRouteConstraintsActionDescriptorProvider.cs" />
<Compile Include="BodyParameterInfo.cs" />
<Compile Include="Controller.cs" />
<Compile Include="ControllerDescriptor.cs" />
@ -69,9 +72,8 @@
<Compile Include="DefaultActionDiscoveryConventions.cs" />
<Compile Include="DefaultActionSelector.cs" />
<Compile Include="DefaultControllerAssemblyProvider.cs" />
<Compile Include="DefaultControllerDescriptorFactory.cs" />
<Compile Include="DefaultControllerFactory.cs" />
<Compile Include="DefaultParameterDescriptorFactory.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedParameterModel.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Filters\FilterItemOrderComparer.cs" />
<Compile Include="Filters\TypeFilterAttribute.cs" />
@ -129,13 +131,11 @@
<Compile Include="IActionResult.cs" />
<Compile Include="IActionSelector.cs" />
<Compile Include="IControllerAssemblyProvider.cs" />
<Compile Include="IControllerDescriptorFactory.cs" />
<Compile Include="IControllerFactory.cs" />
<Compile Include="Injector.cs" />
<Compile Include="Internal\PropertyHelper.cs" />
<Compile Include="Internal\TypeHelper.cs" />
<Compile Include="Internal\UTF8EncodingWithoutBOM.cs" />
<Compile Include="IParameterDescriptorFactory.cs" />
<Compile Include="IUrlHelper.cs" />
<Compile Include="JsonOutputFormatter.cs" />
<Compile Include="MvcOptions.cs" />
@ -233,4 +233,4 @@
<Compile Include="ViewDataDictionaryOfT.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -2,7 +2,9 @@
// 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.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
namespace Microsoft.AspNet.Mvc
{
@ -10,6 +12,11 @@ namespace Microsoft.AspNet.Mvc
{
private AntiForgeryOptions _antiForgeryOptions = new AntiForgeryOptions();
public MvcOptions()
{
ApplicationModelConventions = new List<IReflectedApplicationModelConvention>();
}
public AntiForgeryOptions AntiForgeryOptions
{
get
@ -29,5 +36,7 @@ namespace Microsoft.AspNet.Mvc
_antiForgeryOptions = value;
}
}
public List<IReflectedApplicationModelConvention> ApplicationModelConventions { get; private set; }
}
}

View File

@ -4,7 +4,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if K10
using System.Reflection;
#endif
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
@ -14,23 +18,18 @@ namespace Microsoft.AspNet.Mvc
private readonly IControllerAssemblyProvider _controllerAssemblyProvider;
private readonly IActionDiscoveryConventions _conventions;
private readonly IControllerDescriptorFactory _controllerDescriptorFactory;
private readonly IParameterDescriptorFactory _parameterDescriptorFactory;
private readonly IEnumerable<FilterDescriptor> _globalFilters;
private readonly IEnumerable<IFilter> _globalFilters;
private readonly IEnumerable<IReflectedApplicationModelConvention> _modelConventions;
public ReflectedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider,
IActionDiscoveryConventions conventions,
IControllerDescriptorFactory controllerDescriptorFactory,
IParameterDescriptorFactory parameterDescriptorFactory,
IEnumerable<IFilter> globalFilters)
IEnumerable<IFilter> globalFilters,
IOptionsAccessor<MvcOptions> optionsAccessor)
{
_controllerAssemblyProvider = controllerAssemblyProvider;
_conventions = conventions;
_controllerDescriptorFactory = controllerDescriptorFactory;
_parameterDescriptorFactory = parameterDescriptorFactory;
var filters = globalFilters ?? Enumerable.Empty<IFilter>();
_globalFilters = filters.Select(f => new FilterDescriptor(f, FilterScope.Global));
_globalFilters = globalFilters ?? Enumerable.Empty<IFilter>();
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
}
public int Order
@ -44,34 +43,35 @@ namespace Microsoft.AspNet.Mvc
callNext();
}
public IEnumerable<ActionDescriptor> GetDescriptors()
public IEnumerable<ReflectedActionDescriptor> GetDescriptors()
{
var assemblies = _controllerAssemblyProvider.CandidateAssemblies;
var types = assemblies.SelectMany(a => a.DefinedTypes);
var controllers = types.Where(_conventions.IsController);
var controllerDescriptors = controllers
.Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t))
.ToArray();
var model = BuildModel();
return GetDescriptors(controllerDescriptors);
foreach (var convention in _modelConventions)
{
convention.OnModelCreated(model);
}
return Build(model);
}
// Internal for unit testing.
internal IEnumerable<ActionDescriptor> GetDescriptors(IEnumerable<ControllerDescriptor> controllerDescriptors)
public ReflectedApplicationModel BuildModel()
{
foreach (var cd in controllerDescriptors)
{
var controllerAttributes = cd.ControllerTypeInfo.GetCustomAttributes(inherit: true).ToArray();
var globalAndControllerFilters =
controllerAttributes.OfType<IFilter>()
.Select(filter => new FilterDescriptor(filter, FilterScope.Controller))
.Concat(_globalFilters)
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToArray();
var applicationModel = new ReflectedApplicationModel();
applicationModel.Filters.AddRange(_globalFilters);
foreach (var methodInfo in cd.ControllerTypeInfo.AsType().GetMethods())
var assemblies = _controllerAssemblyProvider.CandidateAssemblies;
var types = assemblies.SelectMany(a => a.DefinedTypes);
var controllerTypes = types.Where(_conventions.IsController);
foreach (var controllerType in controllerTypes)
{
var controllerModel = new ReflectedControllerModel(controllerType);
applicationModel.Controllers.Add(controllerModel);
foreach (var methodInfo in controllerType.AsType().GetMethods())
{
var actionInfos = _conventions.GetActions(methodInfo, cd.ControllerTypeInfo);
var actionInfos = _conventions.GetActions(methodInfo, controllerType);
if (actionInfos == null)
{
continue;
@ -79,62 +79,140 @@ namespace Microsoft.AspNet.Mvc
foreach (var actionInfo in actionInfos)
{
yield return BuildDescriptor(cd, methodInfo, actionInfo, globalAndControllerFilters);
var actionModel = new ReflectedActionModel(methodInfo);
actionModel.ActionName = actionInfo.ActionName;
actionModel.IsActionNameMatchRequired = actionInfo.RequireActionNameMatch;
actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty<string>());
foreach (var parameter in methodInfo.GetParameters())
{
actionModel.Parameters.Add(new ReflectedParameterModel(parameter));
}
controllerModel.Actions.Add(actionModel);
}
}
}
return applicationModel;
}
private ReflectedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor,
MethodInfo methodInfo,
ActionInfo actionInfo,
FilterDescriptor[] globalAndControllerFilters)
private bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)
{
var ad = new ReflectedActionDescriptor
return constraints.Any(
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
}
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
{
var actions = new List<ReflectedActionDescriptor>();
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var controller in model.Controllers)
{
RouteConstraints = new List<RouteDataActionConstraint>
var controllerDescriptor = new ControllerDescriptor(controller.ControllerType);
foreach (var action in controller.Actions)
{
new RouteDataActionConstraint("controller", controllerDescriptor.Name)
},
var parameterDescriptors = new List<ParameterDescriptor>();
foreach (var parameter in action.Parameters)
{
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
Name = actionInfo.ActionName,
ControllerDescriptor = controllerDescriptor,
MethodInfo = methodInfo,
};
parameterDescriptors.Add(new ParameterDescriptor()
{
Name = parameter.ParameterName,
IsOptional = parameter.IsOptional,
var httpMethods = actionInfo.HttpMethods;
if (httpMethods != null && httpMethods.Length > 0)
ParameterBindingInfo = isFromBody
? null
: new ParameterBindingInfo(
parameter.ParameterName,
parameter.ParameterInfo.ParameterType),
BodyParameterInfo = isFromBody
? new BodyParameterInfo(parameter.ParameterInfo.ParameterType)
: null
});
}
var actionDescriptor = new ReflectedActionDescriptor()
{
Name = action.ActionName,
ControllerDescriptor = controllerDescriptor,
MethodInfo = action.ActionMethod,
Parameters = new List<ParameterDescriptor>(),
RouteConstraints = new List<RouteDataActionConstraint>(),
};
var httpMethods = action.HttpMethods;
if (httpMethods != null && httpMethods.Count > 0)
{
actionDescriptor.MethodConstraints = new List<HttpMethodConstraint>()
{
new HttpMethodConstraint(httpMethods)
};
}
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"controller",
controller.ControllerName));
if (action.IsActionNameMatchRequired)
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"action",
action.ActionName));
}
else
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"action",
RouteKeyHandling.DenyKey));
}
foreach (var constraintAttribute in controller.RouteConstraints)
{
if (constraintAttribute.BlockNonAttributedActions)
{
removalConstraints.Add(constraintAttribute.RouteKey);
}
// Skip duplicates
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
constraintAttribute.RouteKey,
constraintAttribute.RouteValue));
}
}
actionDescriptor.FilterDescriptors =
action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action))
.Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
.Concat(model.Filters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToList();
actions.Add(actionDescriptor);
}
}
foreach (var actionDescriptor in actions)
{
ad.MethodConstraints = new List<HttpMethodConstraint>
foreach (var key in removalConstraints)
{
new HttpMethodConstraint(httpMethods)
};
if (!HasConstraint(actionDescriptor.RouteConstraints, key))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
key,
RouteKeyHandling.DenyKey));
}
}
}
if (actionInfo.RequireActionNameMatch)
{
ad.RouteConstraints.Add(new RouteDataActionConstraint("action", actionInfo.ActionName));
}
else
{
ad.RouteConstraints.Add(new RouteDataActionConstraint("action", RouteKeyHandling.DenyKey));
}
ad.Parameters = methodInfo.GetParameters()
.Select(p => _parameterDescriptorFactory.GetDescriptor(p))
.ToList();
var attributes = methodInfo.GetCustomAttributes(inherit: true).ToArray();
var filtersFromAction = attributes
.OfType<IFilter>()
.Select(filter => new FilterDescriptor(filter, FilterScope.Action));
ad.FilterDescriptors = filtersFromAction.Concat(globalAndControllerFilters)
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToList();
return ad;
return actions;
}
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
public interface IReflectedApplicationModelConvention
{
void OnModelCreated([NotNull] ReflectedApplicationModel model);
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
public class ReflectedActionModel
{
public ReflectedActionModel([NotNull] MethodInfo actionMethod)
{
ActionMethod = actionMethod;
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToList() is List<object>
Attributes = actionMethod.GetCustomAttributes(inherit: true).OfType<object>().ToList();
Filters = Attributes.OfType<IFilter>().ToList();
HttpMethods = new List<string>();
Parameters = new List<ReflectedParameterModel>();
}
public MethodInfo ActionMethod { get; private set; }
public string ActionName { get; set; }
public List<object> Attributes { get; private set; }
public List<IFilter> Filters { get; private set; }
public List<string> HttpMethods { get; private set; }
public bool IsActionNameMatchRequired { get; set; }
public List<ReflectedParameterModel> Parameters { get; private set; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
public class ReflectedApplicationModel
{
public ReflectedApplicationModel()
{
Controllers = new List<ReflectedControllerModel>();
Filters = new List<IFilter>();
}
public List<ReflectedControllerModel> Controllers { get; private set; }
public List<IFilter> Filters { get; private set; }
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
public class ReflectedControllerModel
{
public ReflectedControllerModel([NotNull] TypeInfo controllerType)
{
ControllerType = controllerType;
Actions = new List<ReflectedActionModel>();
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToList() is List<object>
Attributes = ControllerType.GetCustomAttributes(inherit: true).OfType<object>().ToList();
Filters = Attributes.OfType<IFilter>().ToList();
RouteConstraints = Attributes.OfType<RouteConstraintAttribute>().ToList();
ControllerName = controllerType.Name.EndsWith("Controller", StringComparison.Ordinal)
? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
: controllerType.Name;
}
public List<ReflectedActionModel> Actions { get; private set; }
public List<object> Attributes { get; private set; }
public string ControllerName { get; set; }
public TypeInfo ControllerType { get; private set; }
public List<IFilter> Filters { get; private set; }
public List<RouteConstraintAttribute> RouteConstraints { get; private set; }
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
public class ReflectedParameterModel
{
public ReflectedParameterModel(ParameterInfo parameterInfo)
{
ParameterInfo = parameterInfo;
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToList() is List<object>
Attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToList();
ParameterName = parameterInfo.Name;
IsOptional = ParameterInfo.HasDefaultValue;
}
public List<object> Attributes { get; private set; }
public bool IsOptional { get; set; }
public ParameterInfo ParameterInfo { get; private set; }
public string ParameterName { get; set; }
}
}

View File

@ -26,10 +26,8 @@ namespace Microsoft.AspNet.Mvc
var describe = new ServiceDescriber(configuration);
yield return describe.Transient<IControllerFactory, DefaultControllerFactory>();
yield return describe.Transient<IControllerDescriptorFactory, DefaultControllerDescriptorFactory>();
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
yield return describe.Transient<IActionInvokerFactory, ActionInvokerFactory>();
yield return describe.Transient<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>();
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
@ -43,8 +41,6 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ReflectedActionDescriptorProvider>();
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ReflectedRouteConstraintsActionDescriptorProvider>();
yield return describe.Transient<INestedProvider<ActionInvokerProviderContext>,
ReflectedActionInvokerProvider>();
yield return describe.Singleton<IActionDescriptorsCollectionProvider,

View File

@ -10,19 +10,17 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.NestedProviders;
using Moq;
using Xunit;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.Core.Test
namespace Microsoft.AspNet.Mvc.Test
{
public class ActionAttributeTests
{
private DefaultActionDiscoveryConventions _actionDiscoveryConventions = new DefaultActionDiscoveryConventions();
private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory();
private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory();
private IEnumerable<Assembly> _controllerAssemblies = new[] { Assembly.GetExecutingAssembly() };
[Theory]
@ -217,12 +215,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
var controllerAssemblyProvider = new Mock<IControllerAssemblyProvider>();
controllerAssemblyProvider.SetupGet(x => x.CandidateAssemblies).Returns(_controllerAssemblies);
return new ReflectedActionDescriptorProvider(
controllerAssemblyProvider.Object,
actionDiscoveryConventions,
_controllerDescriptorFactory,
_parameterDescriptorFactory,
null);
null,
new MockMvcOptionsAccessor());
}
private static HttpContext GetHttpContext(string httpMethod)

View File

@ -15,13 +15,11 @@ using Microsoft.Framework.DependencyInjection.NestedProviders;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test
namespace Microsoft.AspNet.Mvc.Test
{
public class ActionSelectionConventionTests
{
private DefaultActionDiscoveryConventions _actionDiscoveryConventions = new DefaultActionDiscoveryConventions();
private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory();
private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory();
private IEnumerable<Assembly> _controllerAssemblies = new[] { Assembly.GetExecutingAssembly() };
[Theory]
@ -188,9 +186,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
return new ReflectedActionDescriptorProvider(
controllerAssemblyProvider.Object,
actionDiscoveryConventions,
_controllerDescriptorFactory,
_parameterDescriptorFactory,
null);
null,
new MockMvcOptionsAccessor());
}
private static HttpContext GetHttpContext(string httpMethod)

View File

@ -30,6 +30,10 @@
<Compile Include="ActionResults\RedirectResultTest.cs" />
<Compile Include="ActionSelectionConventionTests.cs" />
<Compile Include="AntiXsrf\AntiForgeryOptionsTests.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedParameterModelTests.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedActionModelTests.cs" />
<Compile Include="ReflectedModelBuilder\ReflectedControllerModelTests.cs" />
<Compile Include="MockMvcOptionsAccessor.cs" />
<Compile Include="MvcOptionsTests.cs" />
<Compile Include="AntiXsrf\AntiForgeryTokenSerializerTest.cs" />
<Compile Include="AntiXsrf\ITokenProvider.cs" />
@ -60,6 +64,7 @@
<Compile Include="KnownRouteValueConstraintTests.cs" />
<Compile Include="TestController.cs" />
<Compile Include="TypeHelperTest.cs" />
<Compile Include="StaticActionDiscoveryConventions.cs" />
<Compile Include="UrlHelperTest.cs" />
<Compile Include="ViewResultTest.cs" />
<Compile Include="ViewComponentTests.cs" />

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Test
{
public class MockMvcOptionsAccessor : IOptionsAccessor<MvcOptions>
{
public MockMvcOptionsAccessor()
{
Options = new MvcOptions();
}
public MvcOptions Options { get; private set; }
}
}

View File

@ -1,21 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
namespace Microsoft.AspNet.Mvc.Test
{
public class ReflectedActionDescriptorProviderTests
{
private DefaultActionDiscoveryConventions _actionDiscoveryConventions =
new DefaultActionDiscoveryConventions();
private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory();
private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory();
[Fact]
public void GetDescriptors_GetsDescriptorsOnlyForValidActionsInBaseAndDerivedController()
{
@ -130,25 +126,209 @@ namespace Microsoft.AspNet.Mvc
Assert.DoesNotContain("PrivateStaticMethod", actionNames);
}
private IEnumerable<string> GetActionNamesFromDerivedController()
[Fact]
public void GetDescriptors_IncludesFilters()
{
return GetDescriptors(typeof(DerivedController).GetTypeInfo()).Select(a => a.Name);
// Arrange
var globalFilter = new MyFilterAttribute(1);
var provider = GetProvider(typeof(FiltersController).GetTypeInfo(), new IFilter[]
{
globalFilter,
});
// Act
var descriptors = provider.GetDescriptors();
var descriptor = Assert.Single(descriptors);
// Assert
Assert.Equal(3, descriptor.FilterDescriptors.Count);
var filter1 = descriptor.FilterDescriptors[2];
Assert.Same(globalFilter, filter1.Filter);
Assert.Equal(FilterScope.Global, filter1.Scope);
var filter2 = descriptor.FilterDescriptors[1];
Assert.Equal(2, Assert.IsType<MyFilterAttribute>(filter2.Filter).Value);
Assert.Equal(FilterScope.Controller, filter2.Scope);
var filter3 = descriptor.FilterDescriptors[0];
Assert.Equal(3, Assert.IsType<MyFilterAttribute>(filter3.Filter).Value); ;
Assert.Equal(FilterScope.Action, filter3.Scope);
}
private IEnumerable<ActionDescriptor> GetDescriptors(TypeInfo controllerTypeInfo)
[Fact]
public void GetDescriptors_AddsHttpMethodConstraints()
{
var provider = new ReflectedActionDescriptorProvider(null,
_actionDiscoveryConventions,
_controllerDescriptorFactory,
_parameterDescriptorFactory,
null);
var testControllers = new TypeInfo[]
// Arrange
var provider = GetProvider(typeof(HttpMethodController).GetTypeInfo());
// Act
var descriptors = provider.GetDescriptors();
var descriptor = Assert.Single(descriptors);
// Assert
Assert.Equal("OnlyPost", descriptor.Name);
Assert.Single(descriptor.MethodConstraints);
Assert.Equal(new string[] { "POST" }, descriptor.MethodConstraints[0].HttpMethods);
}
[Fact]
public void GetDescriptors_WithRouteDataConstraint_WithBlockNonAttributedActions()
{
// Arrange & Act
var descriptors = GetDescriptors(
typeof(HttpMethodController).GetTypeInfo(),
typeof(BlockNonAttributedActionsController).GetTypeInfo()).ToArray();
var descriptorWithoutConstraint = Assert.Single(
descriptors,
ad => ad.RouteConstraints.Any(
c => c.RouteKey == "key" && c.KeyHandling == RouteKeyHandling.DenyKey));
var descriptorWithConstraint = Assert.Single(
descriptors,
ad => ad.RouteConstraints.Any(
c =>
c.KeyHandling == RouteKeyHandling.RequireKey &&
c.RouteKey == "key" &&
c.RouteValue == "value"));
// Assert
Assert.Equal(2, descriptors.Length);
Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "BlockNonAttributedActions");
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "Edit");
Assert.Equal(3, descriptorWithoutConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "HttpMethod");
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "OnlyPost");
}
[Fact]
public void GetDescriptors_WithRouteDataConstraint_WithoutBlockNonAttributedActions()
{
// Arrange & Act
var descriptors = GetDescriptors(
typeof(HttpMethodController).GetTypeInfo(),
typeof(DontBlockNonAttributedActionsController).GetTypeInfo()).ToArray();
var descriptorWithConstraint = Assert.Single(
descriptors,
ad => ad.RouteConstraints.Any(
c =>
c.KeyHandling == RouteKeyHandling.RequireKey &&
c.RouteKey == "key" &&
c.RouteValue == "value"));
var descriptorWithoutConstraint = Assert.Single(
descriptors,
ad => !ad.RouteConstraints.Any(c => c.RouteKey == "key"));
// Assert
Assert.Equal(2, descriptors.Length);
Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "DontBlockNonAttributedActions");
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "Create");
Assert.Equal(2, descriptorWithoutConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "HttpMethod");
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "OnlyPost");
}
[Fact]
public void BuildModel_IncludesGlobalFilters()
{
// Arrange
var filter = new MyFilterAttribute(1);
var provider = GetProvider(typeof(BaseController).GetTypeInfo(), new IFilter[]
{
controllerTypeInfo,
};
var controllerDescriptors = testControllers
.Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t));
return provider.GetDescriptors(controllerDescriptors);
filter,
});
// Act
var model = provider.BuildModel();
// Assert
var filters = model.Filters;
Assert.Same(filter, Assert.Single(filters));
}
private IEnumerable<string> GetActionNamesFromDerivedController()
{
return GetDescriptors(typeof(DerivedController).GetTypeInfo()).Select(a => a.Name).ToArray();
}
private ReflectedActionDescriptorProvider GetProvider(
TypeInfo controllerTypeInfo,
IEnumerable<IFilter> filters = null)
{
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
assemblyProvider
.SetupGet(ap => ap.CandidateAssemblies)
.Returns(new Assembly[] { controllerTypeInfo.Assembly });
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfo);
var provider = new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
filters,
new MockMvcOptionsAccessor());
return provider;
}
private IEnumerable<ActionDescriptor> GetDescriptors(params TypeInfo[] controllerTypeInfos)
{
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
assemblyProvider
.SetupGet(ap => ap.CandidateAssemblies)
.Returns(controllerTypeInfos.Select(cti => cti.Assembly).Distinct());
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos);
var provider = new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
null,
new MockMvcOptionsAccessor());
return provider.GetDescriptors();
}
private class DerivedController : BaseController
@ -218,5 +398,56 @@ namespace Microsoft.AspNet.Mvc
return base.Redirect(url + "#RedirectOverride");
}
}
[MyFilter(2)]
private class FiltersController
{
[MyFilter(3)]
public void FilterAction()
{
}
}
private class MyFilterAttribute : Attribute, IFilter
{
public MyFilterAttribute(int value)
{
Value = value;
}
public int Value { get; private set; }
}
private class HttpMethodController
{
[HttpPost]
public void OnlyPost()
{
}
}
[MyRouteConstraintAttribute(blockNonAttributedActions: true)]
private class BlockNonAttributedActionsController
{
public void Edit()
{
}
}
[MyRouteConstraintAttribute(blockNonAttributedActions: false)]
private class DontBlockNonAttributedActionsController
{
public void Create()
{
}
}
private class MyRouteConstraintAttribute : RouteConstraintAttribute
{
public MyRouteConstraintAttribute(bool blockNonAttributedActions)
: base("key", "value", blockNonAttributedActions)
{
}
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
{
public class ReflectedActionModelTests
{
[Fact]
public void ReflectedActionModel_PopulatesAttributes()
{
// Arrange
var actionMethod = typeof(BlogController).GetMethod("Edit");
// Act
var model = new ReflectedActionModel(actionMethod);
// Assert
Assert.Equal(2, model.Attributes.Count);
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
}
[Fact]
public void ReflectedActionModel_PopulatesFilters()
{
// Arrange
var actionMethod = typeof(BlogController).GetMethod("Edit");
// Act
var model = new ReflectedActionModel(actionMethod);
// Assert
Assert.Single(model.Filters);
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
}
private class BlogController
{
[MyOther]
[MyFilter]
public void Edit()
{
}
}
private class MyFilterAttribute : Attribute, IFilter
{
}
private class MyOtherAttribute : Attribute
{
}
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using Xunit;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
{
public class ReflectedControllerModelTests
{
[Fact]
public void ReflectedControllerModel_PopulatesAttributes()
{
// Arrange
var controllerType = typeof(BlogController);
// Act
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
// Assert
Assert.Equal(3, model.Attributes.Count);
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
Assert.Single(model.Attributes, a => a is MyRouteConstraintAttribute);
}
[Fact]
public void ReflectedControllerModel_PopulatesFilters()
{
// Arrange
var controllerType = typeof(BlogController);
// Act
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
// Assert
Assert.Single(model.Filters);
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
}
[Fact]
public void ReflectedControllerModel_PopulatesRouteConstraintAttributes()
{
// Arrange
var controllerType = typeof(BlogController);
// Act
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
// Assert
Assert.Single(model.RouteConstraints);
Assert.IsType<MyRouteConstraintAttribute>(model.RouteConstraints[0]);
}
[Fact]
public void ReflectedControllerModel_ComputesControllerName()
{
// Arrange
var controllerType = typeof(BlogController);
// Act
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
// Assert
Assert.Equal("Blog", model.ControllerName);
}
[Fact]
public void ReflectedControllerModel_ComputesControllerName_WithoutSuffix()
{
// Arrange
var controllerType = typeof(Store);
// Act
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
// Assert
Assert.Equal("Store", model.ControllerName);
}
[MyOther]
[MyFilter]
[MyRouteConstraint]
private class BlogController
{
}
private class Store
{
}
private class MyRouteConstraintAttribute : RouteConstraintAttribute
{
public MyRouteConstraintAttribute()
: base("MyRouteConstraint", "MyRouteConstraint", false)
{
}
}
private class MyFilterAttribute : Attribute, IFilter
{
}
private class MyOtherAttribute : Attribute
{
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
{
public class ReflectedParameterModelTests
{
[Fact]
public void ReflectedParameterModel_PopulatesAttributes()
{
// Arrange
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[0];
// Act
var model = new ReflectedParameterModel(parameterInfo);
// Assert
Assert.Equal(1, model.Attributes.Count);
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
}
[Fact]
public void ReflectedParameterModel_PopulatesParameterName()
{
// Arrange
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[0];
// Act
var model = new ReflectedParameterModel(parameterInfo);
// Assert
Assert.Equal("name", model.ParameterName);
}
[Theory]
[InlineData(0, false)]
[InlineData(1, true)]
public void ReflectedParameterModel_PopulatesIsOptional(int parameterIndex, bool expected)
{
// Arrange
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[parameterIndex];
// Act
var model = new ReflectedParameterModel(parameterInfo);
// Assert
Assert.Equal(expected, model.IsOptional);
}
private class BlogController
{
public void Edit([MyOther] string name, int age = 17)
{
}
}
private class MyOtherAttribute : Attribute
{
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.Test
{
/// <summary>
/// An implementation of DefaultActionDiscoveryConventions that only allows controllers
/// from a fixed set of types.
/// </summary>
public class StaticActionDiscoveryConventions : DefaultActionDiscoveryConventions
{
public StaticActionDiscoveryConventions(params TypeInfo[] controllerTypes)
{
ControllerTypes = new List<TypeInfo>(controllerTypes ?? Enumerable.Empty<TypeInfo>());
}
public List<TypeInfo> ControllerTypes { get; private set; }
public override bool IsController([NotNull]TypeInfo typeInfo)
{
return ControllerTypes.Contains(typeInfo);
}
}
}