Remove Magic Link Generation

This change resolves #3512 and #3636 by removing 'magic' link generation
and adding an extension method to add routes to areas correctly using the new
pattern. This is pretty much exactly the same as how MapWebApiRoute works.

For site authors, we recommend adding area-specific routes in a way that
includes a default AND constraint for the area. Put your most specific
(for link generation) routes FIRST.

Ex:

  routes.MapRoute(
      "Admin/{controller}/{action}/{id?}",
      defaults: new { area = "Admin" },
      constraints: new { area = "Admin" });

The bulk of the changes here are to tests that unwittingly relied on the
old behavior.
This commit is contained in:
Ryan Nowak 2015-11-24 10:45:40 -08:00
parent 91e837d465
commit 6875ee55d3
12 changed files with 453 additions and 55 deletions

View File

@ -0,0 +1,140 @@
// 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.AspNet.Mvc.Core;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Builder
{
/// <summary>
/// Extension methods for <see cref="IRouteBuilder"/>.
/// </summary>
public static class MvcAreaRouteBuilderExtensions
{
/// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the given MVC area with the specified
/// <paramref name="name"/>, <paramref name="areaName"/> and <paramref name="template"/>.
/// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param>
/// <param name="areaName">The MVC area name.</param>
/// <param name="template">The URL pattern of the route.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapAreaRoute(
this IRouteBuilder routeBuilder,
string name,
string areaName,
string template)
{
MapAreaRoute(routeBuilder, name, areaName, template, defaults: null, constraints: null, dataTokens: null);
return routeBuilder;
}
/// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the given MVC area with the specified
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>, and
/// <paramref name="defaults"/>.
/// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param>
/// <param name="areaName">The MVC area name.</param>
/// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">
/// An object that contains default values for route parameters. The object's properties represent the
/// names and values of the default values.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapAreaRoute(
this IRouteBuilder routeBuilder,
string name,
string areaName,
string template,
object defaults)
{
MapAreaRoute(routeBuilder, name, areaName, template, defaults, constraints: null, dataTokens: null);
return routeBuilder;
}
/// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the given MVC area with the specified
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>,
/// <paramref name="defaults"/>, and <paramref name="constraints"/>.
/// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param>
/// <param name="areaName">The MVC area name.</param>
/// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">
/// An object that contains default values for route parameters. The object's properties represent the
/// names and values of the default values.
/// </param>
/// <param name="constraints">
/// An object that contains constraints for the route. The object's properties represent the names and
/// values of the constraints.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapAreaRoute(
this IRouteBuilder routeBuilder,
string name,
string areaName,
string template,
object defaults,
object constraints)
{
MapAreaRoute(routeBuilder, name, areaName, template, defaults, constraints, dataTokens: null);
return routeBuilder;
}
/// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the given MVC area with the specified
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>,
/// <paramref name="defaults"/>, <paramref name="constraints"/>, and <paramref name="dataTokens"/>.
/// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param>
/// <param name="areaName">The MVC area name.</param>
/// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">
/// An object that contains default values for route parameters. The object's properties represent the
/// names and values of the default values.
/// </param>
/// <param name="constraints">
/// An object that contains constraints for the route. The object's properties represent the names and
/// values of the constraints.
/// </param>
/// <param name="dataTokens">
/// An object that contains data tokens for the route. The object's properties represent the names and
/// values of the data tokens.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapAreaRoute(
this IRouteBuilder routeBuilder,
string name,
string areaName,
string template,
object defaults,
object constraints,
object dataTokens)
{
if (routeBuilder == null)
{
throw new ArgumentNullException(nameof(routeBuilder));
}
if (string.IsNullOrEmpty(areaName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName));
}
var defaultsDictionary = new RouteValueDictionary(defaults);
defaultsDictionary["area"] = areaName;
var constraintsDictionary = new RouteValueDictionary(constraints);
constraintsDictionary["area"] = areaName;
routeBuilder.MapRoute(name, template, defaultsDictionary, constraintsDictionary, dataTokens);
return routeBuilder;
}
}
}

View File

@ -32,11 +32,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
throw new ArgumentNullException(nameof(context));
}
EnsureServices(context.Context);
// The contract of this method is to check that the values coming in from the route are valid;
// that they match an existing action, setting IsBound = true if the values are OK.
context.IsBound = _actionSelector.HasValidAction(context);
context.IsBound = true;
// We return null here because we're not responsible for generating the url, the route is.
return null;

View File

@ -0,0 +1,258 @@
// 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 Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Builder
{
public class MvcAreaRouteBuilderExtensionsTest
{
[Fact]
public void MapAreaRoute_Simple()
{
// Arrange
var builder = new RouteBuilder()
{
DefaultHandler = Mock.Of<IRouter>(),
ServiceProvider = CreateServices(),
};
// Act
builder.MapAreaRoute(name: null, areaName: "admin", template: "site/Admin/");
// Assert
var route = Assert.IsType<TemplateRoute>((Assert.Single(builder.Routes)));
Assert.Null(route.Name);
Assert.Equal("site/Admin/", route.RouteTemplate);
Assert.Collection(
route.Constraints.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.IsType<RegexRouteConstraint>(kvp.Value);
});
Assert.Empty(route.DataTokens);
Assert.Collection(
route.Defaults.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.Equal(kvp.Value, "admin");
});
}
[Fact]
public void MapAreaRoute_Defaults()
{
// Arrange
var builder = new RouteBuilder()
{
DefaultHandler = Mock.Of<IRouter>(),
ServiceProvider = CreateServices(),
};
// Act
builder.MapAreaRoute(
name: "admin_area",
areaName: "admin",
template: "site/Admin/",
defaults: new { action = "Home" });
// Assert
var route = Assert.IsType<TemplateRoute>((Assert.Single(builder.Routes)));
Assert.Equal("admin_area", route.Name);
Assert.Equal("site/Admin/", route.RouteTemplate);
Assert.Collection(
route.Constraints.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.IsType<RegexRouteConstraint>(kvp.Value);
});
Assert.Empty(route.DataTokens);
Assert.Collection(
route.Defaults.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "action");
Assert.Equal(kvp.Value, "Home");
},
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.Equal(kvp.Value, "admin");
});
}
[Fact]
public void MapAreaRoute_DefaultsAndConstraints()
{
// Arrange
var builder = new RouteBuilder()
{
DefaultHandler = Mock.Of<IRouter>(),
ServiceProvider = CreateServices(),
};
// Act
builder.MapAreaRoute(
name: "admin_area",
areaName: "admin",
template: "site/Admin/",
defaults: new { action = "Home" },
constraints: new { id = new IntRouteConstraint() });
// Assert
var route = Assert.IsType<TemplateRoute>((Assert.Single(builder.Routes)));
Assert.Equal("admin_area", route.Name);
Assert.Equal("site/Admin/", route.RouteTemplate);
Assert.Collection(
route.Constraints.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.IsType<RegexRouteConstraint>(kvp.Value);
},
kvp =>
{
Assert.Equal(kvp.Key, "id");
Assert.IsType<IntRouteConstraint>(kvp.Value);
});
Assert.Empty(route.DataTokens);
Assert.Collection(
route.Defaults.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "action");
Assert.Equal(kvp.Value, "Home");
},
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.Equal(kvp.Value, "admin");
});
}
[Fact]
public void MapAreaRoute_DefaultsConstraintsAndDataTokens()
{
// Arrange
var builder = new RouteBuilder()
{
DefaultHandler = Mock.Of<IRouter>(),
ServiceProvider = CreateServices(),
};
// Act
builder.MapAreaRoute(
name: "admin_area",
areaName: "admin",
template: "site/Admin/",
defaults: new { action = "Home" },
constraints: new { id = new IntRouteConstraint() },
dataTokens: new { some_token = "hello" });
// Assert
var route = Assert.IsType<TemplateRoute>((Assert.Single(builder.Routes)));
Assert.Equal("admin_area", route.Name);
Assert.Equal("site/Admin/", route.RouteTemplate);
Assert.Collection(
route.Constraints.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.IsType<RegexRouteConstraint>(kvp.Value);
},
kvp =>
{
Assert.Equal(kvp.Key, "id");
Assert.IsType<IntRouteConstraint>(kvp.Value);
});
Assert.Collection(
route.DataTokens.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "some_token");
Assert.Equal(kvp.Value, "hello");
});
Assert.Collection(
route.Defaults.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "action");
Assert.Equal(kvp.Value, "Home");
},
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.Equal(kvp.Value, "admin");
});
}
[Fact]
public void MapAreaRoute_ReplacesValuesForArea()
{
// Arrange
var builder = new RouteBuilder()
{
DefaultHandler = Mock.Of<IRouter>(),
ServiceProvider = CreateServices(),
};
// Act
builder.MapAreaRoute(
name: "admin_area",
areaName: "admin",
template: "site/Admin/",
defaults: new { area = "Home" },
constraints: new { area = new IntRouteConstraint() },
dataTokens: new { some_token = "hello" });
// Assert
var route = Assert.IsType<TemplateRoute>((Assert.Single(builder.Routes)));
Assert.Equal("admin_area", route.Name);
Assert.Equal("site/Admin/", route.RouteTemplate);
Assert.Collection(
route.Constraints.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.IsType<RegexRouteConstraint>(kvp.Value);
});
Assert.Collection(
route.DataTokens.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "some_token");
Assert.Equal(kvp.Value, "hello");
});
Assert.Collection(
route.Defaults.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal(kvp.Key, "area");
Assert.Equal(kvp.Value, "admin");
});
}
private IServiceProvider CreateServices()
{
var services = new ServiceCollection();
services.AddRouting();
return services.BuildServiceProvider();
}
}
}

View File

@ -408,7 +408,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
"InlineConstraints_Products",
"GetProductById",
"id",
"sdsd", ""
"sdsd",
"/area-exists/InlineConstraints_Products/GetProductById?id=sdsd"
};
// Attribute Route, name:alpha constraint
@ -458,7 +459,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
"GetProductByCategoryId",
"catId",
"500",
""
"/area-exists/InlineConstraints_Products/GetProductByCategoryId?catId=500"
};
// Attribute Route, name:length(1,20)? constraint
@ -488,7 +489,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
"GetProductByManufacturerId",
"manId",
"qwer",
""
"/area-exists/InlineConstraints_Products/GetProductByManufacturerId?manId=qwer"
};
// Attribute Route, manId:int:min(10)? constraint
@ -498,7 +499,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
"GetProductByManufacturerId",
"manId",
"1",
""
"/area-exists/InlineConstraints_Products/GetProductByManufacturerId?manId=1"
};
// Attribute Route, dateTime:datetime constraint

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
@"<root data-root=""true""><input class=""form-control"" type=""number"" data-val=""true""" +
@" data-val-range=""The field Age must be between 10 and 100."" data-val-range-max=""100"" "+
@"data-val-range-min=""10"" data-val-required=""The Age field is required."" " +
@"id=""Age"" name=""Age"" value="""" /><a href="""">Back to List</a></root>";
@"id=""Age"" name=""Age"" value="""" /><a href=""/TagHelpers"">Back to List</a></root>";
// Act
var response = await Client.GetStringAsync("http://localhost/TagHelpers/Add");

View File

@ -903,7 +903,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task ConventionalRoutedAction_InArea_ImplicitLeaveArea()
public async Task ConventionalRoutedAction_InArea_StaysInArea()
{
// Arrange
var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", });
@ -919,7 +919,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("Flight", result.Controller);
Assert.Equal("Index", result.Action);
Assert.Equal("/Home/Contact", result.Link);
Assert.Equal("/Travel/Home/Contact", result.Link);
}
[Fact]
@ -985,7 +985,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task AttributeRoutedAction_InArea_ImplicitLeaveArea()
public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist()
{
// Arrange
var url = LinkFrom("http://localhost/ContosoCorp/Trains")
@ -1002,7 +1002,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("Rail", result.Controller);
Assert.Equal("Index", result.Action);
Assert.Equal("/Home/Contact", result.Link);
Assert.Equal("/Travel/Home/Contact", result.Link);
}
[Fact]
@ -1158,7 +1158,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task ControllerWithCatchAll_GenerateLink_FailsWithoutCountry()
public async Task ControllerWithCatchAll_GenerateLink_FallsThroughWithoutCountry()
{
// Arrange
var url = LinkFrom("http://localhost/")
@ -1170,7 +1170,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Null(result.Link);
Assert.Equal("/Products/GetProducts", result.Link);
}
[Theory]

View File

@ -1,10 +1,10 @@
<html>
<body>
<div>
<a title="&lt;the title>" href="">Product Index</a>
<a title="&lt;the title>" href="HtmlEncode[[/HtmlGeneration_Product]]">Product Index</a>
</div>
<div>
<a title="&quot;the&quot; title" href="">Product List</a>
<a title="&quot;the&quot; title" href="HtmlEncode[[/HtmlGeneration_Product/List]]">Product List</a>
</div>
<div>
<a id="HtmlGenerationWebSite_Index">HtmlGenerationWebSite Index</a>
@ -13,10 +13,10 @@
<a href="HtmlEncode[[/]]">Default Controller</a>
</div>
<div>
<a href="">Product Index Fragment</a>
<a href="HtmlEncode[[/Product/Index#fragment]]">Product Index Fragment</a>
</div>
<div>
<a href="">Produt Submit Fragment</a>
<a href="HtmlEncode[[/HtmlGeneration_Product/Submit#fragment]]">Product Submit Fragment</a>
</div>
<div>
<a id="HtmlGenerationWebSite_IndexFragment" href="HtmlEncode[[/#fragment]]">
@ -29,7 +29,7 @@
</a>
</div>
<div>
<a href="">
<a href="HtmlEncode[[unkonwn://localhost/HtmlGeneration_Product/List]]">
Unknown Protocol Product List
</a>
</div>
@ -39,32 +39,32 @@
</a>
</div>
<div>
<a href="">Customer Area Customer Index</a>
<a href="HtmlEncode[[/Customer/Customer#fragment]]">Customer Area Customer Index</a>
</div>
<div>
<a href="/Order/List">Href Order List</a>
</div>
<div>
<a href="">Non-existent Controller</a>
<a href="HtmlEncode[[/NonExistentController]]">Non-existent Controller</a>
</div>
<div>
<a href="">
<a href="HtmlEncode[[/NonExistentController#fragment]]">
Non-existent Controller Fragment
</a>
</div>
<div>
<a href="">Non-existent Action</a>
<a href="HtmlEncode[[/Order/NonExistentAction]]">Non-existent Action</a>
</div>
<div>
<a id="Id" href="HtmlEncode[[http://somewhere/]]">Some Where</a>
</div>
<div>
<a href="">
<a href="HtmlEncode[[unknown://localhost/NoControll#fragment]]">
Unknown Protocol Non-existent Controller Fragment
</a>
</div>
<div>
<a href="">Product Route Non-existent Area Parameter</a>
<a href="HtmlEncode[[/Product/Submit?area=NonExistentArea&id=1#fragment]]">Product Route Non-existent Area Parameter</a>
</div>
<div>
<a href="">Non-existent Area</a>

View File

@ -1,10 +1,10 @@
<html>
<body>
<div>
<a title="&lt;the title>" href="">Product Index</a>
<a title="&lt;the title>" href="/HtmlGeneration_Product">Product Index</a>
</div>
<div>
<a title="&quot;the&quot; title" href="">Product List</a>
<a title="&quot;the&quot; title" href="/HtmlGeneration_Product/List">Product List</a>
</div>
<div>
<a id="HtmlGenerationWebSite_Index">HtmlGenerationWebSite Index</a>
@ -13,10 +13,10 @@
<a href="/">Default Controller</a>
</div>
<div>
<a href="">Product Index Fragment</a>
<a href="/Product/Index#fragment">Product Index Fragment</a>
</div>
<div>
<a href="">Produt Submit Fragment</a>
<a href="/HtmlGeneration_Product/Submit#fragment">Product Submit Fragment</a>
</div>
<div>
<a id="HtmlGenerationWebSite_IndexFragment" href="/#fragment">
@ -29,7 +29,7 @@
</a>
</div>
<div>
<a href="">
<a href="unkonwn://localhost/HtmlGeneration_Product/List">
Unknown Protocol Product List
</a>
</div>
@ -39,32 +39,32 @@
</a>
</div>
<div>
<a href="">Customer Area Customer Index</a>
<a href="/Customer/Customer#fragment">Customer Area Customer Index</a>
</div>
<div>
<a href="/Order/List">Href Order List</a>
</div>
<div>
<a href="">Non-existent Controller</a>
<a href="/NonExistentController">Non-existent Controller</a>
</div>
<div>
<a href="">
<a href="/NonExistentController#fragment">
Non-existent Controller Fragment
</a>
</div>
<div>
<a href="">Non-existent Action</a>
<a href="/Order/NonExistentAction">Non-existent Action</a>
</div>
<div>
<a id="Id" href="http://somewhere/">Some Where</a>
</div>
<div>
<a href="">
<a href="unknown://localhost/NoControll#fragment">
Unknown Protocol Non-existent Controller Fragment
</a>
</div>
<div>
<a href="">Product Route Non-existent Area Parameter</a>
<a href="/Product/Submit?area=NonExistentArea&amp;id=1#fragment">Product Route Non-existent Area Parameter</a>
</div>
<div>
<a href="">Non-existent Area</a>

View File

@ -25,10 +25,6 @@ namespace HtmlGenerationWebSite
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "HtmlGeneration_Home", action = "Index" });
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller}/{action}/{id?}",
@ -37,6 +33,10 @@ namespace HtmlGenerationWebSite
name: "productRoute",
template: "Product/{action}",
defaults: new { controller = "Product" });
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "HtmlGeneration_Home", action = "Index" });
});
}
}

View File

@ -7,10 +7,10 @@
<html>
<body>
<div>
<a asp-controller="Product" title="&lt;the title>">Product Index</a>
<a asp-controller="HtmlGeneration_Product" title="&lt;the title>">Product Index</a>
</div>
<div>
<a asp-controller="Product" asp-action="List" title='"the" title'>Product List</a>
<a asp-controller="HtmlGeneration_Product" asp-action="List" title='"the" title'>Product List</a>
</div>
<div>
<a id="HtmlGenerationWebSite_Index">HtmlGenerationWebSite Index</a>
@ -22,7 +22,7 @@
<a asp-fragment="fragment" asp-controller="Product">Product Index Fragment</a>
</div>
<div>
<a asp-controller="Product" asp-action="Submit" asp-fragment="fragment">Produt Submit Fragment</a>
<a asp-controller="HtmlGeneration_Product" asp-action="Submit" asp-fragment="fragment">Product Submit Fragment</a>
</div>
<div>
<a id="HtmlGenerationWebSite_IndexFragment" asp-fragment="fragment">
@ -35,7 +35,7 @@
</a>
</div>
<div>
<a asp-controller="Product" asp-protocol="unkonwn" asp-action="List">
<a asp-controller="HtmlGeneration_Product" asp-protocol="unkonwn" asp-action="List">
Unknown Protocol Product List
</a>
</div>

View File

@ -24,18 +24,21 @@ namespace RoutingWebSite
app.UseMvc(routes =>
{
routes.MapRoute("areaRoute",
"{area:exists}/{controller}/{action}",
new { controller = "Home", action = "Index" });
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
routes.MapRoute(
"areaRoute",
"{area:exists}/{controller}/{action}",
new { controller = "Home", action = "Index" });
routes.MapRoute(
"products",
"api/Products/{country}/{action}",
defaults: new { controller = "Products" });
routes.MapRoute(
"ActionAsMethod",
"{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
// Added this route to validate that we throw an exception when a conventional
// route matches a link generated by a named attribute route.
// The conventional route will match first, but when the attribute route generates

View File

@ -23,15 +23,15 @@ namespace WebApiCompatShimWebSite
app.UseMvc(routes =>
{
// This route can't access any of our webapi controllers
routes.MapRoute("default", "{controller}/{action}/{id?}");
// Tests include different styles of WebAPI conventional routing and action selection - the prefix keeps
// them from matching too eagerly.
routes.MapWebApiRoute("named-action", "api/Blog/{controller}/{action}/{id?}");
routes.MapWebApiRoute("unnamed-action", "api/Admin/{controller}/{id?}");
routes.MapWebApiRoute("name-as-parameter", "api/Store/{controller}/{name?}");
routes.MapWebApiRoute("extra-parameter", "api/Support/{extra}/{controller}/{id?}");
// This route can't access any of our webapi controllers
routes.MapRoute("default", "{controller}/{action}/{id?}");
});
}
}