'Changing the parameter name Web Api model binding
I'm using Web API model binding to parse query parameters from a URL. For example, here is a model class:
public class QueryParameters
{
[Required]
public string Cap { get; set; }
[Required]
public string Id { get; set; }
}
This works fine when I call something like /api/values/5?cap=somecap&id=1
.
Is there some way I can change the name of the property in the model class but keep the query parameter name the same - for example:
public class QueryParameters
{
[Required]
public string Capability { get; set; }
[Required]
public string Id { get; set; }
}
I thought adding [Display(Name="cap")]
to the Capability
property would work, but it doesn't. Is there some type of data annotation I should use?
The controller would be have a method that looked like this:
public IHttpActionResult GetValue([FromUri]QueryParameters param)
{
// Do Something with param.Cap and param.id
}
Solution 1:[1]
You can use the Name property of the FromUri binding attribute to use query string parameters with different names to the method arguments.
If you pass simple parameters rather than your QueryParameters
type, you can bind the values like this:
/api/values/5?cap=somecap&id=1
public IHttpActionResult GetValue([FromUri(Name = "cap")] string capabilities, int id)
{
}
Solution 2:[2]
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;
}
Solution 3:[3]
I just ran into this and used a getter in my parameter class to return the bound property.
So in your example:
public class QueryParameters
{
public string cap {get; set;}
public string Capability
{
get { return cap; }
}
public string Id { get; set; }
}
Now in your Controller you can reference the Capability
property.
public IHttpActionResult GetValue([FromUri]QueryParameters param)
{
// Do Something with param.Capability,
// except assign it a new value because it's only a getter
}
Of course if you use reflection on the param
object or serialize it the cap
property will be included. Not sure why anyone would need to do that with query parameters though.
Solution 4:[4]
I spent several hours working on a robust solution to the same problem, when a one-liner will do the trick just fine:
myModel.Capability = HttpContext.Current.Request["cap"];
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Paul Taylor |
Solution 2 | Piotr Kula |
Solution 3 | Philip Holly |
Solution 4 | user2966445 |