Passing multiple simple POST Values to ASP.NET Web API
A few weeks backs I wrote about what does and doesn't work With POST values in ASP.NET Web API when it comes to sending data to a Web API controller. One of the features that doesn't work out of the box - somewhat unexpectedly - is the ability to map POST form variables to simple parameters of a Web API method.
For example imagine you have this form and you want to post this data to a Web API end point like this via AJAX:
<form> Name: <input type="name" name="name" value="Rick" /> Value: <input type="value" name="value" value="12" /> Entered: <input type="entered" name="entered" value="12/01/2011" /> <input type="button" id="btnSend" value="Send" /> </form> <script type="text/javascript"> $("#btnSend").click( function() { $.post("samples/PostMultipleSimpleValues?action=kazam", $("form").serialize(), function (result) { alert(result); }); }); </script>
or you might do this more explicitly by creating a simple client map and specifying the POST values directly by hand:
$.post("samples/PostMultipleSimpleValues?action=kazam", { name: "Rick", value: 1, entered: "12/01/2012" }, function (result) { alert(result); });
On the wire this generates a simple POST request with Url Encoded values in the content:
POST /AspNetWebApi/samples/PostMultipleSimpleValues?action=kazam HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1 Accept: application/json Connection: keep-alive Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://localhost/AspNetWebApi/FormPostTest.html Content-Length: 41 Pragma: no-cache Cache-Control: no-cache
name=Rick&value=12&entered=12%2F10%2F2011
Seems simple enough, right? We are basically posting 3 form variables and 1 query string value to the server.
Unfortunately Web API can't handle this request out of the box. If I create a method like this:
[HttpPost] public string PostMultipleSimpleValues(string name, int value, DateTime entered, string action = null) { return string.Format("Name: {0}, Value: {1}, Date: {2}, Action: {3}", name, value, entered, action); }You'll find that you get an HTTP 404 error and
{"Message": "No HTTP resource was found that matches the request URI…"}
Yes, it's possible to pass multiple POST parameters of course, but Web API expects you to use Model Binding for this - mapping the post parameters to a strongly typed .NET object, not to single parameters. Alternately you can also accept a FormDataCollection parameter on your API method to get a name value collection of all POSTed values. If you're using JSON only, using the dynamic JObject/JValue objects might also work.
Model Binding is fine in many use cases, but can quickly become overkill if you only need to pass a couple of simple parameters to many methods. Especially in applications with many, many AJAX callbacks the 'parameter mapping type' per method signature can lead to serious class pollution in a project very quickly. Simple POST variables are also commonly used in AJAX applications to pass data to the server, even in many complex public APIs. So this is not an uncommon use case, and - maybe more so a behavior that I would have expected Web API to support natively. The question "Why aren't my POST parameters mapping to Web API method parameters" is already a frequent one…
So this is something that I think is fairly important, but unfortunately missing in the base Web API installation.
Creating a Custom Parameter Binder
Luckily Web API is greatly extensible and there's a way to create a custom Parameter Binding to provide this functionality! Although this solution took me a long while to find and then only with the help of some folks at Microsoft (thanks Hong Mei!!!), it's not difficult to hook up in your own projects once you know what to implement. It requires one small class and a GlobalConfiguration hookup.
Web API parameter bindings allow you to intercept processing of individual parameters - they deal with mapping parameters to the signature as well as converting the parameters to the actual values that are returned.
Here's the implementation of the SimplePostVariableParameterBinding class:
(updated 9/24/2012: Fixed bug with non-form data trying to read query string values)
public class SimplePostVariableParameterBinding : HttpParameterBinding { private const string MultipleBodyParameters = "MultipleBodyParameters"; public SimplePostVariableParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor) { } /// <summary> /// Check for simple binding parameters in POST data. Bind POST /// data as well as query string data /// </summary> /// <param name="metadataProvider"></param> /// <param name="actionContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { string stringValue = null; NameValueCollection col = TryReadBody(actionContext.Request); if (col != null) stringValue = col[Descriptor.ParameterName]; // try reading query string if we have no POST/PUT match if (stringValue == null) { var query = actionContext.Request.GetQueryNameValuePairs(); if (query != null) { var matches = query.Where(kv => kv.Key.ToLower() == Descriptor.ParameterName.ToLower()); if (matches.Count() > 0) stringValue = matches.First().Value; } } object value = StringToType(stringValue); // Set the binding result here SetValue(actionContext, value); // now, we can return a completed task with no result TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>(); tcs.SetResult(default(AsyncVoid)); return tcs.Task; } /// <summary> /// Method that implements parameter binding hookup to the global configuration object's /// ParameterBindingRules collection delegate. /// /// This routine filters based on POST/PUT method status and simple parameter /// types. /// </summary> /// <example> /// GlobalConfiguration.Configuration. /// .ParameterBindingRules /// .Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding); /// </example> /// <param name="descriptor"></param> /// <returns></returns> public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor) { var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods; // Only apply this binder on POST and PUT operations if (supportedMethods.Contains(HttpMethod.Post) || supportedMethods.Contains(HttpMethod.Put)) { var supportedTypes = new Type[] { typeof(string), typeof(int), typeof(decimal), typeof(double), typeof(bool), typeof(DateTime), typeof(byte[]) }; if (supportedTypes.Where(typ => typ == descriptor.ParameterType).Count() > 0) return new SimplePostVariableParameterBinding(descriptor); } return null; } private object StringToType(string stringValue) { object value = null; if (stringValue == null) value = null; else if (Descriptor.ParameterType == typeof(string)) value = stringValue; else if (Descriptor.ParameterType == typeof(int)) value = int.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(Int32)) value = Int32.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(Int64)) value = Int64.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(decimal)) value = decimal.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(double)) value = double.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(DateTime)) value = DateTime.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof(bool)) { value = false; if (stringValue == "true" || stringValue == "on" || stringValue == "1") value = true; } else value = stringValue; return value; } /// <summary> /// Read and cache the request body /// </summary> /// <param name="request"></param> /// <returns></returns> private NameValueCollection TryReadBody(HttpRequestMessage request) { object result = null; // try to read out of cache first if (!request.Properties.TryGetValue(MultipleBodyParameters, out result)) { var contentType = request.Content.Headers.ContentType; // only read if there's content and it's form data if (contentType == null || contentType.MediaType != "application/x-www-form-urlencoded") { // Nope no data result = null; } else { // parsing the string like firstname=Hongmei&lastname=ASDASD result = request.Content.ReadAsFormDataAsync().Result; } request.Properties.Add(MultipleBodyParameters, result); } return result as NameValueCollection; } private struct AsyncVoid { } }
The ExecuteBindingAsync method is fired for each parameter that is mapped and sent for conversion. This custom binding is fired only if the incoming parameter is a simple type (that gets defined later when I hook up the binding), so this binding never fires on complex types or if the first type is not a simple type.
For the first parameter of a request the Binding first reads the request body into a NameValueCollection and caches that in the request.Properties collection. The request body can only be read once, so the first parameter request reads it and then caches it. Subsequent parameters then use the cached POST value collection. Once the form collection is available the value of the parameter is read, and the value is translated into the target type requested by the Descriptor. SetValue writes out the value to be mapped.
Once you have the ParameterBinding in place, the binding has to be assigned. This is done along with all other Web API configuration tasks at application startup in global.asax's Application_Start:
// Attach simple post variable binding GlobalConfiguration.Configuration. .ParameterBindingRules .Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding);
The hookup code calls the static HookupParameterBinding method of the SimplePostVariableParameterBinding class that provides the delegate that the Insert method requires. The delegate's job is to check which type of requests the ParameterBinding should handle. The logic in HookupParameterBinding checks whether the request is POST or PUT and whether the parameter type is one of the simple types that is supported. Web API calls this delegate once for each method signature it tries to map and the function returns null to indicate it's not handling this parameter, or it returns a new parameter binding instance - in this case the SimplePostVariableParameterBinding.
As a consumer of this class the one line above is all you need!
Once the parameter binding and this hook up code is in place, you can now pass simple POST values to methods with simple parameters. The examples I showed above should now work in addition to the standard bindings.
Summary
Clearly this is not easy to discover. I spent quite a bit of time digging through the Web API source trying to figure this out on my own without much luck. It took Hong Mei from Microsoft to provide a base example, so I can't take credit for this solution :-). But once you know where to look, Web API is brilliantly extensible to make it relatively easy to customize the parameter behavior.
I'm very stoked that this got resolved - in the last two months I've had two customers with projects that decided not to use Web API in AJAX heavy SPA applications because this POST variable mapping wasn't available. This custom parameter binding in Web API might actually change their mind to still switch back and take advantage of the many great features in Web API. I too frequently use plain POST variables for communicating with server AJAX handlers and while I work around this (with untyped JObject or the Form collections mostly), I prefer in many cases to push simple POST variables to the server - it's simply more convenient and more logical in many situations.
I said this in my last post on POST data and say it again here:
I think POST to method parameter mapping should have been shipped in the box with Web API! Without knowing about this limitation and seeing that query string values match, the expectation is that simple POST variables should also map to parameters. I hope Microsoft considers including this type of functionality natively in the next version of Web API natively or at least as a built-in HttpParameterBinding that can be just added. Especially given that this binding doesn't adversely affect existing bindings or even their performance.
Resources
The Voices of Reason
# re: Passing multiple simple POST Values to ASP.NET Web API
# re: Passing multiple simple POST Values to ASP.NET Web API
# re: Passing multiple simple POST Values to ASP.NET Web API
{"No MediaTypeFormatter is available to read an object of type 'FormDataCollection' from content with media type 'application/json'."} at this line "request.Content.ReadAsFormDataAsync().Result;"
Any suggestions??
# re: Passing multiple simple POST Values to ASP.NET Web API
# re: Passing multiple simple POST Values to ASP.NET Web API
But ,Some other actions take a complex type as parameter and i pass JSON data using knockout and jquery ajax. This was working fine before , but once i hookup "SimplePostVariableParameterBinding", in my global configuration, this call fails with the error i posted earlier.
How i can ignore "SimplePostVariableParameterBinding" for JSON data? Please suggest.
# re: Passing multiple simple POST Values to ASP.NET Web API
The fix below in TryReadBody adds a check for the url encoded content explicitly and doesn't try to read the content otherwise. With a complex type the only time the binder should fire is when a query string value is provided.
Here's the updated TryReadBody method (updated in the article and the source online):
private NameValueCollection TryReadBody(HttpRequestMessage request) { object result = null; // try to read out of cache first if (!request.Properties.TryGetValue(MultipleBodyParameters, out result)) { var contentType = request.Content.Headers.ContentType; // only read if there's content and it's form data if (contentType == null || contentType.MediaType != "application/x-www-form-urlencoded") { // Nope no data result = null; } else { // parsing the string like firstname=Hongmei&lastname=ASDASD result = request.Content.ReadAsFormDataAsync().Result; } request.Properties.Add(MultipleBodyParameters, result); } return result as NameValueCollection; }
# re: Passing multiple simple POST Values to ASP.NET Web API
But i have a different error now. Sorry for troubling you, but i want to do this the right way, with your help.
Action on my Test controller looks like this
public bool PutSomething(int id, ComplexType complexType)
and my jquery ajax call looks as below
$.ajax({
url: '@Url.HttpRouteUrl("DefaultApi", new { controller = "Test"})' + '/' + self.id,
type: "PUT",
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(self) // My complex data
}).done(function (data) {
// Do something.
alert('Updated Successfully!!');
});
This call throws an error with the message "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method"
I debugged the code and found that below code does not recognize the Querystring
from the url, unless i change the url to be (?Id= is the change. I have to specifically mention the Id.)
url: '@Url.HttpRouteUrl("DefaultApi", new { controller = "Test"})' + '/?Id=' + self.id
in my ajax call?
// try reading query string if we have no POST/PUT match
if (stringValue == null)
{
var query = actionContext.Request.GetQueryNameValuePairs();
if (query != null)
{
var matches = query.Where(kv => kv.Key.ToLower() == Descriptor.ParameterName.ToLower());
if (matches.Count() > 0)
stringValue = matches.First().Value;
}
}
# re: Passing multiple simple POST Values to ASP.NET Web API
This function only for contentType: 'application/x-www-form-urlencoded; charset=utf-8'
here is my simple javascript code:
$.ajax({
url: serviceRoot + '/api/product/' ,
cache: false,
type: 'PUT',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
data: { id: data.Id, name: data.Name}
});
And I am looking for help for an exception "Multiple actions were found that match the request........."
I have attached simple post variable binding in global.aspx and wrote a test controller with two put methods
[HttpPut]
public void TestPut(int price)
{
}
[HttpPut]
public string TestMultipleSimpleValues(string id, string name)
{
return string.Format("id: {0}, name: {1}", id, name);
}
There is an an exception "Multiple actions were found that match the request........." when I call web api by above javascript code.
But it will work properly when remove put method "public void TestPut(int price)" from test controller.
It seems can only have one put method in controller, but I need more.
Any idea to help?
Thanks.
# re: Passing multiple simple POST Values to ASP.NET Web API
Will be fine if is possible to use custom binding only on params with custom attribute like this
public string PostMultipleSimpleValues([FromWWWPost]string name, [FromWWWPost]int value)
It can eliminate sideeffects. Is it possible to update code to support this?
Petr
BTW: This binding work by default before RC or RTM, but I don't know why MS change this
# re: Passing multiple simple POST Values to ASP.NET Web API
I just tried to implement this in VB, but when Inserting "HookupParameterBinding" in the configuration I get the following error:
Argument not specified for parameter 'descriptor' of 'Public Shared Function HookupParameterBinding(descriptor As HttpParameterDescriptor) As HttpParameterBinding'.
# re: Passing multiple simple POST Values to ASP.NET Web API
When adding the new ParameterBindingRule to the Application_Start in Global.asax, it's important that the binding occur BEFORE any call to "MapHttpAttributeRoutes". For instance, in my test code, this version will result in the ParamterBindingRule never being called:
protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); // Attach simple post variable binding GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, SimplePostVariableParameterBinding.HookupParameterBinding); // ...EXTRA CONFIGURATION CODE REMOVED... }
In the code above, MapHttpAttributeRoutes is being called in the WebApiConfig.Register method. If I simply put the new ParameterBindingRule above that line, the new ParameterBindingRule executes successfully.
protected void Application_Start() { // Attach simple post variable binding GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, SimplePostVariableParameterBinding.HookupParameterBinding); GlobalConfiguration.Configure(WebApiConfig.Register); // ...EXTRA CONFIGURATION CODE REMOVED... }
This could be related to some sort of configuration issue, but it seems to have fixed the issue I was having where even with this code, I was still getting an error message stating that I couldn't pass multiple parameters to the service.
# re: Passing multiple simple POST Values to ASP.NET Web API
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken)
...
object value = StringToType(stringValue);
// Handle optional values.
if (value == null && this.Descriptor.IsOptional)
{
value = this.Descriptor.DefaultValue;
}
// Set the binding result here
SetValue(actionContext, value);
# re: Passing multiple simple POST Values to ASP.NET Web API
I'm not sure if this is the best way to go about it, but to also bind route parameters I added this code after the body and query string checks:
if (stringValue == null) { var routeData = (HttpRouteData)actionContext.Request.Properties["MS_HttpRouteData"]; object routeParameterValue; if (routeData.Values.TryGetValue(Descriptor.ParameterName, out routeParameterValue)) { stringValue = (string)routeParameterValue; } }
Also, per MS documentation (http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api), I added this:
public override bool WillReadBody { get { return true; } }
# re: Passing multiple simple POST Values to ASP.NET Web API
public class SimplePostVariableParameterBinding : HttpParameterBinding { private const string MultipleBodyParameters = "MultipleBodyParameters"; public SimplePostVariableParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor) { } /// <summary> /// Check for simple binding parameters in POST data. Bind POST /// data as well as query string data /// </summary> /// <param name="metadataProvider"></param> /// <param name="actionContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { string stringValue = null; NameValueCollection col = TryReadBody(actionContext.Request); if (col != null) stringValue = col[Descriptor.ParameterName]; // try reading query string if we have no POST/PUT match if (stringValue == null) { var query = actionContext.Request.GetQueryNameValuePairs(); if (query != null) { var matches = query.Where(kv => kv.Key.ToLower() == Descriptor.ParameterName.ToLower()); if (matches.Count() > 0) stringValue = matches.First().Value; } } object value = StringToType(stringValue); // Set the binding result here SetValue(actionContext, value); // now, we can return a completed task with no result TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>(); tcs.SetResult(default(AsyncVoid)); return tcs.Task; } /// <summary> /// Method that implements parameter binding hookup to the global configuration object's /// ParameterBindingRules collection delegate. /// /// This routine filters based on POST/PUT method status and simple parameter /// types. /// </summary> /// <example> /// GlobalConfiguration.Configuration. /// .ParameterBindingRules /// .Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding); /// </example> /// <param name="descriptor"></param> /// <returns></returns> public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor) { var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods; // Only apply this binder on POST and PUT operations if (supportedMethods.Contains(HttpMethod.Post) || supportedMethods.Contains(HttpMethod.Put)) { var supportedTypes = new Type[] { typeof(string), typeof(int), typeof(int?), typeof(decimal), typeof(decimal?), typeof(double), typeof(double?), typeof(long), typeof(long?), typeof(bool), typeof(bool?), typeof(DateTime), typeof(DateTime?), typeof(byte[]) }; if (supportedTypes.Count(typ => typ == descriptor.ParameterType) > 0) return new SimplePostVariableParameterBinding(descriptor); } return null; } object StringToType(string stringValue) { object value = null; if (stringValue == null) value = null; else if (Descriptor.ParameterType == typeof (string)) value = stringValue; else if (Descriptor.ParameterType == typeof (int)) value = int.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (int?)) value = string.IsNullOrWhiteSpace(stringValue) ? (int?) null : int.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (long)) value = long.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (long?)) value = string.IsNullOrWhiteSpace(stringValue) ? (long?) null : long.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (decimal)) value = decimal.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (decimal?)) value = string.IsNullOrWhiteSpace(stringValue) ? (decimal?) null : decimal.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (double)) value = double.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (double?)) value = string.IsNullOrWhiteSpace(stringValue) ? (double?) null : double.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (DateTime)) value = DateTime.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (DateTime?)) value = string.IsNullOrWhiteSpace(stringValue) ? (DateTime?) null : DateTime.Parse(stringValue, CultureInfo.CurrentCulture); else if (Descriptor.ParameterType == typeof (bool)) { value = false; if (stringValue == "true" || stringValue == "on" || stringValue == "1") value = true; } else if (Descriptor.ParameterType == typeof (bool?)) { value = false; if (string.IsNullOrWhiteSpace(stringValue)) value = (bool?) null; else if (stringValue == "true" || stringValue == "on" || stringValue == "1") value = true; } else value = stringValue; return value; } /// <summary> /// Read and cache the request body /// </summary> /// <param name="request"></param> /// <returns></returns> private NameValueCollection TryReadBody(HttpRequestMessage request) { object result = null; // try to read out of cache first if (!request.Properties.TryGetValue(MultipleBodyParameters, out result)) { var contentType = request.Content.Headers.ContentType; // only read if there's content and it's form data if (contentType == null || contentType.MediaType != "application/x-www-form-urlencoded") { // Nope no data result = null; } else { // parsing the string like firstname=Hongmei&lastname=ASDASD result = request.Content.ReadAsFormDataAsync().Result; } request.Properties.Add(MultipleBodyParameters, result); } return result as NameValueCollection; } private struct AsyncVoid { } }
# re: Passing multiple simple POST Values to ASP.NET Web API
https://github.com/RoyiNamir/SimplePostVariableParameterBindingExtended
# re: Passing multiple simple POST Values to ASP.NET Web API
I found that the below code supports only form passed values but what if I send Json request like this: { itemId: "10626217", lookupType: "0" } (multipleBodyparameters is always null)
// only read if there's content and it's form data
if (contentType == null || contentType.MediaType != "application/x-www-form-urlencoded")
{
// Nope no data
result = null;
}
else
{
// parsing the string like firstname=Hongmei&lastname=ASDASD
result = request.Content.ReadAsFormDataAsync().Result;
}
# re: Passing multiple simple POST Values to ASP.NET Web API
Consider a method like this:
public ValidationResult SaveEvent(EventInfo e, List<UserInfo> inviteList, long currentUserID, string token) { ValidateToken(currentUserID, token); return ValidationManager.ValidateAndSaveEvent(e, inviteList, currentUserID); }
It seems that my only option for this is to create a single (view) model that encompasses the first two complex-type parameters. And if I did that, I might as well have that model also include the last two simple parameters, otherwise I'd have to pass the last two parameters via querystring while passing the model parameter in the form body.
Needless to say this requires much more work than it seems like it should, especially since everything was working fine when it was asmx. Any suggestions?
# re: Passing multiple simple POST Values to ASP.NET Web API
Web API can't make these same assumptions as you can pass ANYTHING to it. Therefore there are more restrictions on the actual interface you can pass. I'd argue that passing complex parameters in a REST call is not really clean as it doesn't describe what's actually happening underneath. If you have a model that describes the structure on the other hand you are EXACTLY describing what's happening.
As outlined in the post there are two options to do what you're doing:
* Create a ViewModel that includes each parameter as a property
* Use dynamic types which translate into JObject values
Additionally if you really want to be able to do this using exactly that syntax you could create a custom MessageHandler that could do what you want. Maybe with a custom attribute that identifies when this should happen. Wouldn't be hard to do either. But it's something that has to be explicitly designated.
# re: Passing multiple simple POST Values to ASP.NET Web API
# re: Passing multiple simple POST Values to ASP.NET Web API
# re: Passing multiple simple POST Values to ASP.NET Web API
How do Passing multiple simple POST Values IN ASP.NET Core? Thanks!
# re: Passing multiple simple POST Values to ASP.NET Web API
THANK YOU. I had been searching a while and this is the first thing I found that was actually close to the problem and it worked perfectly for us. You are the man.
# re: Passing multiple simple POST Values to ASP.NET Web API
This is great work, as I am trying to make it so that a Web API method supports both GET and POST requests, as such:
[ActionName("Read")]
[HttpGet, HttpPost]
public async Task<MyObject> Read(int id)
{
...
}
Yes, I know all about how Web API is supposed to work, with the method names matching the HTTP verbs, but this is converting a legacy API that was actually done in MVC, so the method names and the verbs they accept cannot be changed. One thing I noticed is that parameter values that could be part of the path now have to be explicitly named. Consider the following routing:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"\d+" }
);
Now if I send a request to /api/Foo/Bar/1, I end getting the error "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type ..." In this example, I can hack something together to compare the route template to the URI segments, but if I had default values for controller and/or action, then it's just a big pain in the butt.
Furthermore, ideally you'd want to apply any constraints as well, so that /api/Foo/Bar/Baz gets a 404 error instead of a FormatException. At this point it's clear I'd be reinventing the wheel, so do you know if that's all doable via the framework somehow?
# re: Passing multiple simple POST Values to ASP.NET Web API
...and that is why we're still using ASP.NET MVC as our REST-api backend.
Anyways, great post and great workaround, thanks!
# re: Passing multiple simple POST Values to ASP.NET Web API
It's been 8 years and a man from Hawaii saved [a week for] a man from Russia. I can't express how much grateful I am. Thank you.
# re: Passing multiple simple POST Values to ASP.NET Web API
Hello Rick,
Is there a way to achieve something similar in .Net Core? We have a large project with hundreds of Actions where we had used the above strategy to convert form data into input for methods. We are in the process of upgrading the code to .Net 6 and I have not been able to find any comparable way to achieve this.
Most of the information suggest I add [FromForm] on inputs which would be a huge undertaking.
Any suggestions or recommendations would be highly appreciated?
Thanks!
# re: Passing multiple simple POST Values to ASP.NET Web API