Search code examples
asp.netasp.net-web-apiurl-routingasp.net-mvc-routing

Url.Action returning incorrect URL for webapi action with Route attrubute


I have a problem with the behaviour of Url.Action();

I have a webapi where all controllers require explicit route prefix attribute and all actions require a route attribute.

I register my routes in the WebApiConfig.cs

 var constraintResolver = new DefaultInlineConstraintResolver()
            {
                ConstraintMap =
                {
                    ["apiVersion"] = typeof( ApiVersionRouteConstraint )
                }
            };
            config.MapHttpAttributeRoutes(constraintResolver);

I have currently commented out the line below, but (because) it did not change the incorrect behaviour:

  //config.Routes.MapHttpRoute(name: "DefaultApi", 
 //routeTemplate: "api/v{version:apiVersion}/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional});
 

My controllers look as follows:

 [RoutePrefix("api/v{version:apiVersion}/programs")]
 public class ProgramsController : ApiController
 {
    [HttpGet, Route("{telemetryKey}/versions/latest")]
    public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
    {
        // serious business logic
    }

 }

I expect that '@Url.Action("GetLatestVersionInfo", "Programs", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })'

should return /api/v1/programs/43808405-afca-4abb-a92a-519489d62290/versions/latest

however, I get /Programs/GetLatestVersionInfo?telemetryKey=43808405-afca-4abb-a92a-519489d62290 instead. So, my routeprefix and route attributes are ignored.

Swagger correctly discovers my routes and I can validate that requests to the expected routes work OK - it's only the Url.Action() that is confused.

enter image description here

What can be wrong...?


Solution

  • Well, it seems there were a few things wrong.

    Wrong helper: I should be using the Url.HttpRouteUrl for generating API links from a razor view (Url.Link is for generating link from within API controllers)

    Conflict with aspnet-api-versioning library For some reason (perhaps a bug?) the prefix that I have on the controller (apiVersion variable) breaks the URL helper mechanism.

    For now, I have ditched the aspnet-api-versioning library, but created an issue on their github repo, in case its a bug.

    Since I really hate the idea of creating and maintaing magic strings, so I took the following approach - each controller has a public static class which contains const values for the route names:

    [RoutePrefix("api/v1/developers")]
    public class DevelopersController : ApiController
    {
        [HttpGet, Route("{developerId}/programs", Name = Routes.GetPrograms)]
        public async Task<IEnumerable<Program>> GetPrograms(Guid developerId){}
    
        public static class Routes
        {
            public const string GetPrograms = nameof(DevelopersController) +"."+ nameof(DevelopersController.GetPrograms);
        }
    }
    

    Now that can be used from a razor controller in a simple and relatively safe manner:

    @Url.HttpRouteUrl(DevelopersController.Routes.GetPrograms, new { developerId = /* uniquest of guids */})

    A bit better than magic strings. I've also added a bunch of unit tests for controllers where I validate that each route is unique and proper and that the routes class only contains routes for the action it contains.