Pass an Array of Integers to ASP.NET Web API

Passing array of integers to webapi Method

Your code looking pretty Ok to me. Please define structure of "deletedIds" object.
one suggestion is to Use
new Array()
object to initialize deletedIds property and remove JSON.stringify() .
A similar question asked here.

EDIT

Web API supports parsing content data in a variety of ways, but it does not deal with multiple posted content values. A solution for your problem could be to create a ViewModel with a property of int[] type.
Like code below,

public class SimpleViewModel
{
public int[] deletedIds{ get; set; }
}

//Controller
[HttpDelete]
public bool DeleteModel(SimpleViewModel deletedIds)
{
return modelsRepository.DeleteModels(deletedIds.deletedIds);
}

and use it as parameter type.

Web api passing array of integers to action method

Your problem intrigued me so I wanted to come up with a solution that was a bit more generic than the answer Nkosi provided. While Nkosi's answer will work, I'm not a fan of the ModelBinder syntax as well as defining a new ModelBinder for each type. I've been playing around with ParameterBindingAttribute before and really like the syntax so I wanted to start with this. This allows you to define a [FromUri] or [FromBody] like syntax. I also wanted to be able to use different "array" types such as int[] or List or HashSet or best yet, IEnumerable.

Step 1: Create a HttpParameterBinding

Step 2: Create a ParameterBindingAttribute

Step 3: Put it all together

An HttpParameterBinding allows you to parse any RouteData and pass them to your methed by setting the actionContext's ActionArguments dictionary. You simply inherit from HttpParameterBinding and override the ExecuteBindingAsync method. You can throw exception here if you want, but you can also just let it flow through and the method will receive null if it wasn't able to parse the RouteData. For this example, I am creating a JSON string of an array made from the RouteData. Since we know Json.NET is amazing at parsing data types, it seemed natural to use it. This will parse the RouteData for a CSV value. This works best for ints or dates.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using Newtonsoft.Json;

public class CsvParameterBinding : HttpParameterBinding
{
public CsvParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
{
}

public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
var paramName = this.Descriptor.ParameterName;

var rawParamemterValue = actionContext.ControllerContext.RouteData.Values[paramName].ToString();

var rawValues = rawParamemterValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

//To convert the raw value int a true JSON array we need to make sure everything is quoted.
var jsonString = $"[\"{string.Join("\",\"", rawValues)}\"]";

try
{
var obj = JsonConvert.DeserializeObject(jsonString, this.Descriptor.ParameterType);

actionContext.ActionArguments[paramName] = obj;
}
catch
{
//There was an error casting, the jsonString must be invalid.
//Don't set anything and the action will just receive null.
}

return Task.FromResult<object>(null);
}
}

A ParameterBindingAttribute allows us to use the clean syntax of declaring the binding right in the method signature. I decided I wanted to use [FromUriCsv] as the syntax so the class is named appropriately. The only thing to overrid is the GetBinding method in which we wire up the CsvParameterBinding class we just made.

using System.Web.Http;
using System.Web.Http.Controllers;

public class FromUriCsvAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new CsvParameterBinding(parameter);
}
}

Now to put it on the controller and use it.

