4

I am using .net core 3+ web api.

Below is how my action looks like below, it uses HTTP GET and I want to pass few fields and one of the fields is a list of integers.

[HttpGet]
[Route("cities")]
public ActionResult<IEnumerable<City>> GetCities([FromQuery] CityQuery query)
{...}

and here is CityQuery class -

public class CityQuery
{
    [FromQuery(Name = "stateids")]
    [Required(ErrorMessage = "stateid is missing")]
    public string StateIdsStr { get; set; }

    public IEnumerable<int> StateList    
    {
        get
        {
            if (!string.IsNullOrEmpty(StateIdsStr))
            {
                var output = StateIdsStr.Split(',').Select(id =>
                {
                    int.TryParse(id, out var stateId);
                    return stateId;
                }).ToList();
                return output;
            }
            return new List<int>();
        }
    }
}

Is there a generic way I can use to accept list of integers as input and not accept string and then parse it?

Or is there a better way to do this? I tried googling but could not find much. Thanks in advance.

Yasser Shaikh
  • 46,934
  • 46
  • 204
  • 281
  • There's no convention on passing value arrays in query parameters. Each language and frameworks uses its own conventions. Have you tried `[FromUri] int[] stateList` ? How do you want to pass those parameters? – Panagiotis Kanavos Jul 09 '20 at 09:04
  • ASP.NET Core's convention is to allow multiple parameter instances, eg `ids=1&ids=2&ids=3` and bind them to a single `ids` array – Panagiotis Kanavos Jul 09 '20 at 09:12

3 Answers3

7

This can help

    [HttpGet]
    [Route("cities")]
    public ActionResult<IEnumerable<City>> GetCities([FromQuery] int[] stateids)
    {
        ...
    }

but the query string will change to https://localhost/api/controller/cities?stateids=1&stateids=2&stateids=3

If you required comma separated query string with integer, you can go for Custom model binder

https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1

Anand
  • 717
  • 6
  • 20
4

You can use custom model binding, below is a working demo:

Model:

public class CityQuery
{
    public List<int> StateList{ get; set; }
}

CustomModelBinder:

public class CustomModelBinder: IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var values = bindingContext.ValueProvider.GetValue("stateids");

        if (values.Length == 0)
        {
            return Task.CompletedTask;
        }

        var splitData = values.FirstValue.Split(',');
        var result = new CityQuery()
        {
            StateList = new List<int>()
        };

        foreach(var id in splitData)
        {
            result.StateList.Add(int.Parse(id));
        }
        bindingContext.Result = ModelBindingResult.Success(result);
        return Task.CompletedTask;
    }
}

Applying ModelBinding Attribute on Action method:

[HttpGet]
[Route("cities")]
public ActionResult GetCities([ModelBinder(BinderType = typeof(CustomModelBinder))] CityQuery query)
{
    return View();
}

when the url like /cities?stateids=1,2,3, the stateids will be filled to StateList

mj1313
  • 7,930
  • 2
  • 12
  • 32
0

I think you just need to use [FromUri] before int array parameter :

public ActionResult<IEnumerable<City>> GetCities([FromUri] int[] stateList)

And request would be like :

/cities?stateList=1&stateList=2&stateList=3