Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
[Fixes #734] Attribute Routing: Implement Name
Browse files Browse the repository at this point in the history
1. Added support for Name in attribute routing. Name can be defined using [RouteAttribute]
and the different Http*Attributes, for example [HttpGet].

2. Names defined on actions always override names defined on the controller.

3. Actions with a non empty template don't inherit the name from the controller. The name
   is only inherited from the controller when the action template is null or empty.

4. Multiple attribute routes with different templates and the same name are not allowed.
  • Loading branch information
javiercn committed Aug 26, 2014
1 parent 313a537 commit 437c232
Show file tree
Hide file tree
Showing 21 changed files with 984 additions and 23 deletions.
3 changes: 3 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/HttpMethodAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,8 @@ public int Order
return _order;
}
}

/// <inheritdoc />
public string Name { get; set; }
}
}
52 changes: 50 additions & 2 deletions src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
var attributeRouteInfo = combinedRoute == null ? null : new AttributeRouteInfo()
{
Template = combinedRoute.Template,
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder,
Name = combinedRoute.Name
};

var actionDescriptor = new ReflectedActionDescriptor()
Expand Down Expand Up @@ -291,6 +292,8 @@ public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
}
}

var actionsByRouteName = new Dictionary<string, IList<ActionDescriptor>>(StringComparer.OrdinalIgnoreCase);

foreach (var actionDescriptor in actions)
{
if (actionDescriptor.AttributeRouteInfo == null ||
Expand All @@ -317,6 +320,25 @@ public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
}
else
{
// Build a map of attribute route name to action descriptors to ensure that all
// attribute routes with a given name have the same template.
IList<ActionDescriptor> actionGroup;

var attributeRouteInfo = actionDescriptor.AttributeRouteInfo;
if (attributeRouteInfo.Name != null)
{
if (actionsByRouteName.TryGetValue(attributeRouteInfo.Name, out actionGroup))
{
actionGroup.Add(actionDescriptor);
}
else
{
actionGroup = new List<ActionDescriptor>();
actionGroup.Add(actionDescriptor);
actionsByRouteName.Add(attributeRouteInfo.Name, actionGroup);
}
}

// We still want to add a 'null' for any constraint with DenyKey so that link generation
// works properly.
//
Expand All @@ -332,6 +354,12 @@ public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
}
}

var namedRoutedErrors = ValidateNamedAttributeRoutedActions(actionsByRouteName);
if (namedRoutedErrors != null)
{
throw new InvalidOperationException(namedRoutedErrors);
}

if (routeTemplateErrors.Any())
{
var message = Resources.FormatAttributeRoute_AggregateErrorMessage(
Expand All @@ -343,6 +371,41 @@ public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
return actions;
}

private static string ValidateNamedAttributeRoutedActions(
IDictionary<string,
IList<ActionDescriptor>> actionsGroupedByRouteName)
{
foreach (var kvp in actionsGroupedByRouteName)
{
var firstActionDescriptor = kvp.Value[0];
var firstTemplate = firstActionDescriptor.AttributeRouteInfo.Template;

for (var i = 1; i < kvp.Value.Count; i++)
{
var otherActionDescriptor = kvp.Value[i];
var otherActionTemplate = otherActionDescriptor.AttributeRouteInfo.Template;

if (!firstTemplate.Equals(otherActionTemplate, StringComparison.OrdinalIgnoreCase))
{
var descriptions = kvp.Value.Select(ad =>
Resources.FormatActionDescriptor_WithNamedAttributeRouteAndDifferentTemplate(
ad.DisplayName,
ad.AttributeRouteInfo.Template));

var errorDescription = string.Join(Environment.NewLine, descriptions);
var message = Resources.FormatAttributeRoute_ActionsWithDifferentTemplate_AndSameName(
kvp.Key,
Environment.NewLine,
errorDescription);

return message;
}
}
}

return null;
}

private static string GetRouteGroupValue(int order, string template)
{
var group = string.Format("{0}-{1}", order, template);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ public ReflectedAttributeRouteModel([NotNull] IRouteTemplateProvider templatePro
{
Template = templateProvider.Template;
Order = templateProvider.Order;
Name = templateProvider.Name;
}

public string Template { get; set; }

public int? Order { get; set; }

public string Name { get; set; }

/// <summary>
/// Combines two <see cref="ReflectedAttributeRouteModel"/> instances and returns
/// a new <see cref="ReflectedAttributeRouteModel"/> instance with the result.
Expand Down Expand Up @@ -60,10 +63,25 @@ public ReflectedAttributeRouteModel([NotNull] IRouteTemplateProvider templatePro
return new ReflectedAttributeRouteModel()
{
Template = combinedTemplate,
Order = right.Order ?? left.Order
Order = right.Order ?? left.Order,
Name = CombineNames(left, right)
};
}

private static string CombineNames(
ReflectedAttributeRouteModel left,
ReflectedAttributeRouteModel right)
{
if (right.Name == null && (right.Template == null || right.Template == string.Empty))
{
return left.Name;
}
else
{
return right.Name;
}
}

internal static string CombineTemplates(string left, string right)
{
var result = CombineCore(left, right);
Expand Down Expand Up @@ -252,7 +270,7 @@ public static string ReplaceTokens(string template, IDictionary<string, object>
{
// This is an unclosed replacement token
var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax(
template,
template,
Resources.AttributeRoute_TokenReplacement_UnclosedToken);
throw new InvalidOperationException(message);
}
Expand All @@ -272,7 +290,7 @@ public static string ReplaceTokens(string template, IDictionary<string, object>
{
// Unescaped left-bracket is not allowed inside a token.
var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax(
template,
template,
Resources.AttributeRoute_TokenReplacement_UnescapedBraceInToken);
throw new InvalidOperationException(message);
}
Expand Down Expand Up @@ -303,7 +321,7 @@ public static string ReplaceTokens(string template, IDictionary<string, object>
}

builder.Append(value);

if (c == '[')
{
state = TemplateParserState.SeenLeft;
Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,14 @@
<data name="UnableToFindServices" xml:space="preserve">
<value>Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code.</value>
</data>
<data name="AttributeRoute_DifferentLinkGenerationEntries_SameName" xml:space="preserve">
<value>Two or more routes named '{0}' have different templates.</value>
</data>
<data name="ActionDescriptor_WithNamedAttributeRouteAndDifferentTemplate" xml:space="preserve">
<value>'{0}': '{1}'</value>
</data>
<data name="AttributeRoute_ActionsWithDifferentTemplate_AndSameName" xml:space="preserve">
<value>The following named attribute routed actions have the same name '{0}', but different templates:{1}{2}</value>
<comment>{0} is the name of the attribute route, {1} is the newline, {2} is the list of errors formatted using ActionDescriptor_WithNamedAttributeRouteAndDifferentTemplate</comment>
</data>
</root>
3 changes: 3 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/RouteAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ public int Order
return _order;
}
}

/// <inheritdoc />
public string Name { get; set; }
}
}

0 comments on commit 437c232

Please sign in to comment.