[Route("WorkPlanList/{clientsId}/{date:datetime}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] List<int> clientsId, [FromUri] DateTime date)
{
//matches WorkPlanList/2,3,4/7-3-2016
}

[Route("WorkPlanList/{clientsId}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] HashSet<int> clientsId)
{
//matches WorkPlanList/2,3,4,5,2
//clientsId will only contain 2,3,4,5 since it's a HashSet the extra 2 won't be included.
}

[Route("WorkPlanList/{clientsId}/{dates}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] IEnumerable<int> clientsId, [FromUriCsv] IEnumerable<DateTime> dates)
{
//matches WorkPlanList/2,3,4/5-2-16,6-17-16,7-3-2016
}

I really liked how this turned it. It works really well for ints and dates, but failed with decimals because the period in the route really jacks it up. For now, this solves your problem very well. If decimal numbers are needed, the routing should be able to be tweaked using regex or mvc style routing. I've used a similar method to this in order to pull complex types out of header values which worked really well with [FromHeader("headerName")] syntax.

Pass array of integers to Get / ASP.NET Web API

Use a custom ModelBinder:

public class JsArrayStyleModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

if (value == null)
return null;

return new JavaScriptSerializer().Deserialize<string[]>(value.AttemptedValue);
}
}

Then register it in your Global.asax:

ModelBinders.Binders.Add(typeof(string[]), new JsArrayStyleModelBinder());

Or directly on your Action parameter:

[HttpGet]
public ActionResult Show([ModelBinder(typeof(JsArrayStyleModelBinder))] string[] indexes)

WebApi - Passing an Array of Values

Short answer

To send arrays of decimals, WebApi expects url signature like:
GET http://localhost:50668/api/test/calculate?Operand1=1.0&Operand1=2.0&Operand2=3.0&Operand2=4.0

That url will send [1.0,2.0] as Operand1 and [3.0,4.0] as Operand2.

Long answer

By calling your api using GET http://localhost:50668/api/test/calculate, you actually send nothing to your server. (aside of headers content)

If you want to send data to your server, you have (at least) 2 options:

Option 2: Use GET method if operation is idempotent
Like William Xifaras already pointed out, specify that your inputs will come from the URL so WebApi interprets properly. To do so, use [FromUri].

    [HttpGet]
[Route("calculate")]
public List<Calculation> CalculateWithGet([FromUri]decimal[] Operand1, [FromUri]decimal[] Operand2)
{
var results = new List<Calculation>();
for (var i = 0; i < Operand1.Length; i++)
{
var calculation = new Calculation();
calculation.Operand1 = Operand1[i];
calculation.Operand2 = Operand2[i];
calculation.Calculate();

results.Add(calculation);
}
return results;
}

public class Calculation
{
public decimal Operand1 { get; set; }
public decimal Operand2 { get; set; }
public decimal Result { get; set; }

public void Calculate()
{
Result = this.Operand1 + this.Operand2;
}
}

With a REST client, it should look like:
Query using GET method

With GET, data is sent via the URL

Note that if you use GET Method, the server will expect to receive inputs from the URL. You should therefore send queries like:
GET http://localhost:50668/api/test/calculate?op1=1.0&op1=2.0&op2=3.0&op2=4.0

Use POST method if operation is not idempotent

Since the operation does some server side calculation, I pretend it may not always be idempotent. If it is the case, POST might be more appropriate.

    [HttpPost]
[Route("calculate")]
public List<Calculation> CalculateWithPost(CalculationInputs inputs)
{
var results = new List<Calculation>();
for (var i = 0; i < inputs.Operand2.Length; i++)
{
var calculation = new Calculation();
calculation.Operand1 = inputs.Operand1[i];
calculation.Operand2 = inputs.Operand2[i];
calculation.Calculate();

results.Add(calculation);
}
return results;
}

public class CalculationInputs
{
public decimal[] Operand1 { get; set; }
public decimal[] Operand2 { get; set; }
}

public class Calculation
{
public decimal Operand1 { get; set; }
public decimal Operand2 { get; set; }
public decimal Result { get; set; }

public void Calculate()
{
Result = this.Operand1 + this.Operand2;
}
}

With POST, data is sent via the body

With that structure, the server expects to receive inputs from the request body. WebApi will deserialize the body if it matches the signature of your function.

With a REST client, it should look like:
Query using POST method

Sidenote

The nuget package used to get the SwaggerUI generated (printscreens) can be find here. Very useful to run adhoc tests on WebApis.

How to send integer array as a parameter from endpoint without querystring from url in asp.net core webapi

First, in general including a body in a GET request is often not considered very RESTful. It is no longer specifically "banned" by RFC, but it is not typical. That said, you can make this work in ASP.Net Core using the [FromBody] attribute.

The issue has to do with how you are formatting your JSON body. Using the signature for GetModels that you have listed above, the JSON body doesn't match the parameters. Your JSON represents a top-level object with a property Ids that is an array of int, not just an array of it (or List).

If you want to use public void GetModels([FromBody]List<int> Ids) then your JSON body should simply be an array (e.g. [349,350,351]) and nothing else (no brackets, no "Ids" property name).

If you want to use the JSON body you list above, then you need another class to use for model binding, a DTO. That DTO would look something like:

public class IdDto
{
public List<int> Ids { get; set; }
}

and then your GetModels method would look like:

    [HttpGet]
public void GetModels([FromBody] IdDto idDto)
{
var myIds = idDto.Ids;
}

Lastly, be sure that your GET request has a Content-Type set to application/json or ASP.Net will return a 415 "Unsupported Media Type".



Related Topics



Leave a reply



Submit