user3691221
user3691221

Reputation: 109

WebApi – controllers inheritance – new versions of methods

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:

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

http://localhost/version2/api/base

http://localhost/version1/api/values

http://localhost/version2/api/values

{ "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?

Upvotes: 3

Views: 3208

Answers (2)

Nkosi
Nkosi

Reputation: 247098

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

Upvotes: 2

Medo
Medo

Reputation: 967

Multiple actions were found that match the request: webapi

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

Upvotes: -1

Related Questions