Web Api MVC binding FormDataCollection - asp.net-mvc-4

I have a Jquery Ajax call more or like below
$.ajax(url, {
type: httpMethod,
contentType: "application/json; charset=utf-8",
data: '{name:"abc",age:23,address:"29 judd ce"}',
The web api action is
public HttpResponseMessage Post([FromBody] FormDataCollection data)
{
However the parameter 'data' is always null.
Is this a web api limitation or am i doing it the wrong way
Thanks

Use a view model instead:
public class UserViewModel
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
that your controller action will take:
public HttpResponseMessage Post(UserViewModel model)
{
...
}

it's possible
$.ajax("http://localhost:57281/api/Values", {
type: "POST",
data: '{name:"abc",age:23,address:"29 judd ce"}',
contentType: "application/x-www-form-urlencoded"
});
//POST api/values
public void Post([FromBody]FormDataCollection value)
{
}
Also works without [FromBody]
But you will get only ONE pair of key/value
Better use something like that
$.ajax("http://localhost:57281/api/Values", {
type: "POST",
data: 'name=abc&age=23&address=29 judd ce',
contentType: "application/x-www-form-urlencoded"
});
& is used as a separator for each pair

Related

Receive a jagged array in query parameters

I currently have a controller with an action that fetches some information, this action receives as a query param a jagged array to filter the information, example: [["Day", ">=", "01.01.2021"],["User", "=", "SomeUserId"]].
My current action declarion:
[HttpGet]
public async Task<object> Get(string[][] filters)
{
...
}
When I make an AJAX request from the client the URL goes encoded in the following way (DevTools request header parameters view):
filters[0][]: Day
filters[0][]: >=
filters[0][]: 01.07.2021
filters[1][]: User
filters[1][]: =
filters[1][]: SomeUserId
Url Encoded FYI: ...filters%5B0%5D%5B%5D=Day&filters%5B0%5D%5B%5D=%3E%3D&filters%5B0%5D%5B%5D=01.07.2021&filters%5B1%5D%5B%5D=User&filters%5B1%5D%5B%5D=%3D&filters%5B1%5D%5B%5D=SomeUserId
The problem
My action when receives the information above has the value of two empty string arrays string[2][]. The following image is a debug print screen from VS of the filters variable.
Should I serialize? Or use a different structure?
Here is a working demo:
View:
<button type="button" onclick="SendRequest()">Click</button>
#section Scripts
{
<script>
function SendRequest() {
var day = "Day";
var simble = encodeURIComponent(">=");
var date = "01.07.2021";
var user = "UserName";
var simble2 = "=";
var id = "1";
$.ajax({
//url: "api/values?filters[0][0]=Day&filters[0][1]=>=&filters[0][2]=01.07.2021&filters[1][0]=User&filters[1][1]==&filters[1][2]=2"
url: "api/values?filters[0][0]=" + day + "&filters[0][1]=" + simble + "&filters[0][2]=" + date + " &filters[1][0]=" + user + "&filters[1][1]=" + simble2 + "&filters[1][2]=" + id + "",
type: 'GET',
success: function (res) {
alert("success");
},
error: function () {
}
})
}
</script>
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
public void Get([FromQuery] string[][] filters)
{
}
}
One option is to wrap the input in a class like
public class InputRequest
{
public string Field { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
Then use a custom model binder -
class InputRequestModelBinder : IModelBinder
{
static readonly JsonSerializerOptions options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(List<InputRequest>)) return Task.CompletedTask;
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var results = valueProviderResult.Select(t => JsonSerializer.Deserialize<InputRequest>(t, options)).ToList();
bindingContext.Result = ModelBindingResult.Success(results);
return Task.CompletedTask;
}
}
Lastly, hook the model binder to controller method -
[HttpGet]
[Route("show")]
public string Show([ModelBinder(typeof(InputRequestModelBinder))] List<InputRequest> req)
{
//do something
}
Example usage -
/SomeController/show?req=%7B%0A%20%20%22field%22%3A%20%22name%22%2C%0A%20%20%22operator%22%3A%20%22%3D%22%2C%0A%20%20%22value%22%3A%20%22tom%22%0A%7D&req=%7B%0A%20%20%22field%22%3A%20%22age%22%2C%0A%20%20%22operator%22%3A%20%22%3C%22%2C%0A%20%20%22value%22%3A%20%2220%22%0A%7D
Decoded url looks like this -
/SomeController/show?req={
"field": "name",
"operator": "=",
"value": "tom"
}&req={
"field": "age",
"operator": "<",
"value": "20"
}
Pro -
You get to explicitly state the contract to your API consumers about expected values in InputRequest
Con -
Your request query string length increases, so you may hit the limit soon. Although you may convert it to a POST method, but then it violates REST principles.

Json Request Without Model Binding In ASP.NET Core WebAPI

How can use ASP.NET Core WebAPI process json request without model binding?
I have more than one hundred APIs that need to be transferred to ASP.NET Core WebAPI, but I cannot design a model for each API because the number is too much.
In addition, I also need to use Swagger, so I cannot use string parsing like IActionResult Post(string json) or IActionResult Post(JObject obj).
Controller Code(with bug):
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
public class Person { public int Age { get; set; } }
[HttpPost]
public IActionResult Post([FromBody] string name, Person person)
{
return Ok(new
{
Name = name,
person.Age
});
}
}
Postman:
POST /Test HTTP/1.1
Host: localhost:5000
Content-Type: application/json
Content-Length: 68
{
"Name": "Test",
"Person": {
"Age": 10
}
}
Current API Interface:
bool InsertPoint(PointInfo point);
bool[] InsertPoints(PointInfo[] points);
bool RemovePoint(string pointName);
bool[] RemovePoints(string[] pointNames);
string[] Search(SearchCondition condition, int count);
...
What i want to do in controller:
[HttpPost]
[SomeJsonBodyAttribute]
public IActionResult InsertPoint(PointInfo point);
[HttpPost]
[SomeJsonBodyAttribute]
public IActionResult InsertPoints(PointInfo[] points);
[HttpPost]
[SomeJsonBodyAttribute]
public IActionResult RemovePoint(string pointName);
[HttpPost]
[SomeJsonBodyAttribute]
public IActionResult RemovePoints(string[] pointNames);
[HttpPost]
[SomeJsonBodyAttribute]
public IActionResult Search(SearchCondition condition, int count);
...
What i want to do in request:
RemovePoint
POST /Point/RemovePoint HTTP/1.1
Content-Type: application/json
{
"pointName": "Test"
}
Search
POST /Point/Search HTTP/1.1
Content-Type: application/json
{
"condition": {
...
},
"count": 10
}
Be sure add Newtonsoft support in your project.You could refer to the following answer:
https://stackoverflow.com/a/65101721/11398810
Then you could get data like below:
[HttpPost]
public IActionResult Post([FromBody] JObject obj)
{
//for list model:
//obj["Person"][0]["Age"].ToString()
return Ok(new { Name = obj["Name"].ToString(), Age = obj["Person"]["Age"].ToString() });
}
With json:
{
"Name": "Test",
"Person": {
"Age": 10
}
}
Result:

Asp.Net Core 3.1 Model Binding Doesn't Work

Model binding doesn't work on asp.net core 3.1 version. I have no problem with previous dotnet core versions or .net framework version. Somebody help me please.
Best regards
JavaScript code:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19", "message": "test", "logLevel": "Error" };
fetch('/log/list', {
method: "post",
body: JSON.stringify(postData),
headers: {
"Content-Type": "application/json"
}
})
.then(res => res.json())
.then(res => {
console.log(res);
}).catch(err => {
console.error(err);
});
C# code:
[HttpPost]
public IActionResult List([FromBody] LogSearchModel searchModel) // searchModel is null
{
string rawBodyStr = "";
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
rawBodyStr = reader.ReadToEndAsync().Result;
}
// rawBodyStr is null
}
public class LogSearchModel
{
[DisplayName("Başlangıç tarihi")]
[UIHint("DateNullable")]
public DateTime? CreatedFrom { get; set; }
[DisplayName("Bitiş tarihi")]
[UIHint("DateNullable")]
public DateTime? CreatedTo { get; set; }
[DisplayName("Açıklama")]
public string Message { get; set; }
[DisplayName("Olay türü")]
public int LogLevel { get; set; }
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<LogManager>();
services.AddControllersWithViews();
}
Your LogLevel is type of int,but you pass the string data.
Change from:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19",
"message": "test", "logLevel": "Error" };
To:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19",
"message": "test", "logLevel": 1 };

