Bind array of values in ASP.NET Core

Hey there! There are often times when you want in a controller action to bind a set of values, more specifically an array. So if you have the following situation:


HttpGet("byIds/{ids}")]
public async Task GetByIds(int[] ids)

You’ll notice that ids will always be null. This happens because there’n no model binder for arrays in ASP.NET Core. That’s why we are going to build one that works for primitive types separated by commas. If you need more adjustments, it’s then easy to adapt the code to your needs.

The code has been adapted from this blog post to ASP.NET Core 2.2. So let’s get straight to business:


public class CommaSeparatedArrayModelBinder : IModelBinder
{
    private static readonly Type] supportedElementTypes = {
        typeof(int), typeof(long), typeof(short), typeof(byte),
        typeof(uint), typeof(ulong), typeof(ushort), typeof(Guid)
    };
    private static Array CopyAndConvertArray(IReadOnlyList sourceArray, Type elementType)
    {
        var targetArray = Array.CreateInstance(elementType, sourceArray.Count);
        if (sourceArray.Count > 0)
        {
            var converter = TypeDescriptor.GetConverter(elementType);
            for (var i = 0; i < sourceArray.Count; i++)
                    targetArray.SetValue(converter.ConvertFromString(sourceArray[i]), i);
        }
        return targetArray;
    }

    internal static bool IsSupportedModelType(Type modelType)
    {
        return modelType.IsArray && modelType.GetArrayRank() == 1
                && modelType.HasElementType
                && supportedElementTypes.Contains(modelType.GetElementType());
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (!IsSupportedModelType(bindingContext.ModelType))
        {
            return Task.CompletedTask;
        }

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        var stringArray = valueProviderResult.Values.FirstOrDefault()
                ?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

        if (stringArray == null)
        {
            return Task.CompletedTask;
        }

        var elementType = bindingContext.ModelType.GetElementType();
        if (elementType == null)
        {
            return Task.CompletedTask;
        }

        bindingContext.Result = ModelBindingResult.Success(CopyAndConvertArray(stringArray, elementType));

        return Task.CompletedTask;
    }
}

I think the implementation for BindModelAsync method is pretty straightforward. The interesting method is the CopyAndConvertArray which uses activators to create an instance of an array based on the requested type but also uses a converter to convert the values of the array from string to the required type. This is actually very useful, because it keeps the code clean without lots of switch cases ๐Ÿ˜Š

Good, having this class ready, we just need a few things to have. First we need the model binder provider, which looks like so:


public class CommaSeparatedArrayModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        return CommaSeparatedArrayModelBinder.IsSupportedModelType(context.Metadata.ModelType)
                ? new CommaSeparatedArrayModelBinder() : null;
    }
}

This class will actually decide whether to use our custom model binder or not. The next step is to register our model binder provider in ConfigureServices method in Startup:


services.AddMvc(options => 
{
     options.ModelBinderProviders.Insert(0, new CommaSeparatedArrayModelBinderProvider());
})

So this is quite about it, now your action should receive the parameter correctly. Hope this is useful for you!

Thanks for reading, I hope you found this article useful and interesting. If you have any suggestions don't hesitate to contact me. I also invite you to share and subscribe to the newsletter by using the buttons below! Cheersย ย ๐Ÿ˜‰

How useful was this post?

Click on a star to rate it!

Average rating / 5. Vote count:

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Leave a Reply

avatar
  Subscribe  
Notify of