Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core routing engine confusion

I am trying to create a solid example of exactly how the ASP.NET Core routing engine works and I was surprised by the results.

The premise behind this example is hitting a controllers index page, and then using AJAX requests to load data.

I created a ASP.Net Core application with MVC. Then I added the following Controller:

namespace WebApplication2.Controllers {
    using Microsoft.AspNetCore.Mvc;

    public class SearchController : Controller {
        public IActionResult Index() {
            return View();
        }

        [HttpGet("{company}")]
        public IActionResult Get(string company) {
            return Ok($"company: {company}");
        }

        [HttpGet("{country}/{program}")]
        public IActionResult Get(string country, string program) {
            return Ok($"country: {country} program: {program}");
        }
    }
}

I also create a simple View to go along with Index with the words "Search Page" so that you can see it get called.

The problem is that the routes that are created from this don't make sense.

Expected Results

  • /Search/Index
  • /Search/{company}
  • /Search/{country}/{program}

Using Company: "Abc", Country: "Canada" and Program: "Plumbing" as an example:

  • /Search/Index

    Produces: "Search Page"

  • /Search/Abc

    Produces "company: Abc"

  • /Search/Canada/Plumbing

    Produces: "country: Canada program: Plumbing"

Actual Results

However it doesn't work like this at all. Instead these are the results:

  • /Search/Index

Produces: "country: Search program: Index"

  • /Search/Abc

Produces: "country: Search program: Abc"

  • /Search/Canada/Plumbing

Produces: 404 Not Found

Clearly the route for Index and Get string company are confused, and it is treating the Controller name as a parameter.

I can make it work with the following code, but I thought the routing engine would produce the same results:

    public class SearchController : Controller {
        public IActionResult Index() {
            return View();
        }

        [HttpGet("[controller]/{company}")]
        public IActionResult Get(string company) {
            return Ok($"company: {company}");
        }

        [HttpGet("[controller]/{country}/{program}")]
        public IActionResult Get(string country, string program) {
            return Ok($"country: {country} program: {program}");
        }

What is wrong with my understanding? It seems silly to have to specify [controller] explicitly.

like image 866
Jeff Avatar asked Sep 13 '19 20:09

Jeff


3 Answers

You have mixed the conventional routing and the attribute routing while you should not do that.

For your original code, it will work when you remove all the /Search in your url.

To use the controller name, you need to set [Route("[controller]")] on your mvc controller to make your url work as expected.

[Route("[controller]")]
public class SearchController : Controller
{
    [HttpGet("[action]")]
    public IActionResult Index()
    {
        return View();
    }

    [HttpGet("{company}")]
    public IActionResult Get(string company)
    {
        return Ok($"company: {company}");
    }

    [HttpGet("{country}/{program}")]
    public IActionResult Get(string country, string program)
    {
        return Ok($"country: {country} program: {program}");
    }
}
like image 194
Ryan Avatar answered Oct 13 '22 16:10

Ryan


Specify the root route above your class

[Route("Search")]
public class SearchController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [HttpGet("{company}")]
    public IActionResult Get(string company)
    {
        return Ok($"company: {company}");
    }

    [HttpGet("{country}/{program}")]
    public IActionResult Get(string country, string program)
    {
        return Ok($"country: {country} program: {program}");
    }
}
like image 40
DetectivePikachu Avatar answered Oct 13 '22 15:10

DetectivePikachu


Routing is responsible for mapping request URL to an endpoint and it comes with two types of Conventional and Attributes routing.

And from your question, you are expecting conventional routing with default route which you can achieve in .NET CORE using below line of code.

app.UseMvc(routes =>
{
    routes.MapRoute("default", "{controller=Search}/{action}/{id?}");
});

Note: But keep in mind that conventional routing will not work if you decorate your controller with [ApiController] attribute.

By default .NET CORE supports attribute routing so you have to prefix the route by placing the [Route] attribute on the controller level. Please see the below example

[Route("api/[controller]")]
    [ApiController]
    public class SearchController : ControllerBase
    {
        
    [HttpGet("{company}")]
    public IActionResult Get(string company) {
        return Ok($"company: {company}");
    }

    [HttpGet("{country}/{program}")]
    public IActionResult Get(string country, string program) {
        return Ok($"country: {country} program: {program}");
    }
   }

The above code will work as you expected.

If you are decorating your controller by [ApiController] attribute then you have to use Attribute routing and any conventional routing defined in startup class will be overridden. Please see more details here.

like image 45
Mahesh More Avatar answered Oct 13 '22 15:10

Mahesh More