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












1















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...?










share|improve this question



























    1















    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...?










    share|improve this question

























      1












      1








      1


      1






      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...?










      share|improve this question














      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...?







      asp.net asp.net-web-api url-routing asp.net-mvc-routing






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Dec 30 '18 at 14:37









      BartoszBartosz

      1,16921541




      1,16921541
























          2 Answers
          2






          active

          oldest

          votes


















          0














          Try the following:



          Name your route:



          [HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
          public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
          {
          // serious business logic
          }


          Use Url.Link method:



          @Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })





          share|improve this answer
























          • I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

            – Bartosz
            Dec 30 '18 at 21:21











          • I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

            – Mohsin Mehmood
            Dec 30 '18 at 21:27






          • 1





            You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

            – Mohsin Mehmood
            Dec 30 '18 at 21:28











          • Actually, I tried the approach with the named route, but it returns an empty string.

            – Bartosz
            Dec 31 '18 at 8:37



















          0














          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.






          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53978511%2furl-action-returning-incorrect-url-for-webapi-action-with-route-attrubute%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            0














            Try the following:



            Name your route:



            [HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
            public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
            {
            // serious business logic
            }


            Use Url.Link method:



            @Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })





            share|improve this answer
























            • I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

              – Bartosz
              Dec 30 '18 at 21:21











            • I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

              – Mohsin Mehmood
              Dec 30 '18 at 21:27






            • 1





              You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

              – Mohsin Mehmood
              Dec 30 '18 at 21:28











            • Actually, I tried the approach with the named route, but it returns an empty string.

              – Bartosz
              Dec 31 '18 at 8:37
















            0














            Try the following:



            Name your route:



            [HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
            public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
            {
            // serious business logic
            }


            Use Url.Link method:



            @Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })





            share|improve this answer
























            • I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

              – Bartosz
              Dec 30 '18 at 21:21











            • I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

              – Mohsin Mehmood
              Dec 30 '18 at 21:27






            • 1





              You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

              – Mohsin Mehmood
              Dec 30 '18 at 21:28











            • Actually, I tried the approach with the named route, but it returns an empty string.

              – Bartosz
              Dec 31 '18 at 8:37














            0












            0








            0







            Try the following:



            Name your route:



            [HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
            public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
            {
            // serious business logic
            }


            Use Url.Link method:



            @Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })





            share|improve this answer













            Try the following:



            Name your route:



            [HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
            public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
            {
            // serious business logic
            }


            Use Url.Link method:



            @Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Dec 30 '18 at 21:13









            Mohsin MehmoodMohsin Mehmood

            2,2212513




            2,2212513













            • I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

              – Bartosz
              Dec 30 '18 at 21:21











            • I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

              – Mohsin Mehmood
              Dec 30 '18 at 21:27






            • 1





              You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

              – Mohsin Mehmood
              Dec 30 '18 at 21:28











            • Actually, I tried the approach with the named route, but it returns an empty string.

              – Bartosz
              Dec 31 '18 at 8:37



















            • I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

              – Bartosz
              Dec 30 '18 at 21:21











            • I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

              – Mohsin Mehmood
              Dec 30 '18 at 21:27






            • 1





              You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

              – Mohsin Mehmood
              Dec 30 '18 at 21:28











            • Actually, I tried the approach with the named route, but it returns an empty string.

              – Bartosz
              Dec 31 '18 at 8:37

















            I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

            – Bartosz
            Dec 30 '18 at 21:21





            I have 100 actions on all controllers... does this means I need to create 100 named routes and maintain those 100 magic strings with route names?

            – Bartosz
            Dec 30 '18 at 21:21













            I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

            – Mohsin Mehmood
            Dec 30 '18 at 21:27





            I believe so. Also, if you want to get relative url then use @Url.Route. @Url.Link will give you absolute url.

            – Mohsin Mehmood
            Dec 30 '18 at 21:27




            1




            1





            You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

            – Mohsin Mehmood
            Dec 30 '18 at 21:28





            You can check the code of UrlHelper class at github github.com/aspnet/AspNetWebStack/blob/master/src/…

            – Mohsin Mehmood
            Dec 30 '18 at 21:28













            Actually, I tried the approach with the named route, but it returns an empty string.

            – Bartosz
            Dec 31 '18 at 8:37





            Actually, I tried the approach with the named route, but it returns an empty string.

            – Bartosz
            Dec 31 '18 at 8:37













            0














            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.






            share|improve this answer




























              0














              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.






              share|improve this answer


























                0












                0








                0







                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.






                share|improve this answer













                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.







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Jan 2 at 13:43









                BartoszBartosz

                1,16921541




                1,16921541






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53978511%2furl-action-returning-incorrect-url-for-webapi-action-with-route-attrubute%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Monofisismo

                    Angular Downloading a file using contenturl with Basic Authentication

                    Olmecas