asp-action tag helper doesn't work with Route attribute on controller - .net-core-3.1

I tried to do some simple thing. I wanted to have different action name and different method name:
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
}
Look that there is no Route attribute on the controller.
In such case tag helper asp-action works perfectly. But I thought that my action dosth would be placed in: localhost/Super/dosth
But it was not. So I figured it out that I probably should set the route for the whole controller, like this:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
But now asp-action stopped working. For example:
<a asp-controller="Super" asp-action="Register">
creates anchor to: localhost/Super and not to: localhost/Super/Register
When I remove Route tag from controller it works again.
My mappings are configured as standard says:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
So, how come asp-action does not work when there is a Route attribute on the whole controller

Applying [Route] attribute on a controller enables attribute routing for all controller methods. So, by doing so you force yourself to provide the route for every method (in one way or another).
With [Route("[controller]")] the base route template for your controller actions is just the controller name, so if you have multiple actions without [Route] or [HttpGet] (and other HTTP verbs) attributes applied on them:
[Route("[controller]")]
public class SuperController: Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
...you get yourself a AmbiguousMatchException because multiple controller actions will be matching same route:
/Super
You can either explicitly specify the route for every action:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
[HttpGet("Register")]
public IActionResult Register()
{
return View();
}
}
or specify action name as an part of expected route already on controller level:
[Route("[controller]/[action]")]
public class SuperController : Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
then, you don't have to specify the routes for actions, because you applied a route template on a controller level. Your actions will inherit that route template.
But, be aware that in order to overwrite it you'll have to overwrite it all, otherwise you'll append to the route template.
[HttpGet("/[controller]/dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
Read more about routing in official documentation.

Related

how to set up url routing with sub-paths

i am new to webapi and MVC in general. If I wanted to group my service URLs like this
/api/account/create
/api/account/login
/api/account/resetpass
Am I able to put all 3 method calls in the same controller file and somehow map a particular request to the right method?
Create a Controller named Account and Create 3 [GET, POST, PUT, DELETE] method and name them create , login ,resetpass.
By Default, this is the routing for MVC / API(Id can be optional)
route Template: "api/{controller}/{id}",
Example :
public class AccountController : ApiController
{
[HttpPost]
public string Create()
{
// CODE
}
[HttpPost] // or [HttpGet]
public string Login ()
{
// CODE
}
[HttpPost]
public string Resetpass()
{
// CODE
}
}
if you had trouble calling them, try to give them a specific route :
[HttpGet("GetSubject/{subject}")]
public int GetSubjectId(String subject)
{
//CODE
}
Please if you get any error or misunderstanding, don't hesitate to post a comment

Is it possible to have two controllers with the same route?

Is it possible to have two controllers in two assemblies with the same Route prefix attribute, but different Route attributes on the actions?
[RoutePrefix("api/route")]
public class Controller1 : ApiController
{
[Route("action1")]
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] string body)
{ }
[Route("{id}")]
public async Task<HttpResponseMessage> Delete(string id)
{ }
}
[RoutePrefix("api/route")]
public class Controller2 : ApiController
{
[Route("action2")]
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] string body)
{ }
}
This is possible. What was my problem was that the first controller had defined a DELETE action with route "{id}". Since id was an unconstrained parameter Web Api could not see the difference between "api/route/action2" and "api/route/idtodelete".
I ended up creating a regex constraint on the delete which excludes "action2" and allows all alpanumeric characters. Now it works.
[HttpDelete]
[Route("{id:regex(^(?!action2)[a-zA-Z0-9]*$)}")]
If you are use the different Route for all action method than it will be working. But if you are using the same Route prefix with same Route action than it will give an error.

Web Api Core 2 distinguishing GETs

Why can't Web API Core 2 tell these apart?
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values?name=dave
[HttpGet]
public string Get(string name)
{
return $"name is {name}";
}
Here's what happens -
Both http://localhost:65528/api/values and http://localhost:65528/api/values?name=dave cause the first Get() method to execute.
This exact code works fine in Web Api 2.
I know multiple ways of getting around this, but I don't know why it happens.
Can someone explain why this has changed?
I don't think you can even compile your code in ASP.NET Core Mvc 2.0 since you have 2 actions mapped to same route [HttGet] api/values:
AmbiguousActionException: Multiple actions matched.
Remember, ASP.NET Web API uses the HTTP verb as part of the request to figure which action to call. Although it uses conventional routing (you name your actions Get, Post, Put and Delete, etc) if you don't have route attribute specify, I would highly recommend to always use routing attribute to annotate your controllers and actions.
Api Design time
Now it's up to you to design the route, as a developer. Remember the route is supposed to be a Uri that can identify a resource / resources.
Use the name as identifier along with the route
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")] // constraint as a string
public IActionResult GetByName(string name)
{
...
}
}
Use the name as filter, against the resource collection
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
// api/customers?name=dave
[HttpGet]
public IActionResult Get(string name)
{
...
}
}
To confuse you more
api/customers/dave will still execute GetById first!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
...
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
...
}
}
Both methods GetByName and GetById are potential candidates but MVC picks GetById method first because MVC compares the method/template name {name} and {id} through case-insensitive string comparison, and i comes before n.
That's when you want to put constraints.
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
You can also specify the Ordering too!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/portland
[HttpGet("{city:alpha}", Order = 2)]
public IActionResult GetByCity(string city)
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}", Order = 1)]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
Without the Order, the method GetByCity will be in favor than GetByName because character c of {city} comes before the character n of {name}. But if you specify the order, MVC will pick the action based on the Order.
Sigh the post is too long....
Because in your case the best match in the route pipeline is the default httpget attribute (the one that get all). The query is a regular string so if you don't ask what you want from the query the best match is still the one that get all.
[HttpGet]
public string Get([FromQuery]string name)
{
return $"name is {name}";
}
The [FromQuery] is pointing to the key "name" in the query string to get the value.
Your should read Routing in asp.net core

Override controller Authorize attribute for viewresult

If the authorize attribute has been applied to the controller is it possible to allow unauthorized access to an action/viewresult inside that controller?
Say for example I didn't want authorization to occur on Test2 in the following:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
public ViewResult Test2()
{
return View();
}
}
Thanks in advance.
No, this is not possible. You will have to apply the Authorize attribute on the Test action and not on the controller. Another possibility is to put the Test2 action on another controller which is not decorated with this attribute.
Back in MVC 3 it appears it was indeed not possible to do (as mentioned Darin Dimitrov), but if anyone using MVC 4 (and up) comes across this question, he\she should be able to use AllowAnonymous filter to achieve the result. So the code would become:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
[AllowAnonymous]
public ViewResult Test2()
{
return View();
}
}

Switch off request validation in the view

is it possible to switch request validation in the view.
I would like to switch it off in the view because of this bit of code in the base controller
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
if (!this.CurrentStore.IsDefault)
{
IStoreRepository storeRepo = ObjectFactory.GetInstance<IStoreRepository>();
IStoreEntity store = storeRepo.GetById(this.CurrentStore.Id);
this.CurrentStore = store;
}
base.Execute(requestContext);
}
it fails in base.Execute(requestContext);
N.B I do not wish to switch it off for my entire site. I would like to switch it off in a few action methods.
In MVC you don't do it at the view level, you do it at the controller or a method of the controller level. You can use ValidateInput attribute for that, for example:
public class HomeController : Controller
{
[ValidateInput(false)] // prevent validation on this method
public ActionResult Index()
{
return View();
}
}

Resources