Given this route:
routes.MapRoute("home", "{action}/{id}", new { controller = "home", action = "index", id = UrlParameter.Optional });
... and this action:
public ActionResult Hi(string id) { return Content("hello, id: " + id); }
Question No. 1 What is the answer for:
GET http://localhost:2247/hi/7?id=55 HTTP/1.1
Question No. 2 What is the answer for:
POST http://localhost:2247/hi/7?id=55 HTTP/1.1 Content-Length: 4 Content-Type: application/x-www-form-urlencoded id=3
<h / "> I believe that this is a mistake and the route value should always take precedence, since the URL is what the resource identifies. If you write POST, PUT or DELETE, you expect the identifier to come from the URL addresses, not from the request body, which can lead to changes on another resource and can be used by cybercriminals.
After some research, it turned out that the problem is a standard ValueProviderFactory registration order, where FormValueProviderFactory precedes RouteDataValueProviderFactory. Instead of messing with order, I created a CustomModelBinderAttribute:
[AttributeUsage(AttributeTargets.Parameter)] public sealed class FromRouteAttribute : CustomModelBinderAttribute, IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { bindingContext.ValueProvider = new RouteDataValueProvider(controllerContext); return ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext); } public override IModelBinder GetBinder() { return this; } }
... which you can use as follows:
public ActionResult Hi([FromRoute]string id) { return Content("hello, id: " + id); }