title: .Net core 2.2 to 3.x migration: Url.Action generate wrong URL with endpoint routing deactivated
body: <p>I'm working on migrating a webapp from .Net core 2.2 to 3.1. In it, We have extensive use of attribute routing and Url.Action in both controllers and views (we don't use any asp tag). Because we would need to change a lot to use endpoint routing, I deactivated it using <code>EnableEndpointRouting = false</code>. </p>
  
  <p>Problem is: Url.Action is generating wrong urls that ends up in 404! It ignores completely the attribute routing when generating the url and I don't understand why.</p>
  
  <p>If anyone could help me understand what I'm doing wrong and how to look properly at this thing, it would be much appreciated.</p>
  
  <p>Startup.cs:</p>
  
  <pre><code>public void ConfigureServices(IServiceCollection services)
  {
      // First I tried this, using 
      services.AddControllersWithViews(options =&gt; options.EnableEndpointRouting = false)
                  .AddRazorRuntimeCompilation()
                  .AddNewtonsoftJson(options =&gt;
                  {
                      // Use the default property (Pascal) casing
                      options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                  });
      services.AddRazorPages()
                  .AddMvcOptions(options =&gt; options.EnableEndpointRouting = false)
                  .AddRazorRuntimeCompilation();
  
      // Then I tried this
      services
                  .AddMvc(options =&gt; options.EnableEndpointRouting = false)
                  .AddRazorRuntimeCompilation()
                  .AddNewtonsoftJson(options =&gt; options.SerializerSettings.ContractResolver = new DefaultContractResolver())
                  .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
  }
  
  public void Configure(IApplicationBuilder app)
  {
      app.UseAuthentication();
      app.UseAuthorization();
  
      // I remove all app.UseRouting and app.UseEndpoints to keep only mvc
      app.UseMvc(
                  routes =&gt;
                  {
                      routes.MapRoute(name: "default", template: "controller=Home}/{action=Index}/{id?}");
  
                  });
  }
  </code></pre>
  
  <p>Then I have the following controller with attribute routing:</p>
  
  <pre><code>    [Authorize(AuthPermissions.HardLogin)]
      [Route("maker/cards")]
      public class CreditCardsController : Controller
      {     
  
          [Route("")]
          [HttpGet]
          public async Task&lt;IActionResult&gt; IndexAsync()
          {//Do stuff}
      } 
  </code></pre>
  
  <p>To  access this one, it needs to go through <code>~/maker/cards</code>, easy.</p>
  
  <p>And then in some cshtml page, Using the following to link to the previous action:</p>
  
  <pre><code>// Existing link
  &lt;a href="@Url.Action("IndexAsync", "CreditCards")" data-test-credit-cards&gt;
      &lt;span&gt;Stored cards&lt;/span&gt;
  &lt;/a&gt;
  
  // Tried this as well
  &lt;a asp-controller="CreditCards" asp-action="IndexAsync" data-test-credit-cards&gt;Stored cards asp-action&lt;/a&gt;
  </code></pre>
  
  <p>Problem both generate the following url <code>~/creditcards/indexasync</code>, which is not the one configure in the routing! In 2.2, the same Url.Action generates the expected url <code>/maker/cards</code>.</p>
  
  <p>With <code>EnableEndpointRouting = false</code>, I would expect the UrlHelper to use the attribute routing to generate the link, but it doesn't. What am I missing here?</p>
  
  <p>fun fact: if I use the <code>Url.RouteUrl</code> and add the route name to the controller, it generates it properly!</p>
  
  <pre><code>&lt;a href="@Url.RouteUrl("MyCreditCards")" data-test-subscriptions&gt;
      &lt;span&gt;Manage subscriptions (@(Model.User.SubscriptionsCount))&lt;/span&gt;
  &lt;/a&gt;
  =&gt; Generates '/maker/cards'
  </code></pre>
  
  <p>Edit: after more than a day spent on it and only after an hour after posting here, I found the problem. In 3.0, they introduced a new functionality that ignores <code>Async</code> in action name, which forces you to use the name without async when generating a link.
  <a href="https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-2.2&amp;tabs=visual-studio#async-suffix-removal-from-controller-action-names" rel="nofollow noreferrer">https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-2.2&amp;tabs=visual-studio#async-suffix-removal-from-controller-action-names</a></p>
  
  <p>So there was 2 solutions:</p>
  
  <ul>
  <li>I could transform <code>@Url.Action("IndexAsync", "CreditCards")</code> to <code>@Url.Action("Index", "CreditCards")</code> and it works, but I have to update it everywhere in the app. (funny enough, with that format, visual studio does not recognises the endpoint and tags it as unknown)</li>
  <li>Or there is an option to deactivate this option app wise by adding the following options in the ConfigureService <code>options.SuppressAsyncSuffixInActionNames = false;</code>. Which is what I ended up doing.</li>
  </ul>
  
  <p>Working solution is as follow now:
  Startup.cs</p>
  
  <pre><code>ConfigureServices:
             services.AddControllersWithViews(options =&gt;
                  {
                      options.EnableEndpointRouting = false;
                      options.SuppressAsyncSuffixInActionNames = false;
                  })
                  .AddRazorRuntimeCompilation()
                  .AddNewtonsoftJson(options =&gt;
                  {
                      // Use the default property (Pascal) casing
                      options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                  });
  
  Configure:
          app.UseMvc(
                  routes =&gt;
                  {
                      routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
                  });
  
  (btw you can replace the above by app.UseMvcWithDefaultRoute(); now)
  
  </code></pre>
  
  <p>I now have the same behaviour as before bumping to 3.1.</p>