Webapi 2 Attribute Routing - c#

I have the following routes set up in a user controller:
[EnableCors("*", "*", "*")]
[RoutePrefix("api/users")]
[Authorize]
public class UserController : ApiController
{
[Route("")]
public IHttpActionResult Get()
{
}
[HttpGet]
[Route("{id:int}")]
public IHttpActionResult Get(int id)
}
[HttpPost]
[Route("validateUser")]
public IHttpActionResult ValidateUser()
{
}
[HttpPost]
[Route("verify/{identityId}/{emailAddress}")]
public void VerifyUserEmailAddress(string identityId, string emailAddress)
{
}
}
The first three routes work just fine. But the fourth fails with a 404. I'm using fiddler to make the call:
http://localhost:39897/api/users/verify/asldkfj/jb#test.com (post is selected)
Does post require data sent in the body? Can anyone see what I'm doing wrong and why the verify route is not being found?

The .com in the email is the issue.
Sure its a valid email, but IIS treats requests with file extensions as actual file requests and tries to find it on disk. When it can't find it then you get the 404 Not Found
If you add a trailing slash / to the request it should work.
ie http://localhost:39897/api/users/verify/asldkfj/jb#test.com/

Related

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.
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}");
}
}
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}");
}
}
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.

Getting 404 when calling a post method on Web API

I have an API controller which have standard GET,POST and Delete actions.
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
//Get
[HttpPost]
public async Task Post([FromBody] TestUser testUser, string tempPassword, role = "Guest")
{
}
}
Now I am adding a new action using:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post(string email, List<string> roles)
{
}
When I am trying to call the API using postman ,
Type : POST
Endpoint : http://localhost/api/users/UpdateRole
Request body:
{
"email":"something#mail.com",
"roles":["S1","s3"]
}
But I am getting a 404 as response back. On server I can see ,
the application completed without reading the entire request body.
It seems that your overall route is /api/Users/api/Users/UpdateRoute because of how RouteAttribute works.
[Route("a")]
public class MyController
{
[Route("a/b")]
public IActionResult MyAction()
{
}
}
The above will have a route of /a/a/b because the action route is appended to the controller route in this case.
Your options are:
Change the controller route to [Route("[controller]/[action]")] and remove the action route, in which case the example above would become /MyController/MyAction
Change the action route to simply [Route("b")], in which case the full route would be a/b
Use an absolute path for the action route [Route("/a/b")], in which case the controller route would be ignored and the full route will simply be /a/b.
See here for more information about routing.
As for your issue with null values, ASP.NET Core is currently expecting email and roles as querystring parameters. Instead, you should create a model for your request body:
public class MyModel
{
public string Email { get; set; }
public List<string> Roles { get; set; }
}
And then change your action to accept it:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post([FromBody]MyModel model)
{
}

How can i make routing detect a subroute with routeprefix?

I have a Web API action method like this
[HttpPost]
[Route("{idFlux}/Transfert")]
public IHttpActionResult Post(int idFlux, Transfert parametre)
The controller has a prefix of
[RoutePrefix("api/v1/Flux")]
localhost/api/v1/Flux/59 works, but localhost/api/v1/Flux/59/Transfert is not working, and I think everything is set right.
Just add both acceptable routes to action
[HttpPost]
[Route("{idFlux}/Transfert")]
[Route("{idFlux}")]
public IHttpActionResult Post(int idFlux, Transfert parametre)

Can't map route to action, ASP.NET Core Web API

I am working on Web API project and have the following problem:
I have tried to call the action method called 'GetUserBy' with the following Url (https://localhost:44328/api/Users/GetUserBy?username=myusername&password=mypassword), but the result I received in the browser looks like this:
{"id":["The value 'GetUserBy' is not valid."]}
Below is my UsersController:
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
// GET: api/Users
[HttpGet]
public IEnumerable<User> GetUsers()
{
//this works
//code removed for simplicity
}
//GET: api/Users/5
[HttpGet("{id}")]
public async Task<IActionResult> GetUser([FromRoute] int id)
{
//this works too
}
[HttpGet("Users/GetUserBy")]
public async Task<IActionResult> GetUserBy([FromQuery]string username, [FromQuery]string password)
{
//this doesn't work
}
}
when I insert the breakpoint on this method, code execution never seems to come there regardless I call it or not.
I added the following code in startup.cs file, but nothing has changed.
app.UseMvc(
routes =>
{
routes.MapRoute("GetUserBy", "{controller=Users}/{action=GetUserBy}");
}
);
I have also visited the following web page, but I can't find the answer.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.1
try changing your Tag from this:
[HttpGet("Users/GetUserBy")]
to this:
[HttpGet("GetUserBy")]
you already have it routing to the Controller Users
You are experiencing route conflicts.
api/Users/GetUserBy
matches this route
//GET: api/Users/5
[HttpGet("{id}")]
public async Task<IActionResult> GetUser([FromRoute] int id)
{
//this works too
}
but it is treating the GetUserBy string in the URL as the {id} in the route template.
Since "GetUserBy" is not an int you get that invalid value error message.
add a route constraint so that it will only match for an integer.
//GET: api/Users/5
[HttpGet("{id:int}")]
public async Task<IActionResult> GetUser([FromRoute] int id) {
//...
}
The current GetUserBy action has Users/GetUserBy as its route template, which would resolve to api/Users/Users/GetUserBy given the current api/[controller] route template on the controller.
Consider using the action token to get the desired behavior.
Here is the completed code with the changes suggested above.
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase {
// GET: api/Users
[HttpGet]
public IEnumerable<User> GetUsers() {
//...
}
//GET: api/Users/5
[HttpGet("{id:int}")]
public async Task<IActionResult> GetUser([FromRoute] int id) {
//...
}
//GET: api/Users/GetUserBy
[HttpGet("[action]")]
public async Task<IActionResult> GetUserBy([FromQuery]string username, [FromQuery]string password) {
//...
}
}
Reference Routing to controller actions in ASP.NET Core

ASP.NET Core Middleware

I have build custom Identity Middle-ware. Inside Invoke method I am checking if request has token etc. After token is checked I want to pass request further to controller. It`s working for GET request - it jump into controller method. It is not working for POST request.
Here is Invoke Method
public async Task Invoke(HttpContext context)
{
//checking
await _next(context);
}
Its working controller:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[AllowAnonymous]
[HttpGet]
public string Get()
{
return "Allow annymous";
}
}
And not working one
[Route("api/[controller]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost]
public void Login()
{
//some logic
HttpContext.Response.WriteAsync("Unauthorized");
}
}
Making POST call Postman returns 404 not found.
The route for the action in question would be /api/Account. Make sure that is correct.
If instead you wanted /api/Account/Login:
[Route("api/[controller]/[action]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost]
public void Login()
{
//some logic
HttpContext.Response.WriteAsync("Unauthorized");
}
}
Try returning something like an IActionResult rather than simply void.
[AllowAnonymous]
[HttpPost]
public IActionResult Login()
{
// some logic
return Unauthorized();
}
Looking at your code I hopping there is only one method inside the AccountController called Login. If not please add attribute [Route("Login")] to Login method (make sure you do not keep it empty like [Route("Login")] otherwise it will have same as what you are doing currently).
Then make the call to the POST http://host:***/api/account/login url and that should work.
FYI: I have tried this thing on my machine and it is working.

Categories

Resources