I have an interesting situation that has me stumped.
It seems that posting appliction/json
content type makes the basic routing engine unable to bind action method arguments.
Using the default route:
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I have an action method that looks like this:
//Controller name: TestController
[HttpPost]
public ActionResult SaveItem(int id, JsonDto dto)
{
// if content type of the request is application/json id = 0, JsonDto is populated
// if content type of the request is application/x-www-form-urlencode id = 1
}
I am posting to this url /Test/SaveItem/1
+ the json object.
The reason that I need to id
and the JsonDto
is that the id
argument references the parent object that the JsonDto
object need to releate to.
I suppose I could change the dto to contain the parent id as a property and work around this whole problem.
It just strikes me as strange that the id
argument does not get populated when I post a application/json
request.
OK, you haven't shown how you are calling this action so we can only be guessing here. Here's an example of something that works perfectly fine for me and gets everything populated as expected in the SaveItem
method:
Model:
public class JsonDto
{
public string Foo { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult SaveItem(int id, JsonDto dto)
{
return Content("success", "text/plain");
}
}
Index View:
<script type="text/javascript">
$.ajax({
url: '@Url.Action("SaveItem", new { id = 123 })',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
foo: 'bar'
}),
success: function (result) {
// TODO: handle the results
}
});
</script>
I have figured out my problem.
The problem is that the Json data that is being posted to the action method includes an Id
property, in addition to the id
route value from the default route. So when binding the JSON
object, its Id
property wins over the route value in the URL. So to tweak Darin's example:
<script type="text/javascript">
$.ajax({
url: '@Url.Action("SaveItem", new { id = 123 })',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
"Id": 456
}),
success: function (result) {
// TODO: handle the results
}
});
</script>
When the action method exectures, the int id
argument contains 456 and not 123 as I
(apparently mistakenly) expected.
So the workaround for me was to change my default route to:
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{urlId}", // URL with parameters
new { controller = "Home", action = "Index", urlId = UrlParameter.Optional } //
);
Renaming the default id
route parameter to urlId
and updating my action methods solved the
conflict for me.