5

ASP.NET Web API 2 allows to set route name on a controller:

Route names are useful for generating links, so that you can include a link in an HTTP response.

I define two controllers, each of them returns a link to another one in the response (it does not matter if that is a SSC ServicesApiController or standard Web API ApiController):

[Route("api/valuesA", Name = "ValuesA")]
public class ValuesAController : ServicesApiController
{
    public object Get()
    {
        var link = Url.Link("ValuesB", new { });
        return new { link_to_valuesB = link };
    }
}

[Route("api/valuesB", Name = "ValuesB")]
public class ValuesBController : ServicesApiController
{
    public object Get()
    {
        var link = Url.Link("ValuesA", new { });
        return new { link_to_valuesA = link };
    }
}

Request api/valuesA returns link to valuesB:

{
    "link_to_valuesB": "http://mysite/api/valuesB"
}

The controllers work fine, but all the existing SSC controllers inherited from either EntityService<> or ServiceBaseODataController<> class just crash. All the SPEAK-based marketing applications heavily rely on the EntityService<> and all of them just stop working returning numerous 500 Server Errors:

{"Message":"An error has occurred.","ExceptionMessage":"A route named 'ValuesA' is already in the route collection. Route names must be unique.\r\n
Parameter name: name","ExceptionType":"System.ArgumentException","StackTrace":"   at System.Web.Routing.RouteCollection.Add(String name, RouteBase item)\r\n
at System.Web.Http.Routing.AttributeRoutingMapper.AddGenerationHooksForSubRoutes(HttpRouteCollection routeTable, IEnumerable`1 entries)\r\n
at System.Web.Http.Routing.AttributeRoutingMapper.<>c__DisplayClass2.<MapAttributeRoutes>b__0(HttpConfiguration config)\r\n
at System.Web.Http.HttpConfiguration.ApplyControllerSettings(HttpControllerSettings settings, HttpConfiguration configuration)\r\n
at System.Web.Http.Controllers.HttpControllerDescriptor.InvokeAttributesOnControllerType(HttpControllerDescriptor controllerDescriptor, Type type)\r\n
at System.Web.Http.Controllers.HttpControllerDescriptor..ctor(HttpConfiguration configuration, String controllerName, Type controllerType)\r\n
at System.Web.Http.Dispatcher.DefaultHttpControllerSelector.InitializeControllerInfoCache()\r\n
at System.Lazy`1.CreateValue()\r\n
--- End of stack trace from previous location where exception was thrown ---\r\n
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Lazy`1.get_Value()\r\n
at System.Web.Http.Dispatcher.DefaultHttpControllerSelector.SelectController(HttpRequestMessage request)\r\n
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"}

Any idea why is that? (Sitecore 8.2.3).

4
  • This may be related to the order that routes are registered. How are you registering your custom routes? Jun 21, 2017 at 16:43
  • Via attributes, as in the question above Jun 21, 2017 at 16:45
  • See @Richard Seal's answer below. You are using named Routes. Those must be registered somewhere. Jun 21, 2017 at 17:29
  • I suppose there is no need to register the routes since that's already done by SSC. The problem (as I see it) is opposite. The route is being registered twice: "ExceptionMessage":"A route named 'ValuesA' is already in the route collection.. Jun 22, 2017 at 7:22

2 Answers 2

4

This will be the way the routes get registered when applied by attributes. The correct way to register your own routes with Sitecore is to write a custom initialize pipeline processor and patch it in before the Sitecore InitializeRoutes processor.

Take this example from the Habitat project:

public class RegisterWebApiRoutes
{
    public void Process(PipelineArgs args)
    {
        RouteTable.Routes.MapRoute("Feature.Accounts.Api", "api/accounts/{action}", new
        {
            controller = "Accounts"
        });
    }
}

This registers the route, and then the config registers the pipeline processor:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <initialize>
                <processor type="Sitecore.Feature.Accounts.Pipelines.RegisterWebApiRoutes, Sitecore.Feature.Accounts" 
                           patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" />
            </initialize>
        </pipelines>
    </sitecore>
</configuration>

Notice that you register before the Sitecore processor to initialize routes, this is important so you don't override any of the standard Sitecore routes.

1
  • thanks for your answer but are you sure I need to register the routes? The error says it's already registered. BTW, the pipeline processor you shared deals with MVC controllers but I'm designing controllers using Web API 2 (the route registration mechanism is different). Jun 22, 2017 at 7:28
0

This is rather a workaround than the answer to the original question.

I used Richard Seal's idea and registered my Web API controllers manually. I removed all the [Route(Name="...")] attributes, implemented Sitecore.Services.Infrastructure.Web.Http.IMapRoutes interface and registered it instead of the default one:

public class MyRouteMapper : IMapRoutes
{
    private readonly IMapRoutes _defaultRoutes = new DefaultRouteMapper();

    public void MapRoutes(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute("ValuesA", "api/valuesA", new { });
        config.Routes.MapHttpRoute("ValuesB", "api/valuesB", new { });
        _defaultRoutes.MapRoutes(config);
    }

    public void MapRoutes(RouteCollection routes)
    {
        _defaultRoutes.MapRoutes(routes);
    }
}


<settings>
  <setting name="Sitecore.Services.RouteMapper">
    <patch:attribute name="value">MyProj.MyRouteMapper,MyProj</patch:attribute>
  </setting>
</settings>

Now all the existing and new controllers work fine but the question with named routes is still open.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.