I'm not sure if it will be helpful at all, but I mentioned in my comments above that I ended up using a custom model binder to do this. I dug up what I believe was the original code that prompted this question, and this is what I ended up with:
public async Task<ActionResult> Post(dynamic request)
{
return await ExecuteRequest(request, "application/json");
}
and a custom model binder as follows (it works on actions called "post" or "public" though you could choose your own convention - and falls back to default on all other actions)
public class MyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var actionName = controllerContext.RouteData.Values["Action"];
if (controllerContext.Controller.GetType() == typeof(MyController) && actionName != null &&
(string.Compare(actionName.ToString(), "post", StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(actionName.ToString(), "public", StringComparison.OrdinalIgnoreCase) == 0))
{
string contentText;
using (var stream = controllerContext.HttpContext.Request.InputStream)
{
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream))
contentText = reader.ReadToEnd();
}
if (string.IsNullOrEmpty(contentText)) return (null);
return JObject.Parse(contentText);
}
return base.BindModel(controllerContext, bindingContext);
}
}
Then register the custom model binder in the beginning of Application_Start:
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new MyModelBinder();