Web API uses a bit different mechanism for model binding than ASP.NET MVC. It uses formatters for data passed in the body and model binders for data passed in query string (as in your case). Formatters respect additional metadata attributes whereas model binders do not.
So if you passed your model in message body rather than query string, you could annotate your data as follows and it would work:
public class QueryParameters
{
[DataMember(Name="Cap")]
public string Capability { get; set; }
public string Id { get; set; }
}
You probably know about that already. To get it to work with query string parameters and therefore model binder you would have to use your own custom model binder that would actually inspect and use DataMember attributes.
The following piece of code would do the trick (although it is far from production quality):
public class MemberModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var model = Activator.CreateInstance(bindingContext.ModelType);
foreach (var prop in bindingContext.PropertyMetadata)
{
// Retrieving attribute
var attr = bindingContext.ModelType.GetProperty(prop.Key)
.GetCustomAttributes(false)
.OfType<DataMemberAttribute>()
.FirstOrDefault();
// Overwriting name if attribute present
var qsParam = (attr != null) ? attr.Name : prop.Key;
// Setting value of model property based on query string value
var value = bindingContext.ValueProvider.GetValue(qsParam).AttemptedValue;
var property = bindingContext.ModelType.GetProperty(prop.Key);
property.SetValue(model, value);
}
bindingContext.Model = model;
return true;
}
}
You will also need to indicate in your controller method that you want to use this model binder:
public IHttpActionResult GetValue([ModelBinder(typeof(MemberModelBinder))]QueryParameters param)
- EDIT - Added some more code to convert to types
In case you want to use a few more types on your class I just added a few most common types (int,bool,DateTime,etc) parsing. string is default.
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var model = Activator.CreateInstance(bindingContext.ModelType);
foreach (var prop in bindingContext.PropertyMetadata)
{
// Retrieving attribute
var attr = bindingContext.ModelType.GetProperty(prop.Key)
.GetCustomAttributes(false)
.OfType<DataMemberAttribute>()
.FirstOrDefault();
// Overwriting name if attribute present
var qsParam = (attr != null) ? attr.Name : prop.Key;
// Setting value of model property based on query string value
var value = bindingContext.ValueProvider.GetValue(qsParam).AttemptedValue;
var property = bindingContext.ModelType.GetProperty(prop.Key);
var propertyType = property.PropertyType;
object convertedValueObject;
switch (Type.GetTypeCode(propertyType))
{
case TypeCode.Empty:
case TypeCode.Object:
case TypeCode.DBNull:
case TypeCode.Char:
case TypeCode.SByte:
case TypeCode.Byte:
case TypeCode.Single:
throw new NotImplementedException($"This model binder does not support this type. {propertyType} ");
case TypeCode.Int16:
case TypeCode.UInt16:
convertedValueObject = ushort.Parse(value);
break;
case TypeCode.Int32:
case TypeCode.UInt32:
convertedValueObject = int.Parse(value);
break;
case TypeCode.Int64:
case TypeCode.UInt64:
convertedValueObject = long.Parse(value);
break;
case TypeCode.Double:
convertedValueObject = double.Parse(value);
break;
case TypeCode.Decimal:
convertedValueObject = decimal.Parse(value);
break;
case TypeCode.DateTime:
convertedValueObject = DateTime.Parse(value);
break;
case TypeCode.Boolean:
convertedValueObject = bool.Parse(value);
break;
default:
convertedValueObject = value;
break;
}
property.SetValue(model, convertedValueObject);
}
bindingContext.Model = model;
return true;
}