Search code examples
c#asp.net-coremodel-bindingdatacontract

How to use DataMember Name for [FromQuery] object Asp.Net core, ModelBinding


For [FromBody] parameters I can use DataMember.Name to set custom name of property, but it doesn't work for [FromQuery]. I guess it depends on model binding

I want to process query like ?status=a&status=b&status=c

with query object [FromQuery]MyQuery

[DataContract]
class MyQuery {
     [DataMember(Name = "status")
     public IReadOnlyList<string> Statuses { get; set; }
}

I can do it like

class MyQuery {
     [FromQuery("status")
     public IReadOnlyList<string> Statuses { get; set; }
}

but I would like to avoid model dependency from AspNetCore, is there any solutions?

(there is similar question about Web API 2 but it has not answer)


Solution

  • I didn't find way do to it with standart attributes, so I use statuses name, use custom attribute for string collections

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    internal class StringCollectionAttribute : Attribute
    {
    }
    

    and use custom model binder

    public class StringCollectionBinderProvider : IModelBinderProvider
    {
        private static readonly Type BinderType = typeof(StringCollectionBinder);
        private static readonly Type ModelType = typeof(List<string>);
    
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            var propertyAttributes = (context.Metadata as DefaultModelMetadata)?.Attributes.PropertyAttributes;
    
            var isStringCollection =
                propertyAttributes?.Any(x => x is StringCollectionAttribute) == true
                && context.Metadata.ModelType.IsAssignableFrom(ModelType);
    
            return isStringCollection ? new BinderTypeModelBinder(BinderType) : null;
        }
    }
    
    public class StringCollectionBinder : IModelBinder
    {
        private const char ValueSeparator = ',';
    
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
    
            var modelName = bindingContext.ModelName;
            var valueCollection = bindingContext.ValueProvider.GetValue(modelName);
    
            if (valueCollection == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }
    
            bindingContext.ModelState.SetModelValue(modelName, valueCollection);
    
            var stringCollection = valueCollection.FirstValue;
    
            if (string.IsNullOrEmpty(stringCollection))
            {
                return Task.CompletedTask;
            }
    
            var collection = stringCollection.Split(ValueSeparator).ToList();
            bindingContext.Result = ModelBindingResult.Success(collection);
            return Task.CompletedTask;
        }
    }