Create a custom Model Binder for a custom type

In an ASP.NET CORE 1.1 project I have the following model:
public class GetProductsModel {
public OrderExpression OrderBy { get; set; }
}
OrderExpression is a class which has the following method:
Boolean TryParse(String value, out OrderExpression expression)
The method creates a OrderExpression instance from a String and can be used:
OrderExpression expression;
Boolean parsed = OrderExpression.TryParse(value, out expression);
How can I create a custom Model Binder to properties of type OrderExpression?
I assume that within your request data there is a property orderBy that you want to bind into an OrderExpression using OrderExpression.TryParse.
Let's assume your OrderExpression class looks like follows, where I have provided a very simple implementation of your TryParse method:
public class OrderExpression
{
public string RawValue { get; set; }
public static bool TryParse(string value, out OrderExpression expr)
{
expr = new OrderExpression { RawValue = value };
return true;
}
}
Then you could create a model binder which basically gets the raw string value and calls OrderExpression.TryParse:
public class OrderExpressionBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values.Length == 0) return Task.CompletedTask;
// Attempt to parse
var stringValue = values.FirstValue;
OrderExpression expression;
if (OrderExpression.TryParse(stringValue, out expression))
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, expression, stringValue);
bindingContext.Result = ModelBindingResult.Success(expression);
}
return Task.CompletedTask;
}
}
You will also need a new model binder provider, which returns your new binder just for the OrderExpression type:
public class OrderExpressionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return context.Metadata.ModelType == typeof(OrderExpression) ? new OrderExpressionBinder() : null;
}
}
// It should be registered in your Startup class, adding it to the ModelBinderProviders collection:
services.AddMvc(opts => {
opts.ModelBinderProviders.Insert(0, new OrderExpressionBinderProvider());
});
With this in place, you will be able to bind OrderExpression parameters of the controller actions. Something like in the following example:
[HttpPost]
public IActionResult Products([FromBody]OrderExpression orderBy)
{
return Ok();
}
$.ajax({
method: 'POST',
dataType: 'json',
url: '/home/products',
data: {orderby: 'my orderby expression'}
});
However there is something else that needs to be done for you to be able to send a json and bind it to a complex model like GetProductsModel which internally contains an OrderExpression. I am talking about a scenario like this:
[HttpPost]
public IActionResult Products([FromBody]GetProductsModel model)
{
return Ok();
}
public class GetProductsModel
{
public OrderExpression OrderBy { get; set; }
}
$.ajax({
method: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
url: '/home/products',
data: JSON.stringify({orderby: 'my orderby expression'})
});
In that scenario ASP.Net Core will just use Newtonsoft.Json as the InputFormatter and convert the received json into an instance of the GetProductsModel model, without trying to use the new OrderExpressionBinderProvider for the internal property.
Luckily, you can also tell Newtonsoft.Json how to format properties of OrderExpression type by creating your JsonConverter:
public class OrderExpressionJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(OrderExpression);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var stringValue = reader.Value?.ToString();
OrderExpression expression;
if (OrderExpression.TryParse(stringValue, out expression))
{
return expression;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Which should be registered in your Startup class:
services.AddMvc(opts => {
opts.ModelBinderProviders.Insert(0, new OrderExpressionBinderProvider());
}).AddJsonOptions(opts => {
opts.SerializerSettings.Converters.Add(new OrderExpressionJsonConverter());
});
Now you will finally be able to handle both scenarios :)

