Reputation: 12739
I can get model binding to work fine without attribute routing - eg:
/// <summary>
/// Collect user details
/// </summary>
public IActionResult RegisterDetails(Guid CustomerId)
{
var Details = new Details()
{
CustomerID = CustomerId
};
return View(Details);
}
/// <summary>
/// Save user details in Db if valid
/// </summary>
[HttpPost]
public IActionResult RegisterDetails(Details Details)
{
if (ModelState.IsValid)
{
// Do stuff
}
// Error, return ViewModel to view
return View(RegisterDetails);
}
But I'm not sure how to pass the model to the method that handles it. When I submit the form it runs the original method, not the one under [HttpPost] - it keeps posting to the original method again and again (where // Do stuff is - when I do this:
/// <summary>
/// Collect user details
/// </summary>
[Route("Register/Details/{CustomerId}")]
public IActionResult RegisterDetails(Guid CustomerId)
{
var Details = new Details()
{
CustomerID = CustomerId
};
return View(Details);
}
/// <summary>
/// Save user details in Db if valid
/// </summary>
[HttpPost]
[Route("Register/Details")]
public IActionResult RegisterDetails(Details Details)
{
if (ModelState.IsValid)
{
// Do stuff
}
// Error, return ViewModel to view
return View(RegisterDetails);
}
How can I bind the model correctly when using attribute routing?
I searched Google - I found things that didn't help, eg this: https://www.red-gate.com/simple-talk/dotnet/asp-net/improved-model-binding-asp-net-core/
thx
Update
I've also noticed the CustomerId is being appended to the Url, even after the form has been posted. I don't think this happened in MVC 5 and don't require this, the CustomerId is hidden in the page.
How can I remove this (it's causing the route to not match the [HttpPost] decorated method.
Upvotes: 1
Views: 2004
Reputation: 11
try to use FromBody attribute.
public IActionResult RegisterDetails([FromBody]Details Details)
Upvotes: 0
Reputation: 239250
The issue is two-fold. First, you have your RegisterDetails(Guid CustomerId)
action with the route set via the Route
attribute. This opens it up to accept all the HTTP verbs: GET, POST, etc. Second, although you did not post your view, I can assume that you likely have a form with an empty action, i.e.:
<form method="post">
...
</form>
That's going to default to posting back to the same page, which here in this scenario would /Register/Details/{CustomerId}
. Since that route does not match your action explicitly marked as HttpPost
and the action it does match accepts a POST (because of using Route
), the action that takes a customer id is called again. Since there's no code to actually handle a post in that, it simply reloads your page.
First and foremost, you should use the explicit HTTP verb attributes rather than Route
on your actions, i.e.:
[HttpGet("Register/Details/{CustomerId}")]
public IActionResult RegisterDetails(Guid CustomerId)
[HttpPost("Register/Details")]
public IActionResult RegisterDetails(Details Details)
Then, if you want to keep this route scheme, you need to be explicit with your form. However, since both actions have the same name, that's a bit difficult to do. ASP.NET Core is going to attempt to backfill the CustomerId portion of the route because it already exists in the route data. You have three options:
Use different action names. Then, you can explicitly bind your form to your post action:
[HttpPost("Register/Details")]
public IActionResult RegisterDetailsPost(Details Details)
Then:
<form asp-action="RegisterDetailsPost" method="post">
Employ route names:
[HttpPost("Register/Details", Name = "RegisterDetailsPost")]
public IActionResult RegisterDetails(Details Details)
Then:
<form asp-route="RegisterDetailsPost" method="post">
Don't make the customer id part of the initial route. If the two routes actually match, then the right one will be pull based on the HTTP verb in play. In other words:
[HttpGet("Register/Details")]
public IActionResult RegisterDetails(Guid CustomerId)
You can still supply a customer id, but you need to use the query string, i.e. /Register/Details?CustomerId=123456
. Generating the URL in your links and such will be no different. ASP.NET Core will attach any route data included that's no explicitly a route param as a query string param automatically. In other words, you can still just do something like:
<a asp-action="RegisterDetails" asp-route-customerId="123456">Click me</a>
Upvotes: 1
Reputation: 333
Normally, you can add the attribute "route" through the API Class controller as a base path and then adding parameters. I also tried to add always the verb for the controller for a better understanding...
[Route("Register/Details/")]
public class RegistrerExampleController : ControllerBase
{
/// <summary>
/// Collect user details
/// </summary>
[HttpGet("{CustomerId}", Name = nameof(RegisterDetails))]
public IActionResult RegisterDetails(Guid CustomerId)
{
var Details = new Details()
{
CustomerID = CustomerId
};
return View(Details);
}
/// <summary>
/// Save user details in Db if valid
/// </summary>
[HttpPost]
public IActionResult RegisterDetails(Details Details)
{
if (ModelState.IsValid)
{
// Do stuff
}
// Error, return ViewModel to view
return View(RegisterDetails);
}
}
Have you tried this?
Upvotes: 2