1

My Controller:

[Route("Categories")]
[ApiController]
public class CategoriesController : ControllerBase
{
    [HttpGet]
    [Route("My")]
    public string[] My()
    {
        return new[]
        {
            "Is the Microwave working?",
            "Where can i pick the washing machine from?",
        };
    }
}

My startup.cs Configure():

app.UseMvc(routes =>
   {
       routes.MapRoute(name: "api", template: "api/v1/{controller}/{action}/");
   });

This works only if I hit url "https://localhost:44325/categories/my"

I need it to have "https://localhost:44325/api/v1/categories/my".

What should I set differently?

I tried something like [Route("[api]/Categories")] attribute on the controller to compose the desired route, but it did not work...

I got:

The following errors occurred with attribute routing information:

Error 1: For action: 'historyAccounts.WebApi.Controllers.CategoriesController.My (historyAccounts.WebApi)' Error: While processing template '[api]/Categories/My', a replacement value for the token 'api' could not be found. Available tokens: 'action, controller'. To use a '[' or ']' as a literal string in a route or within a constraint, use '[[' or ']]' instead.

2
  • is it not [Route("api/v1/[controller]")] ? Commented Jul 17, 2019 at 10:01
  • I want to avoid writing "api/v1" it explicitly in every controller, because when the time comes to change to v2, I will have to update ALL of them ... Commented Jul 17, 2019 at 11:56

3 Answers 3

3

Solution with Attribute Routing

Instead of using MapRoute in Startup create a custom base class -

[Route("api/v1/[controller]/[action]")]
[ApiController]
public class MyControllerBase : ControllerBase { }

Now derive all your controllers from this base class instead of ControllerBase -

public class CategoriesController : MyControllerBase
{
    [HttpGet]
    public string[] My()
    {
        return new[]
        {
            "Is the Microwave working?????",
            "Where can i pick the washing machine from?",
        };
    }
}

This way all your requirements are fulfilled -

  1. Your version information is in one place MyControllerBase
  2. Retained [ApiController] and hence all features it provides

Old Solution

Based on your requirement, if you simply remove Route attribute from your Controllers, it should work the way you want it -

public class CategoriesController : ControllerBase
{
   [HttpGet]
   public string[] My()
   {
       return new[]
       {
           "Is the Microwave working?",
           "Where can i pick the washing machine from?",
       };
   }
}

Keeping you Route map as is -

app.UseMvc(routes =>
{
   routes.MapRoute(name: "api", template: "api/v1/{controller}/{action}/");
});
  • If you notice, in addition to removing Route attributes I have also removed [ApiController]attribute from class. This attribute force user to use Route instead of Map routing.
  • Also, Attribute Routing has its own benefits. And as I mentioned earlier "Based on your requirement" if you are not using any of the features of Attributed Routing you can use this approach.
Sign up to request clarification or add additional context in comments.

4 Comments

If I remove Route attrs, I get Exception "Action '...My()' does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed."
@cnom I updated my answer, you have to remove [ApiController] attribute as well.
thank you, but removing [ApiController] attribute is not recommended.
Updated my answer with better one retaining [ApiController] :)
2

For MVC Conversation route and Attribute Route, attribute route will override the conversation route.

For the solution from @Gaurav Mathur, all the controller will need to append api/v1 to the request url.

Based on your scenario, if you want to enable version for api, you could try web api version feature instead of configuring the route in the mvc converstaion.

  1. Install package Microsoft.AspNetCore.Mvc.Versioning
  2. Configure in Startup.cs

    services.AddApiVersioning(opt => {
        opt.DefaultApiVersion = new ApiVersion(1,0); //this is your version v1
    });
    
  3. ApiController

    [Route("api/v{version:apiVersion}/[controller]/[action]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public IActionResult Hello()
        {
            return Ok("Test");
        }
    }
    
  4. Request Url:https://localhost:44369/api/v1/values/hello

2 Comments

but this cannot cover the case where we need to rename "api" part. i.e. /myapi/v1
@cnom If you prefer chaning other static string, kindly go with the suggestion from Gaurav Mathur
0

I already accepted a previous answer, but another possible approach is a global constant (i.e. declared on Startup):

public const string BaseUrl = "/api/v1/"; 

and

[Route(Startup.BaseUrl + "[controller]")]
[ApiController]
public class CategoriesController : ControllerBase

Comments

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.