3

I have ASP.net Web Api project and I decided that it was time to support versioning. I am using official Microsoft Nuget to support versioning (more info here), and I decided to version by namespace (as exampled here).

Unfortunately I cannot get code to work. If I call my method like this:

http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0

I get error:

Multiple types were found that match the controller named 'Saved'. This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.

And if I call it like this: http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser

I get error 404:

The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

I am not sure what I am doing wrong. Here is my code:

Startup.cs

public void Configuration(IAppBuilder app)
    {
        var configuration = new HttpConfiguration();
        var httpServer = new HttpServer(configuration);


        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
        configuration.AddApiVersioning(o => 
        {
            o.AssumeDefaultVersionWhenUnspecified = true;
            o.ReportApiVersions = true; 
            o.DefaultApiVersion = ApiVersion.Default;
        });


        configuration.Routes.MapHttpRoute(
            "VersionedUrl",
            "v{apiVersion}/{controller}/{action}/{id}",
            defaults: null,
            constraints: new { apiVersion = new ApiVersionRouteConstraint() });

        configuration.Routes.MapHttpRoute(
            "VersionedQueryString",
            "api/{controller}/{action}/{id}",
            defaults: null);


        app.UseWebApi(httpServer);

        ConfigureAuth(app);
    }

Saved Controller (v1)

namespace Master.Infrastructure.Api.Controllers
{

    [Authorize]
    [RoutePrefix("api/Saved")]
    [ApiVersion("1.0")]
    public class SavedController : ApiController
    {

        private readonly IUserService _userService;

        public SavedController(IUserService userService)
        {
            _userService = userService;

        }


        [HttpGet]
        [ActionName("GetNumberOfSavedWorkouts")]
        public async Task<NumberOfSavedWorkouts> GetNumberOfSavedWorkouts()
        {
            var numOfSavedWorkouts = new NumberOfSavedWorkouts
            {
                CurrentNumberOfSavedWorkouts =
                    await _userService.GetNumberOfSavedWorkoutsForUserAsync(User.Identity.GetUserId())
            };

            return numOfSavedWorkouts;
        }

    }
}

Saved Controller (v2)

namespace Master.Infrastructure.Api.V2.Controllers
{
    [Authorize]
    [ApiVersion("2.0")]
    [RoutePrefix("v{version:apiVersion}/Saved")]
    public class SavedController : ApiController
    {

        private readonly ISavedWorkoutService _savedWorkoutService;


        public SavedController(ISavedWorkoutService savedWorkoutService)
        {       
            _savedWorkoutService = savedWorkoutService;
        }


        [ActionName("GetNumberOfSavedWorkoutsForUser")]
        public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
        {
            var cnt = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(User.Identity.GetUserId());

            return Ok(cnt);
        }
    }
}

1 Answer 1

8

Your routes are incorrect. I strongly discourage you from mixing routing styles unless you really need to. It can be very difficult to troubleshoot.

There are several things going on here:

  1. You have configurations to version both by query string and URL segment, which one do you want? I would choose only one. The default and my recommendation would be to use the query string method.
  2. Your convention-based route is different from the attribute-base route
  3. Since you have RoutePrefixAttribute defined, it appears you prefer the attribute-routing style. I would remove all convention-based routes (ex: configuration.Routes.MapHttpRoute).

In your convention, the route template:

v{apiVersion}/{controller}/{action}/{id}

but in your attribute it's:

api/Saved

Neither of these will match your expected routes:

http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser

For the query string method using route attributes, things should look like:

configuration.AddApiVersioning(o => o.ReportApiVersions = true);

namespace Master.Infrastructure.Api.Controllers
{
    [Authorize]
    [ApiVersion("1.0")]
    [RoutePrefix("api/Saved")]
    public class SavedController : ApiController
    {
       private readonly IUserService _userService;
       public SavedController(IUserService userService) => _userService = userService;

       [HttpGet]
       [Route("GetNumberOfSavedWorkouts")]
       public async Task<IHttpActionResult> GetNumberOfSavedWorkouts()
       {
            var userId = User.Identity.GetUserId();
            var count = await _userService.GetNumberOfSavedWorkoutsForUserAsync(userId);
            return Ok(new NumberOfSavedWorkouts(){ CurrentNumberOfSavedWorkouts = count });
       }
    }
}

namespace Master.Infrastructure.Api.V2.Controllers
{
    [Authorize]
    [ApiVersion("2.0")]
    [RoutePrefix("api/Saved")]
    public class SavedController : ApiController
    {
       private readonly ISavedWorkoutService _savedWorkoutService;
       public SavedController(ISavedWorkoutService savedWorkoutService) => _savedWorkoutService = savedWorkoutService;

       [HttpGet]
       [Route("GetNumberOfSavedWorkoutsForUser")]
       public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
       {
            var userId = User.Identity.GetUserId();
            var count = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(userId);
            return Ok(count);
       }
    }
}

The following should then work:

http://localhost:7291/api/Saved/GetNumberOfSavedWorkouts?api-version=1.0 http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0

I hope that help.s

Sign up to request clarification or add additional context in comments.

3 Comments

Just realized I forgot one more thing. You also need this in your configuration for using attribute-base routing: configuration.MapHttpAttributeRoutes()
You must add this nuget package Microsoft.AspNet.WebApi.Versioning
@PauloAbreu that was stated in the OP; however, that is no longer the correct package. It has been replaced by Asp.Versioning.WebApi. Both packages will state that in the README on the NuGet site

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.