Home > Software engineering >  Is there a clean way to accept an array of values in a query parameter?
Is there a clean way to accept an array of values in a query parameter?

Time:12-08

I'm trying to cleanly find a way to retrieve a list of IDs in a query parameter. i.e.

/users?ids=abc123,def4141,ggg555
/users?ids=abce123&def4141&ggg555

I want both routes to be mapped to a single endpoint in .NET Core.

If I use a string, /users?ids=abce123&ids=def4141&ids=ggg555 doesn't work properly since we'll only get the first ID, abce123

public ActionResult GetByIds(string ids) { 
   // do stuff
}

If I use a list of strings, it'll work for the second route /users?ids=abce123&def4141&ggg555 but not the first.

public ActionResult GetByIds(List<string> ids) { 
   // do stuff
}

CodePudding user response:

You need a model binder

public class ArrayModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (!bindingContext.ModelMetadata.IsEnumerableType)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }

        var providedValue = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName)
            .ToString();

        if (string.IsNullOrEmpty(providedValue))
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }

        var genericType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0]; 
        var converter = TypeDescriptor.GetConverter(genericType);
        
        var objectArray = providedValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
            .Select(x => converter.ConvertFromString(x.Trim()))
            .ToArray(); 
        
        var idsArray = Array.CreateInstance(genericType, objectArray.Length);
        objectArray.CopyTo(idsArray, 0); 
        bindingContext.Model = idsArray; 
        
        bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); 
        return Task.CompletedTask;
    }
}

And in your controller:

[HttpGet]
public async Task<IActionResult> GetUsersByIds([FromQuery][ModelBinder(BinderType = typeof(ArrayModelBinder))] IEnumerable<string> ids)
{
    // for example something like this to get the users from db
    var users = await _service.Users.GetByIdsAsync(ids, trackChanges: false);
    return Ok(users);
}

and now this will work: /users?ids=abc123,def4141,ggg555

** Credits to codemaze for this

  • Related