WebApiContrib Jsonp and Attribute Routing

Per the WebApiContrib.Formatting.Jsonp GitHub readme, it appears that in the RouteConfig.cs this should be entered:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{format}",
defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional }
);
I currently don't have a RouteConfig.cs file in my AppStart. I created it using the Web API 2 template and I don't think I changed anything structurally. I do have a WebApiConfig.cs where I have set:
public static void Register (HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
how do I include it such that all routes have the ability to return Jsonp?
You could create a custom route attribute which implements IHttpRouteInfoProvider (which Web API route builder looks for when adding routes to route table) and then modify the template that is being generated by appending {format}
Example:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[CustomRoute(Order = 1)]
public IEnumerable<string> GetAll()
{
return new string[] { "value1", "value2" };
}
[CustomRoute("{id}")]
public string GetSingle(int id)
{
return "value";
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class CustomRouteAttribute : Attribute, IHttpRouteInfoProvider
{
public CustomRouteAttribute()
{
Template = String.Empty;
}
public CustomRouteAttribute(string template)
{
if (template == null)
{
throw new ArgumentNullException("template");
}
if (template == string.Empty)
{
Template = template + "{format?}";
}
else
{
Template = template.TrimEnd('/') + "/{format?}";
}
}
public string Name { get; set; }
public int Order { get; set; }
public string Template { get; private set; }
}
I found this comment in a pull request but I don't understand if this is yet implemented into the production package nor if it got pulled at all.
If you are using Attribute Routing, you should add "/{format}" after each route if you plan to use the URI mapping for jsonp, e.g. [Route("api/value/{id:int}/{format?}")]. If you will require the Content-Type header to specify text/javascript, then you can leave your routes alone. (See the sample applications for examples.)