Part 2 of fix for #2248 - Support 'override' of attribute routes on action
methods This change applies the 'override' semantic to attribute routes on virtual methods. A method override can either inherit attribute routes from a base definition, or can replace them. It's not possible to inherit routes and also add to them.
This commit is contained in:
parent
46db71cfce
commit
8a476b56d8
|
|
@ -37,10 +37,60 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
||||||
return Enumerable.Empty<ActionModel>();
|
return Enumerable.Empty<ActionModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For attribute routes on a action, we want want to support 'overriding' routes on a
|
||||||
|
// virtual method, but allow 'overriding'. So we need to walk up the hierarchy looking
|
||||||
|
// for the first definition to define routes.
|
||||||
|
//
|
||||||
|
// Then we want to 'filter' the set of attributes, so that only the effective routes apply.
|
||||||
|
var currentMethodInfo = methodInfo;
|
||||||
|
|
||||||
|
IRouteTemplateProvider[] routeAttributes = null;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
routeAttributes = currentMethodInfo
|
||||||
|
.GetCustomAttributes(inherit: false)
|
||||||
|
.OfType<IRouteTemplateProvider>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (routeAttributes.Length > 0)
|
||||||
|
{
|
||||||
|
// Found 1 or more route attributes.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBaseDefinition returns 'this' when it gets to the bottom of the chain.
|
||||||
|
var nextMethodInfo = currentMethodInfo.GetBaseDefinition();
|
||||||
|
if (currentMethodInfo == nextMethodInfo)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMethodInfo = nextMethodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||||
// is needed to so that the result of ToArray() is object
|
// is needed to so that the result of ToArray() is object
|
||||||
var attributes = methodInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
|
var attributes = methodInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
|
||||||
|
|
||||||
|
// This is fairly complicated so that we maintain referential equality between items in
|
||||||
|
// ActionModel.Attributes and ActionModel.Attributes[*].Attribute.
|
||||||
|
var applicableAttributes = new List<object>();
|
||||||
|
foreach (var attribute in attributes)
|
||||||
|
{
|
||||||
|
if (attribute is IRouteTemplateProvider)
|
||||||
|
{
|
||||||
|
// This attribute is a route-attribute, leave it out.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicableAttributes.Add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applicableAttributes.AddRange(routeAttributes);
|
||||||
|
|
||||||
|
attributes = applicableAttributes.ToArray();
|
||||||
|
|
||||||
// Route attributes create multiple actions, we want to split the set of
|
// Route attributes create multiple actions, we want to split the set of
|
||||||
// attributes based on these so each action only has the attributes that affect it.
|
// attributes based on these so each action only has the attributes that affect it.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,52 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
||||||
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
|
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetActions_InheritedAttributeRoutes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateTestDefaultActionModelBuilder();
|
||||||
|
var typeInfo = typeof(DerivedClassInheritsAttributeRoutesController).GetTypeInfo();
|
||||||
|
var actionName = nameof(DerivedClassInheritsAttributeRoutesController.Edit);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = builder.BuildActionModels(typeInfo, typeInfo.GetMethod(actionName));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, actions.Count());
|
||||||
|
|
||||||
|
var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "A");
|
||||||
|
Assert.Equal(1, action.Attributes.Count);
|
||||||
|
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
|
||||||
|
|
||||||
|
action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "B");
|
||||||
|
Assert.Equal(1, action.Attributes.Count);
|
||||||
|
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetActions_InheritedAttributeRoutesOverridden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateTestDefaultActionModelBuilder();
|
||||||
|
var typeInfo = typeof(DerivedClassOverridesAttributeRoutesController).GetTypeInfo();
|
||||||
|
var actionName = nameof(DerivedClassOverridesAttributeRoutesController.Edit);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = builder.BuildActionModels(typeInfo, typeInfo.GetMethod(actionName));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, actions.Count());
|
||||||
|
|
||||||
|
var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "C");
|
||||||
|
Assert.Equal(1, action.Attributes.Count);
|
||||||
|
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
|
||||||
|
|
||||||
|
action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "D");
|
||||||
|
Assert.Equal(1, action.Attributes.Count);
|
||||||
|
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
|
||||||
|
}
|
||||||
|
|
||||||
private static DefaultActionModelBuilder CreateTestDefaultActionModelBuilder(
|
private static DefaultActionModelBuilder CreateTestDefaultActionModelBuilder(
|
||||||
AuthorizationOptions authOptions = null)
|
AuthorizationOptions authOptions = null)
|
||||||
{
|
{
|
||||||
|
|
@ -731,6 +777,31 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BaseClassWithAttributeRoutesController
|
||||||
|
{
|
||||||
|
[Route("A")]
|
||||||
|
[Route("B")]
|
||||||
|
public virtual void Edit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedClassInheritsAttributeRoutesController : BaseClassWithAttributeRoutesController
|
||||||
|
{
|
||||||
|
public override void Edit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedClassOverridesAttributeRoutesController : BaseClassWithAttributeRoutesController
|
||||||
|
{
|
||||||
|
[Route("C")]
|
||||||
|
[Route("D")]
|
||||||
|
public override void Edit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class BaseController : Controller
|
private class BaseController : Controller
|
||||||
{
|
{
|
||||||
public void GetFromBase() // Valid action method.
|
public void GetFromBase() // Valid action method.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue