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>();
|
||||
}
|
||||
|
||||
// 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>
|
||||
// is needed to so that the result of ToArray() is object
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
[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(
|
||||
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
|
||||
{
|
||||
public void GetFromBase() // Valid action method.
|
||||
|
|
|
|||
Loading…
Reference in New Issue