9

I am trying to accomplish this task in which I need to send a list of id's (integers) to a web api 2 get request.

So I've found some samples here and it even has a sample project, but it doesn't work...

Here is my web api method code:

[HttpGet]
[Route("api/NewHotelData/{ids}")]
public HttpResponseMessage Get([FromUri] List<int> ids)
{
    // ids.Count is 0
    // ids is empty...
}

and here is the URL which I test in fiddler:

http://192.168.9.43/api/NewHotelData/?ids=1,2,3,4

But the list is always empty and none of the id's are passing through to the method.

can't seem to understand if the problem is in the method, in the URL or in both...

So how this is possible to accomplish ?

Liran Friedman
  • 4,027
  • 13
  • 53
  • 96

5 Answers5

12

You'll need custom model binder to get this working. Here's simplified version you can start work with:

public class CsvIntModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var key = bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(key);
        if (valueProviderResult == null)
        {
            return false;
        }

        var attemptedValue = valueProviderResult.AttemptedValue;
        if (attemptedValue != null)
        {
            var list = attemptedValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).
                       Select(v => int.Parse(v.Trim())).ToList();

            bindingContext.Model = list;
        }
        else
        {
            bindingContext.Model = new List<int>();
        }
        return true;
    }
}

And use it this way (remove {ids} from route):

[HttpGet]
[Route("api/NewHotelData")]
public HttpResponseMessage Get([ModelBinder(typeof(CsvIntModelBinder))] List<int> ids)

If you want to keep {ids} in route, you should change client request to:

api/NewHotelData/1,2,3,4

Another option (without custom model binder) is changing get request to:

?ids=1&ids=2&ids=3
Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
9

Using custom model binder as suggested in comments is the proper way to do this. However, you can also do it the quick-and-dirty way like so:

[HttpGet]
[Route("api/NewHotelData")]
public HttpResponseMessage Get([FromUri] string ids)
{
    var separated = ids.Split(new char[] { ',' });
    List<int> parsed = separated.Select(s => int.Parse(s)).ToList();
}

First, I'm splitting the uri ids string and then I'm converting them into a list of integers using Linq. Please beware that this is missing sanity checks and will throw expections if the arguments are in incorrect format.

You call it like this: http://192.168.9.43/api/NewHotelData?ids=5,10,20

Update: Personally, I think that using the model binder for a simple thing like this is over-engineering. You need a lot of code to make thing this simple work. The code you would use in a model binder is actually very similar, you would just get a nicer syntax of the method argument. If you wrap the integer parsing into a try-catch block and return an appropriate error message in case of bad format, I don't see a reason why not to use this approach.

Jan Kalfus
  • 5,422
  • 2
  • 30
  • 38
1
[HttpGet]
[Route("api/getsomething")]
public HttpResponseMessage Get([FromUri] params int[] ids)
{
}

Usage: http GET localhost:9000/api/getsomething?ids=1&ids=5&ids=9

Pujubuju
  • 119
  • 1
  • 7
  • This works, but my requirement is to have the method called without the method name so like this: localhost:9000/api/?ids=1&ids=5&ids=9, but this doesn't work... – Liran Friedman Jul 06 '16 at 07:14
1

Apparently this could work out of the box:

http://192.168.9.43/api/NewHotelData/?ids=1&ids=2&ids=3&ids=4

That is, repeating the parameter name with the different values. But what would happen if those ids became huge and you had to include a lot of them, potentially making the URL too long?

I think it would be cleaner to just make it a POST request and be done with it, though. You wrote that you need the request to be a GET and not a POST, but why? Using POST to retrieve things is perfectly acceptable in the context of AJAX requests.

s.m.
  • 7,895
  • 2
  • 38
  • 46
  • @LiranFriedman ok, but does repeating the parameter name work? That's what's important, everything else are just my own thoughts. – s.m. Jul 06 '16 at 07:15
  • I'm not sure, beause till now no values got to the server. – Liran Friedman Jul 06 '16 at 07:25
  • 1
    In theory you shouldn't use POST for fetching data as your main goal. POST is for creating entities and PUT is for updating, while GET is for fetching. Sure you can get away with doing it this way, but what you end up doing is going against the design of the protocol. Usually the 'fetching' part of PUT and POST involves fetching the newly created / updated entity. I am not an expert on the HTTP protocol by any means, but that's the general consensus (as far as I know). – gEdringer Jun 08 '22 at 16:48
1

This might be too tacky, but one could also do something like:
In your .NET class (model):

public class TackyList
{
     public IEnumerable<int> myIntList {get; set;}
}

On the client side you'd do a post i.e: {myIntList: [4,2,0]}

Now the action/method on your controller would look something like:

public void myApiMethodThatDoesSomethingWith(TackyList tl)
{
   // here you should be able to do something like:
   if(tl != null && tl.Count > 0) // blah
}
E.P
  • 11
  • 1