I have this API method:
[HttpGet("foo")]
public IActionResult Foo([FromQuery] bool parameter)
{
// ...
}
And I know I can call my method like this and it will work:
.../foo?parameter=true
But I also want to support numeric values 0 and 1 and call my method like this:
.../foo?parameter=1
But when I try, I get this exception inside System.Private.CoreLib
System.FormatException: 'String '1' was not recognized as a valid Boolean.'
Is this even possible?
Because the default ModelBinder didn't support 1 to bool, we can try to create our own binding logic by implementing IModelBinder interface.
public class BoolModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
if (int.TryParse(value, out var intValue)) {
bindingContext.Result = ModelBindingResult.Success(intValue == 1);
} else if (bool.TryParse(value, out var boolValue)) {
bindingContext.Result = ModelBindingResult.Success(boolValue);
} else if (string.IsNullOrWhiteSpace(value)) {
bindingContext.Result = ModelBindingResult.Success(false);
}
return Task.CompletedTask;
}
}
then we can use ModelBinderAttribute to assign our BoolModelBinder type as this binder.
[HttpGet("foo")]
public IActionResult Foo([ModelBinder(typeof(BoolModelBinder))] bool parameter)
{
// ...
}
the easiest way is to change an input parameter type to string
[HttpGet("foo")]
public IActionResult Foo([FromQuery] string parameter)
{
if( parameter=="true" || parameter=="1") ....
else if( parameter=="false" || parameter=="0") ....
else ....
}
Related
I want to apply some preprocessing to raw data before it assigned to model properties. Namely to replace comma with dot to allow converting both this strings "324.32" and "324,32" into double. So I wrote this model binder
public class MoneyModelBinder: IModelBinder
{
private readonly Type _modelType;
public MoneyModelBinder(Type modelType)
{
_modelType = modelType;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
string modelName = bindingContext.ModelName;
ValueProviderResult providerResult = bindingContext.ValueProvider.GetValue(modelName);
if (providerResult == ValueProviderResult.None)
{
return TaskCache.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, providerResult);
string value = providerResult.FirstValue;
if (string.IsNullOrEmpty(value))
{
return TaskCache.CompletedTask;
}
value = value.Replace(",", ".");
object result;
if(_modelType == typeof(double))
{
result = Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
else if(_modelType == typeof(decimal))
{
result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
}
else if(_modelType == typeof(float))
{
result = Convert.ToSingle(value, CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException($"binder doesn't implement this type {_modelType}");
}
bindingContext.Result = ModelBindingResult.Success(result);
return TaskCache.CompletedTask;
}
}
then appropriate provider
public class MoneyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if(context.Metadata?.ModelType == null)
{
return null;
}
if (context.Metadata.ModelType.In(typeof(double), typeof(decimal), typeof(float)))
{
return new MoneyModelBinder(context.Metadata.ModelType);
}
return null;
}
}
and registering it inside Startup.cs
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MoneyModelBinderProvider());
});
but I noticed some strange behavior or maybe I missed something. If I use this kind of action
public class Model
{
public string Str { get; set; }
public double Number { get; set; }
}
[HttpPost]
public IActionResult Post(Model model)
{
return Ok("ok");
}
and supply parameters inside query string everything works fine: first provider is called for model itself then for every property of the model. But if I use [FromBody] attribute and supply parameters by JSON, provider is called for model but never called for properties of this model. But why? How can I use binders with FromBody?
I've found solution. As it described here [FromBody] behaves differently in comparing to other value providers - it converts complex objects all at once via JsonFormatters. So in addition model binders we should write separate logic just for FromBody. And of course we can catch some points during json processing:
public class MoneyJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(double);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = (reader.Value ?? "").Replace(" ", "").Replace(",", ".");
TypeConverter converter = TypeDescriptor.GetConverter(modelType);
object result = converter.ConvertFromInvariantString(value);
return result;;
}
}
and using
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MoneyModelBinderProvider());
}).AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new MoneyJsonConverter());
});
I'm trying to grab the "status" and "all" key, value from the requested URL, and can't figure out how to build my class object.
The JSON API specification I'm referring to can be found here:
http://jsonapi.org/recommendations/#filtering
// requested url
/api/endpoint?filter[status]=all
// my attempt at model binding
public class FilterParams
{
public Dictionary<string, string> Filter { get; set; }
}
[HttpGet]
public string Get([FromUri] FilterParams filter)
{
// never gets populated...
var filterStatus = filter.Filter["status"];
}
If you're building json:api apps on .Net Core, I strongly recommend checking out this library: https://github.com/json-api-dotnet/JsonApiDotNetCore
It handles all of the heavy lifting for you and for this specific example, (you need to get the filter value) the solution looks like:
public FooController : JsonApiController<Foo> {
private readonly IQueryAccessor _queryAccessor;
public FooController(IQueryAccessor queryAccessor, /* ... */)
: base(/* ... */) {
_queryAccessor = queryAccessor;
}
[HttpGet]
public override async Task<IActionResult> GetAsync() {
var status = _queryAccessor.GetRequired<string>("status");
// ...
}
}
You could use IModelBinder for that:
Define a model binder:
public class FilterParamsModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(FilterParams)) return false;
Dictionary<string, string> result = new Dictionary<string, string>();
var parameters = actionContext.Request.RequestUri.Query.Substring(1);
if(parameters.Length == 0) return false;
var regex = new Regex(#"filter\[(?<key>[\w]+)\]=(?<value>[\w^,]+)");
parameters
.Split('&')
.ToList()
.ForEach(_ =>
{
var groups = regex.Match(_).Groups;
if(groups.Count == 0)
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value.");
result.Add(groups["key"].Value, groups["value"].Value);
});
bindingContext.Model = new FilterParams { Filter = result};
return bindingContext.ModelState.IsValid;
}
}
Use it:
[HttpGet]
public string Get([ModelBinderAttribute(typeof(FilterParamsModelBinder))] FilterParams filter)
{
//your code
}
If you could define a route like "/api/endpoint?filter=status,all" instead, than you could use a TypeConverter for that:
Define a converter:
public class FilterConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string)) return base.ConvertFrom(context, culture, value);
var keyValue = ((string)value).Split(',');
return new FilterParams
{
Filter = new Dictionary<string, string> { [keyValue[0]] = keyValue[1] }
};
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
}
Use it:
[TypeConverter(typeof(FilterConverter))]
public class FilterParams
{
public Dictionary<string, string> Filter { get; set; }
}
[HttpGet]
public string Get(FilterParams filter)
{
var filterStatus = filter.Filter["status"];
}
I'm having trouble unit testing ASP.NET Core MVC controllers that return anonymous objects. The unit testing is set up in a separate project and calls the controller methods from the main project directly.
The controller methods return IActionResult but typically these are OkObjectResult and BadRequestObjectResult objects that get translated into a JSON response with the appropriate HTTP status code. The anonymous objects are passed as the constructor parameters for the ObjectResult objects and it is these I'm trying to make assertions against (accessible via ObjectResult.Value).
I found this question (how can i access internals in asp.net 5) that has an answer that says to use dynamics and add
[assembly: InternalsVisibleTo("Namespace")]
to AssemblyInfo.cs to allow the test project access to the internal object properties of the anonymous objects. However, latest versions of ASP.NET Core MVC do not have AssemblyInfo.cs and adding one as suggested in the answers to the linked question does not work either.
Is there now a different location to add the InternalsVisibleTo or am I missing something?
Original idea from this answer with a more generic approach. Using a custom DynamicObject as a wrapper for inspecting the value via reflection there was no need to add the InternalsVisibleTo
public class DynamicObjectResultValue : DynamicObject, IEquatable<DynamicObjectResultValue> {
private readonly object value;
public DynamicObjectResultValue(object value) {
this.value = value;
}
#region Operators
public static bool operator ==(DynamicObjectResultValue a, DynamicObjectResultValue b) {
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b)) {
return true;
}
// If one is null, but not both, return false.
if (ReferenceEquals((object)a, null) || ReferenceEquals((object)b, null)) {
return false;
}
// Return true if the fields match:
return a.value == b.value;
}
public static bool operator !=(DynamicObjectResultValue a, DynamicObjectResultValue b) {
return !(a == b);
}
#endregion
public override IEnumerable<string> GetDynamicMemberNames() {
return value.GetType().GetProperties().Select(p => p.Name);
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
//initialize value
result = null;
//Search possible matches and get its value
var property = value.GetType().GetProperty(binder.Name);
if (property != null) {
// If the property is found,
// set the value parameter and return true.
var propertyValue = property.GetValue(value, null);
result = propertyValue;
return true;
}
// Otherwise, return false.
return false;
}
public override bool Equals(object obj) {
if (obj is DynamicObjectResultValue)
return Equals(obj as DynamicObjectResultValue);
// If parameter is null return false.
if (ReferenceEquals(obj, null)) return false;
// Return true if the fields match:
return this.value == obj;
}
public bool Equals(DynamicObjectResultValue other) {
// If parameter is null return false.
if (ReferenceEquals(other, null)) return false;
// Return true if the fields match:
return this.value == other.value;
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
return string.Format("{0}", value);
}
}
Assuming the following controller
public class FooController : Controller {
public IActionResult GetAnonymousObject() {
var jsonResult = new {
id = 1,
name = "Foo",
type = "Bar"
};
return Ok(jsonResult);
}
public IActionResult GetAnonymousCollection() {
var jsonResult = Enumerable.Range(1, 20).Select(x => new {
id = x,
name = "Foo" + x,
type = "Bar" + x
}).ToList();
return Ok(jsonResult);
}
}
Tests could look like
[TestMethod]
public void TestDynamicResults() {
//Arrange
var controller = new FooController();
//Act
var result = controller.GetAnonymousObject() as OkObjectResult;
//Assert
dynamic obj = new DynamicObjectResultValue(result.Value);
Assert.IsNotNull(obj);
Assert.AreEqual(1, obj.id);
Assert.AreEqual("Foo", obj.name);
Assert.AreEqual(3, obj.name.Length);
Assert.AreEqual("Bar", obj.type);
}
[TestMethod]
public void TestDynamicCollection() {
//Arrange
var controller = new FooController();
//Act
var result = controller.GetAnonymousCollection() as OkObjectResult;
//Assert
Assert.IsNotNull(result, "No ActionResult returned from action method.");
dynamic jsonCollection = result.Value;
foreach (dynamic value in jsonCollection) {
dynamic json = new DynamicObjectResultValue(value);
Assert.IsNotNull(json.id,
"JSON record does not contain \"id\" required property.");
Assert.IsNotNull(json.name,
"JSON record does not contain \"name\" required property.");
Assert.IsNotNull(json.type,
"JSON record does not contain \"type\" required property.");
}
}
I'm trying to understand a way to pass in two or more extension methods to a another method as parameters and return the value back. I have extension methods for each datatype to return a value or default value and a value or a null value and also a value or throw an error. The code has scenarios that would require each of these, but it also has scenarios combining the results from each of these in a ternary test, examples below.
public static int IntOrError(this object val, string fieldName)
{
int test;
if (string.IsNullOrEmpty(val.ToString()))
{
throw new Exception("I threw it");
}
else if (int.TryParse(val.ToString(), out test) == false)
{
throw new Exception("Bad Int Value");
}
else
{
return test;
}
}
public static int IntOrDefault(this object val)
{
int test;
if (int.TryParse(val.ToString(), out test))
{
return test;
}
else
{
return -1;
}
}
public static int? IntOrNull(this object val)
{
int test;
if (int.TryParse(val.ToString(), out test))
{
return test;
}
else
{
return -1;
}
}
I've been trying to make a reusable method that could process taking in in this example IntOrNull, IntOrDefault and IntOrError and pass back the int or throw an error. So far I've only been able to get this to work. I'm trying to avoid creating this method for every datatype also.
public static int IntDefaultValidated(this object val, string fieldName)
{
return val.IntOrNUll() != null
? val.IntOrDefaultError(fieldName)
: val.IntOrDefault();
}
I am trying to get a generic method or a functional style of method that will take in the extension methods as params and return back the value.
I'm hoping to avoid reflection if possible.
//just a psuedo example
var intVal = "";
var valRet = DefaultValidated(intVal.IntOrNull(), intVal.IntOrdefault(), intVal.IntOrDefaultError("intVal"));
//or maybe like this, or some combination of extension, generic, functional
var valRet = intVal.DefaultOrValidated(IntOrNull(intVal), IntOrDefault(intVal), IntOrDefaultError(intVal, "intVal"));
Your logic of IntOrDefault, IntOrNull and IntDefaultValidated does not really make sense, so I think it's just an usage example.
Beside this - my advice is to implement your functions as generic extensions.
public static class MyExtensions
{
public static T ValueOrError<T>(this object val)
{
try
{
// http://stackoverflow.com/a/8633/2534462
return (T)Convert.ChangeType(val, typeof(T));
} catch
{
throw new Exception("Throw your own exception if you really want to");
}
}
public static T ValueOrDefault<T>(this object val, T defaultValue)
{
try
{
return val.ValueOrError<T>();
}
catch
{
return defaultValue; // usally use: return default(T);
}
}
public static T ValueOrNull<T>(this object val)
{
try
{
return val.ValueOrError<T>();
}
catch
{
// check for nullable type
//https://msdn.microsoft.com/de-de/library/ms366789.aspx
var type = typeof(T);
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return default(T); // null on nullable types
} else {
throw new Exception("Callable only on Nullable types");
// my return another default value ??? .. -1 ???
}
}
}
public static T ValueDefaultValidated<T>(this object val, T defaultValue)
{
return val.ValueOrNull<T>() != null
? val.ValueOrError<T>()
: val.ValueOrDefault<T>(defaultValue);
}
}
Usage
string aNumber = "10";
var intNumber = aNumber.ValueDefaultValidated(-1); // int
var decNumber = aNumber.ValueDefaultValidated(-1m); // decimal
string naNumber = "whatever";
var defaultInt = naNumber.ValueOrDefault(-1); // int
var defaultDecimal = naNumber.ValueDefaultValidated<decimal?>(-1m);
// ValueOrNull ist undefined on Non-Nullable-Type.
var undefined = naNumber.ValueDefaultValidated<decimal>(-1m);
Something like this?
public static T Convert<T>(this object input)
{
if (typeof (T) == input.GetType())
return (T) input;
var stringValue = input.ToString();
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter.CanConvertFrom(typeof(string)))
{
return (T)converter.ConvertFrom(stringValue);
}
return default(T);
}
This test passes. May be this is not exactly what you need but still.
[Fact]
public void TestMethod1()
{
object testInt = 1;
object testString = "123";
double testDouble = 1.0;
string testWrong = "abc";
int resultInt = testInt.Convert<int>();
string resultString = testString.Convert<string>();
int resultIntString = testString.Convert<int>();
int dbl2int = testDouble.Convert<int>();
Assert.Throws<Exception>(() => testWrong.Convert<int>());
Assert.Equal(1, resultInt);
Assert.Equal("123", resultString);
Assert.Equal(123, resultIntString);
Assert.Equal(1, dbl2int);
}
I have the following problem with the Asp.net Web Api.
I try to use the following object as parameter of my action
[DataContract]
public class MyObject
{
[DataMember]
public object MyIdProperty {get;set;}
}
the property MyIdProperty can contains either a Guid or an Int32
In MVC I did an ModelBinder and it works like a charm so I did one for the WebApi like this
public class HttpObjectIdPropertyModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != ObjectType
|| (bindingContext.ModelName.TrimHasValue()
&& !bindingContext.ModelName.EndsWith("Id", StringComparison.OrdinalIgnoreCase)))
{
return false;
}
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null || result.RawValue == null || result.RawValue.GetType() == ObjectType)
{
bindingContext.Model = null;
return true;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, result);
string stringValue = result.RawValue as string;
if (stringValue == null)
{
string[] stringValues = result.RawValue as string[];
if (stringValues != null && stringValues.Length == 1)
{
stringValue = stringValues[0];
}
if (stringValue == null)
{
return false;
}
}
Guid guid;
int integer;
if (Guid.TryParse(stringValue, out guid))
{
bindingContext.Model = guid;
}
else if (int.TryParse(stringValue, out integer))
{
bindingContext.Model = integer;
}
else
{
return false;
}
return true;
}
private static readonly Type ObjectType = typeof(object);
private static HttpParameterBinding EvaluateRule(HttpObjectIdPropertyModelBinder binder, HttpParameterDescriptor parameter)
{
if (parameter.ParameterType == ObjectType
&& parameter.ParameterName.EndsWith("Id", StringComparison.OrdinalIgnoreCase))
{
return parameter.BindWithModelBinding(binder);
}
return null;
}
public static void Register()
{
var binder = new HttpObjectIdPropertyModelBinder();
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, new SimpleModelBinderProvider(typeof(object), binder));
GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, param => EvaluateRule(binder, param));
}
}
It's the first time I'm doing a model binder for the WebApi so I'm not even sure if I'm doing it well and if it's a good way to fix this problem.
Anyway with this model binder if I have an action like this
public IEnumerable<MyObject> Get(object id)
{
// code here...
}
The parameter id is deserialized properly using the Json formatter or the Xml formatter and the model binder
But if I use the following action
public void Post(MyObject myObject)
{
// code here...
}
The parameter myObject is deserialized perfectly when I use the Xml formatter but when I use the Json formatter the property MyIdProperty contains a string instead of a Guid or Int32.
And in both case my model binder is NOT used at all. It's like it stop the evaluation of the model at the action paramaters compare to MVC that use the model binders for each property with a complex type.
Note: I would like to not use the true type or use a internal or protected property with the true type because I have this kind of property in a lot of different of object and the code will become really hard to maintain if I have to duplicate them each time