3

I use Web API 2. I provide clients methods in version1:

http://localhost/version1/api/base 
http://localhost/version1/api/values

It is mine controller:

[RoutePrefix("version1")]
public class ValuesController : ApiController
{
    [Route("api/base")]
    public string GetBaseMethod()
    {
        return "bbb";
    }

    [Route("api/values")]
    public string GetVersion1()
    {
        return "aaa";
    }   
}

Now I would like to provide clients in version2 methods:

  • /version2/api/base (method the same like in version1)
  • /version2/api/values (method has changed, now it returns int, not string but address must be the same like in version1: /api/values)

So I have the second controller with inheritance:

[RoutePrefix("version2")]
public class Values2Controller : ValuesController
{
    [Route("api/values")]
    public int GetVersion2()
    {
        return 5;
    }
}

I have enabled attribute routes inheritance in WebApiConfig.cs:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

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

public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
    {
        return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(inherit: true);
    }
}

Result:

http://localhost/version1/api/base

  • it works.

http://localhost/version2/api/base

  • it works.

http://localhost/version1/api/values

  • it works.

http://localhost/version2/api/values

  • it DOESN”t work. I have error:

{ "Message": "An error has occurred.", "ExceptionMessage": "Multiple actions were found that match the request: \r\nGetVersion2 on type WebApplication1.Controllers.Values2Controller\r\nGetVersion1 on type WebApplication1.Controllers.Values2Controller", "ExceptionType": "System.InvalidOperationException", "StackTrace": " w System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n w System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n w System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n w System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()" }

What can I do?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
user3691221
  • 109
  • 2
  • 13
  • Hi, have you found any solution for the problem? – tenbits Nov 26 '15 at 10:53
  • I am facing the same issue...Is there a solution for this problem? – Gaurav Aug 08 '16 at 14:07
  • I believe there are several issues: 1. You should have the base class have it's method be virtual so you can override it in classes that implement the base 2. You should only have the `Route` attribute on the base class, specifying it again in the class that implements the base causes the conflict. – Ben Black Sep 13 '18 at 14:33

2 Answers2

2

I was able to recreate your problem. Playing around with different variations I was able to get it to work only when I made the original GetVersion1 virtual, changed the return type to a more general object and then overriding it in inherited classes.

Like so:

[RoutePrefix("version1")]
public class ValuesController : ApiController {
    [Route("api/base")]
    public string GetBaseMethod() {
        return "bbb";
    }

    [Route("api/values")]
    public virtual object GetVersion1() {
        return "aaa";
    }
}

[RoutePrefix("version2")]
public class Values2Controller : ValuesController {

    public override object GetVersion1() {
        return 5;
    }
}

Once there were conflicting Route attributes and route inheritance enabled it failed just as you indicated.

That got me thinking. If you're going to use inheritance then why not just treat it as such with the routes as well. So I eventually settled on the following given your example:

[RoutePrefix("version1")]
public class ValuesController : ApiController {
    [Route("api/base")]
    public virtual HttpResponseMessage GetBaseMethod() {
        return Request.CreateResponse("bbb");
    }

    [Route("api/values")]
    public virtual HttpResponseMessage GetVersion1() {
        return Request.CreateResponse("aaa");
    }
}

[RoutePrefix("version2")]
public class Values2Controller : ValuesController {

    public override HttpResponseMessage GetVersion1() {
        return Request.CreateResponse(5);
    }
}

which would allow me to change the underlying returned value in future versions without route conflicts.

Result:

http://localhost/version1/api/base

  • it works. => "bbb"

http://localhost/version2/api/base

  • it works. => "bbb"

http://localhost/version1/api/values

  • it works. => "aaa"

http://localhost/version2/api/values

  • it works. => 5
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    Also important to note is that you only specify the `Route` attribute on the base class's methods that are virtual. Specifying them on the class that overrides them causes conflicts. – Ben Black Sep 13 '18 at 14:36
  • How to stop access GetBaseMethod from version2? I want to let user know that this method is not available on version2. – uzay95 Dec 08 '19 at 09:16
-1

Multiple actions were found that match the request: webapi

I belive this is the same problem. You need "api/{controller}/{action}/{id}"

Community
  • 1
  • 1
Medo
  • 968
  • 1
  • 11
  • 26