Aspnet-api-versioning: RedirectToAction not working with endpoint routing

Created on 18 Dec 2018  路  10Comments  路  Source: microsoft/aspnet-api-versioning

I came across some issue when I was trying RedirectToAction

With .NET Core 2.2 and Versioning 3.0.0, RedirectToAction cannot properly redirect my call to another controller's action.
``` C#
return RedirectToAction("Search", "Users", new { query, token });

It throws the following error message
> No route matches the supplied values.

Turn out it is again caused by the new endpoint routing and setting the following will fix the issue.
``` C#
.AddMvc(opt => opt.EnableEndpointRouting = false)

Reference: https://github.com/Microsoft/aspnet-api-versioning/issues/363#issuecomment-443544305
Related: https://github.com/Microsoft/aspnet-api-versioning/issues/18 https://github.com/Microsoft/aspnet-api-versioning/issues/131

asp.net core duplicate

Most helpful comment

The ApiVersion is _special_ as it relates to model binding because it can come from multiple places, even within the same request. The value is ultimately provided by the IApiVersioningFeature. There are a couple of other ways you can get at this value too.

  • Request.HttpContext.GetRequestedApiVersion()
  • HttpContext.Features.Get<IApiVersioningFeature>().RequestedApiVersion

The following _union_ will work for your scenario:

c# [HttpGet("redirect")] public ActionResult<IEnumerable<string>> Redirect([FromQuery]Query q, ApiVersion apiVersion) { var routeValues = new RouteValueDictionary(q) { ["version"] = apiVersion.ToString() }; return RedirectToAction("Test", "Values", routeValues); }

There _might_ be a way to hook into or otherwise extend the URL generation mechanism to automatically inject the route value when available. Keep in mind the following:

  1. The version of requested API and the linked API need to match
  2. The route parameter names always need to be the same (e.g. version)

If those are wrong, link generation may fail or produce the incorrect result.

I hope that helps.

All 10 comments

Correct. 3.0 doesn't support _Endpoint Routing_ - yet. This was mentioned, albeit casually, in the release notes. I'm currently working on the support for it. It will be available in 3.1 and is being tracked by #413.

In case you're wondering, the main reason for not immediately supporting it at the release of 3.0 is because _Endpoint Routing_ will require using ASP.NET Core 2.2+. The 3.0 release still allows you to go back to ASP.NET Core 1.0. The planned release is by the end of January, but things are progressing very well and I expect it to be much earlier than that. I'm nearly down to just testing, but there is a large test matrix to replicate for _Endpoint Routing_ because the _legacy_ routing system is still supported.

So, with 3.0, if we are on .NET Core 2.2, it will be better adding EnableEndpointRouting = false as most stuffs are broken because of this _Endpoint Routing_.

If you want to use other 2.2 specific features (a la the _compatibility switch_), then - yes.

There's obviously a lot of interest in the new 2.2 features, but I was already in the middle of flushing out the 3.0 release. Unfortunately, I just need have the capacity to get everything done ahead of time. In addition, the requirement to update to 2.2 drove me to have a 3.0 release that still uses 2.0 which is closely followed up by 3.1 which does.

I'll notify all open issues once the beta is ready. Thanks for your patience.

3.1 has been published and includes support for _Endpoint Routing_. All of the sample applications have been updated to use _Endpoint Routing_ by default as well. Thanks.

@commonsensesoftware Seems like 3.1 doesn't fix this issue.
I upgraded from NuGet to <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="3.1.0" />
Turn back _Endpoint Routing_ on again by removing opt => opt.EnableEndpointRouting = false
RedirectToAction throw No route matches the supplied values. again.

Disable _Endpoint Routing_ will again fix this issue.

Can you share your setup and/or repro? The sample apps and tests have been updated to use CreatAtAction and they all work fine.

The problem with my case is with RedirectToAction, not sure if it is related to CreatAtAction

Here is a quick repo I just created with .NET Core 2.2 API template and Versioning 3.1.0
https://github.com/jasonycw/TestRedirectToAction

I added a TestController to redirect to another API in ValueController
``` C#
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace TestApi.Controllers
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
public class TestController : Controller
{
[HttpGet]
public ActionResult> Get()
=> RedirectToAction("Get", "Values");
}
}

With the following line, RedirectToAction will throw `InvalidOperationException: No route matches the supplied values.`
https://github.com/jasonycw/TestRedirectToAction/blob/master/TestApi/Startup.cs#L18

If I change that to 
``` C#
.AddMvc(opt => opt.EnableEndpointRouting = false)

/api/v1/test will redirect to /api/v1/value

I suspect this is because the version route parameter has not been specified. The legacy routing system used existing, matching route parameters, but Endpoint Routing doesn't. Something like:

c# [HttpGet] public ActionResult<IEnumerable<string>> Get(ApiVersion apiVersion) => RedirectToAction("Get", "Values", new { version = apiVersion.ToString() });

3.0 introduced model binding support for the ApiVersion. Be aware that cross-controller link generation like this needs to have congruent API version numbers.

Oh, so specifying the version do make it work, thanks!
``` C#
[HttpGet]
public ActionResult> Get()
=> RedirectToAction("Get", "Values", new { version = "1.0" });

But with this, that means I cannot reuse the route query values directly
``` C#
[HttpGet("redirect")]
public ActionResult<IEnumerable<string>> Redirect([FromQuery]Query q) 
    => RedirectToAction("Test", "Values", q);

and need to layout every parameter
``` C#
[HttpGet("redirect")]
public ActionResult> Redirect([FromQuery]Query q)
=> RedirectToAction("Test", "Values", new { version = "1.0", id = q.Id, ... });

Is it possible to add another attribute for specify the version instead of binding it to the `routeValues`, like
``` C#
RedirectToAction("Search", "Users", query, version: "1.0");

If not, we will need to think of how to pass our query model and handle versioning properly 馃

The ApiVersion is _special_ as it relates to model binding because it can come from multiple places, even within the same request. The value is ultimately provided by the IApiVersioningFeature. There are a couple of other ways you can get at this value too.

  • Request.HttpContext.GetRequestedApiVersion()
  • HttpContext.Features.Get<IApiVersioningFeature>().RequestedApiVersion

The following _union_ will work for your scenario:

c# [HttpGet("redirect")] public ActionResult<IEnumerable<string>> Redirect([FromQuery]Query q, ApiVersion apiVersion) { var routeValues = new RouteValueDictionary(q) { ["version"] = apiVersion.ToString() }; return RedirectToAction("Test", "Values", routeValues); }

There _might_ be a way to hook into or otherwise extend the URL generation mechanism to automatically inject the route value when available. Keep in mind the following:

  1. The version of requested API and the linked API need to match
  2. The route parameter names always need to be the same (e.g. version)

If those are wrong, link generation may fail or produce the incorrect result.

I hope that helps.

Was this page helpful?
0 / 5 - 0 ratings