Accept comma and dot as decimal separator [duplicate] - asp.net-mvc

This question already has answers here:
How to set decimal separators in ASP.NET MVC controllers?
(4 answers)
Closed 5 years ago.
Model binding in ASP.NET MVC is great, but it follows locale settings. In my locale decimal separator is comma (','), but users use dot ('.') too, because they are lazy to switch layouts. I want this implemented in one place for all decimal fields in my models.
Should I implement my own Value Provider (or event Model Binder) for decimal type or I've missed some simple way to do this?

Cleanest way is to implement your own model binder
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult == null ? base.BindModel(controllerContext, bindingContext) : Convert.ToDecimal(valueProviderResult.AttemptedValue);
// of course replace with your custom conversion logic
}
}
And register it inside Application_Start():
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
Credits : Default ASP.NET MVC 3 model binder doesn't bind decimal properties

To properly handle group separator, just replace
Convert.ToDecimal(valueProviderResult.AttemptedValue);
in selected answer with
Decimal.Parse(valueProviderResult.AttemptedValue, NumberStyles.Currency);

Thanks to accepted answer I ended up with the following implementation to handle float, double and decimal.
public abstract class FloatingPointModelBinderBase<T> : DefaultModelBinder
{
protected abstract Func<string, IFormatProvider, T> ConvertFunc { get; }
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null) return base.BindModel(controllerContext, bindingContext);
try
{
return ConvertFunc.Invoke(valueProviderResult.AttemptedValue, CultureInfo.CurrentUICulture);
}
catch (FormatException)
{
// If format error then fallback to InvariantCulture instead of current UI culture
return ConvertFunc.Invoke(valueProviderResult.AttemptedValue, CultureInfo.InvariantCulture);
}
}
}
public class DecimalModelBinder : FloatingPointModelBinderBase<decimal>
{
protected override Func<string, IFormatProvider, decimal> ConvertFunc => Convert.ToDecimal;
}
public class DoubleModelBinder : FloatingPointModelBinderBase<double>
{
protected override Func<string, IFormatProvider, double> ConvertFunc => Convert.ToDouble;
}
public class SingleModelBinder : FloatingPointModelBinderBase<float>
{
protected override Func<string, IFormatProvider, float> ConvertFunc => Convert.ToSingle;
}
Then you just have to set your ModelBinders on Application_Start method
ModelBinders.Binders[typeof(float)] = new SingleModelBinder();
ModelBinders.Binders[typeof(double)] = new DoubleModelBinder();
ModelBinders.Binders[typeof(decimal)] = new DecimalModelBinder();

var nfInfo = new System.Globalization.CultureInfo(lang, false)
{
NumberFormat =
{
NumberDecimalSeparator = "."
}
};
Thread.CurrentThread.CurrentCulture = nfInfo;
Thread.CurrentThread.CurrentUICulture = nfInfo;

Related

Manipulate model value before passing it to DefaultModelBinder.BindModel

Some decimal and decimal? properties in my view model are marked as "Percent" data type, along with other data annotations, for example:
[DataType("Percent")]
[Display(Name = "Percent of foo completed")]
[Range(0, 1)]
public decimal? FooPercent { get; set; }
I'd like to permit the user some flexibility in how they enter the data, i.e. with or without the percent sign, intermediate spaces, etc. But I still want to use the DefaultModelBinder behavior to get all of its functionality such as checking the RangeAttribute and adding the appropriate validation messages.
Is there a way to parse and change the model value, then pass it along? Here is what I am trying, but am getting a runtime exception. (Ignore the actual parsing logic; this is not its final form. I'm just interested in the model replacement question at this point.)
public class PercentModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.DataTypeName == "Percent")
{
ValueProviderResult result =
bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (result != null)
{
string stringValue =
(string)result.ConvertTo(typeof(string));
decimal decimalValue;
if (!string.IsNullOrWhiteSpace(stringValue) &&
decimal.TryParse(
stringValue.TrimEnd(new char[] { '%', ' ' }),
out decimalValue))
{
decimalValue /= 100.0m;
// EXCEPTION : This property setter is obsolete,
// because its value is derived from
// ModelMetadata.Model now.
bindingContext.Model = decimalValue;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
Never mind, this was a fundamental misunderstanding of where validation happens in the MVC cycle. After spending some time in the MVC source code, I see how this works.
In case it is helpful to others, here is what is working for me:
[DataType("Percent")]
[Display(Name = "Percent of foo completed")]
[Range(0.0d, 1.0d, ErrorMessage="The field {0} must be between {1:P0} and {2:P0}.")]
public decimal? FooPercent { get; set; }
And in the binder, you just return the value:
public class PercentModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.DataTypeName == "Percent")
{
ValueProviderResult result =
bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (result != null)
{
string stringValue =
(string)result.ConvertTo(typeof(string));
decimal decimalValue;
if (!string.IsNullOrWhiteSpace(stringValue) &&
decimal.TryParse(
stringValue.TrimEnd(new char[] { '%', ' ' }),
out decimalValue))
{
return decimalValue / 100.0m;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}

Custom model binding issue

In my MVC 3 solution I want to have all Ids in querystring to be crypted. To decrypt URLs I inherited from DefaultModelBinder and overrided BindProperty method:
public class CryptedIdBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name.ToLower() == "id")
{
propertyDescriptor.SetValue(bindingContext.Model, CryptoHelper.Decrypt(controllerContext.HttpContext.Request.Form["id"]));
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
After that I set new DefaultBinder in global.asax on Application_Start:
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new CryptedIdBinder();
I didn't inherit from IModelBinder because I want to change binding logic only for id fields in solution.
The issue is that BindProperty method is never called. What am I doning wrong?
PS. In order to be sure that I call at least BindModel method I added a peace of this code inside my custom binder, and it was hit by the debugger:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return base.BindModel(controllerContext, bindingContext);
}
If your models don't have Id properties of course the BindProperty won't be called. Because it called on the model properties. If I understood your question what you need is to transform each Id named query string parameter. In this case you need a custom value provider instead of a modelbinder. This is good article about the value providers. And it's quite easy to write one:
public class MyValueProviderFacotry : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new MyValueProvider(controllerContext);
}
}
public class MyValueProvider : IValueProvider
{
private ControllerContext controllerContext;
public MyValueProvider(ControllerContext controllerContext)
{
this.controllerContext = controllerContext;
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
if (key.ToLower() == "id")
{
var originalValue = controllerContext.HttpContext.Request.QueryString[key];
var transformedValue = CryptoHelper.Decrypt(orignalValue );
var result = new ValueProviderResult(transformedValue,originalValue,CultureInfo.CurrentCulture);
return result;
}
return null;
}
}
In global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(4, new MyValueProviderFacotry()); //Its need to be inserted before the QueryStringValueProviderFactory
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

Use custom ASP.NET MVC IValueProvider, without setting it globally?

I want to be able to grab keys/values from a cookie and use that to bind a model.
Rather than building a custom ModelBinder, I believe that the DefaultModelBinder works well out of the box, and the best way to choose where the values come from would be to set the IValueProvider that it uses.
To do this I don't want to create a custom ValueProviderFactory and bind it globally, because I only want this ValueProvider to be used in a specific action method.
I've built an attribute that does this:
/// <summary>
/// Replaces the current value provider with the specified value provider
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class SetValueProviderAttribute : ActionFilterAttribute
{
public SetValueProviderAttribute(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
private Type _ValueProviderType;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
IValueProvider valueProviderToAdd = GetValueProviderToAdd();
filterContext.Controller.ValueProvider = valueProviderToAdd;
}
private IValueProvider GetValueProviderToAdd()
{
return (IValueProvider)Activator.CreateInstance(_ValueProviderType);
}
}
Unfortunately, the ModelBinder and its IValueProvider are set BEFORE OnActionExecuting (why?????). Has anyone else figured out a way to inject a custom IValueProvider into the DefaultModelBinder without using the ValueProviderFactory?
You should still use a ValueProviderFactory in this case.
The method that you have to implement on your ValueProviderFactory has this signature:
IValueProvider GetValueProvider(ControllerContext controllerContext)
Within your implementation of that method you can inspect the controller context, and if the incoming request is for the controller/action that you want to leverage cookies on, return some CustomCookieValueProvider.
If you don't want to leverage cookies for the request, just return null and the framework will filter that out of from the list of Value Providers.
As a bonus, you might not want to hard code the logic for when to use the CustomCookieValueProvider into the ValueProviderFactory. You could, perhaps, leverage DataTokens to match when to use cookies with given routes. So add a route like this:
routes.MapRoute("SomeRoute","{controller}/{action}").DataTokens.Add("UseCookies", true);
Notice the DataTokens.Add() call in there, now inside you GetValueProvider method you could do something like this:
if (controllerContext.RouteData.DataTokens.ContainsKey("UseCookies"))
{
return new CustomCookieValueProvider(controllerContext.RequestContext.HttpContext.Request.Cookies);
}
return null;
Here is an alternative that lets you specify IValueProviders as attributes against an actions parameters.
This makes the IValueProviders transient and not Global.
public interface IControllerContextAware
{
ControllerContext ControllerContext { get; set; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class ValueProviderAttribute : CustomModelBinderAttribute
{
public Type[] ValueProviders { get; private set; }
public ValueProviderAttribute(params Type[] valueProviders)
{
if (valueProviders == null)
{
throw new ArgumentNullException("valueProviders");
}
foreach (var valueProvider in valueProviders.Where(valueProvider => !typeof(IValueProvider).IsAssignableFrom(valueProvider)))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The valueProvider {0} must be of type {1}", valueProvider.FullName, typeof(IValueProvider)), "valueProviders");
}
ValueProviders = valueProviders;
}
public override IModelBinder GetBinder()
{
return new ValueProviderModelBinder
{
ValueProviderTypes = ValueProviders.ToList(),
CreateValueProvider = OnCreateValueProvider
};
}
protected virtual IValueProvider OnCreateValueProvider(Type valueProviderType, ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProvider = (IValueProvider)Activator.CreateInstance(valueProviderType);
if (valueProvider is IControllerContextAware)
{
(valueProvider as IControllerContextAware).ControllerContext = controllerContext;
}
return valueProvider;
}
private class ValueProviderModelBinder : DefaultModelBinder
{
public IList<Type> ValueProviderTypes { get; set; }
public Func<Type, ControllerContext, ModelBindingContext, IValueProvider> CreateValueProvider { get; set; }
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviders = from type in ValueProviderTypes
select CreateValueProvider(type, controllerContext, bindingContext);
bindingContext.ValueProvider = new ValueProviderCollection(valueProviders.Concat((Collection<IValueProvider>)bindingContext.ValueProvider).ToList());
return base.BindModel(controllerContext, bindingContext);
}
}
}
This is basically the code form the ModelBinderAttribute, but with a few tweaks.
It isn't sealed and so you can alter the way in which the IValueProviders are created if need be.
Here is a simple example which looks in another field, possibly a hidden or encrypted field, and takes the data and puts it into another property.
Here is the model, which has no knowledge of the IValueProvider, but does know about the hidden field.
public class SomeModel
{
[Required]
public string MyString { get; set; }
[Required]
public string MyOtherString { get; set; }
[Required]
public string Data { get; set; }
}
THen we have the IValueProvider, in this case, my provider knows explicitly about my model, but this doesn't have to be the case.
public class MyValueProvider : IValueProvider, IControllerContextAware
{
public ControllerContext ControllerContext { get; set; }
public bool ContainsPrefix(string prefix)
{
var containsPrefix = prefix == "MyString" && ControllerContext.HttpContext.Request.Params.AllKeys.Any(key => key == "Data");
return containsPrefix;
}
public ValueProviderResult GetValue(string key)
{
if (key == "MyString")
{
var data = ControllerContext.RequestContext.HttpContext.Request.Params["Data"];
var myString = data.Split(':')[1];
return new ValueProviderResult(myString, myString, CultureInfo.CurrentCulture);
}
return null;
}
}
and then the action that ties all this together:
[HttpGet]
public ActionResult Test()
{
return View(new SomeModel());
}
[HttpPost]
public ActionResult Test([ValueProvider(typeof(MyValueProvider))]SomeModel model)
{
return View(model);
}
Figured out how to do this. First, create a custom model binder that takes a value provider type in the constructor - but inherits from default modelbinder. This allows you to use standard model binding with a custom value provider:
/// <summary>
/// Uses default model binding, but sets the value provider it uses
/// </summary>
public class SetValueProviderDefaultModelBinder : DefaultModelBinder
{
private Type _ValueProviderType;
public SetValueProviderDefaultModelBinder(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
/// <summary>
/// Before binding the model, set the IValueProvider it uses
/// </summary>
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ValueProvider = GetValueProvider();
return base.BindModel(controllerContext, bindingContext);
}
private IValueProvider GetValueProvider()
{
return (IValueProvider)Activator.CreateInstance(_ValueProviderType);
}
}
Then we create a model binding attribute that will inject the value provider type in the custom model binder created above, and use that as the model binder:
/// <summary>
/// On the default model binder, replaces the current value provider with the specified value provider. Cannot use custom model binder with this.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public class SetValueProviderAttribute : CustomModelBinderAttribute
{
// Originally, this was an action filter, that OnActionExecuting, set the controller's IValueProvider, expecting it to be picked up by the default model binder
// when binding the model. Unfortunately, OnActionExecuting occurs AFTER the IValueProvider is set on the DefaultModelBinder. The only way around this is
// to create a custom model binder that inherits from DefaultModelBinder, and in its BindModel method set the ValueProvider and then do the standard model binding.
public SetValueProviderAttribute(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
private Type _ValueProviderType;
public override IModelBinder GetBinder()
{
var modelBinder = new SetValueProviderDefaultModelBinder(_ValueProviderType);
return modelBinder;
}
}

How to pass ObjectId from MongoDB in MVC.net

I'm starting a new project with Mongo, NoRM and MVC .Net.
Before I was using FluentNHibernate so my IDs were integer, now my IDs are ObjectId. So when I have an Edit link my URL looks like this :
WebSite/Admin/Edit/23,111,160,3,240,200,191,56,25,0,0,0
And it does not bind automaticly to my controller as an ObjectId
Do you have any suggestions/best practices to work with this? Do I need to encode/decode the ID everytime?
Thanks!
Use a custom model binder like this ... (working against the offical C# MongoDB driver)
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(ObjectId), new ObjectIdModelBinder());
}
public class ObjectIdModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
{
return ObjectId.Empty;
}
return ObjectId.Parse((string)result.ConvertTo(typeof(string)));
}
}
I Use following
public class ObjectIdModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string value = controllerContext.RouteData.Values[bindingContext.ModelName] as string;
if (String.IsNullOrEmpty(value)) {
return ObjectId.Empty;
}
return new ObjectId(value);
}
}
and
protected void Application_Start()
{
......
ModelBinders.Binders.Add(typeof(ObjectId), new ObjectIdModelBinder());
}
almost forgot, make URLs from ObjectId.ToString()
I am not familiar with the ObjectId type but you could write a custom model binder that will take care of converting the id route constraint to an instance of ObjectId.
Did you know you can use the [MongoIdentifier] attribute to make any property act as the unique key?
I've been solving this issue by borrowing a technique from WordPress by having every entity also be represented by a "url slug" property and decorating that property with [MongoIdentifier].
So if I had a person named Johnny Walker I'd create a slug of "johnny-walker". You just have to make sure these url slugs stay unique and you get to keep clean urls without ugly object ids.
For Web API you can add Custom parameter binding ule in WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//...
config.ParameterBindingRules.Insert(0, GetCustomParameterBinding);
//...
}
public static HttpParameterBinding GetCustomParameterBinding(HttpParameterDescriptor descriptor)
{
if (descriptor.ParameterType == typeof(ObjectId))
{
return new ObjectIdParameterBinding(descriptor);
}
// any other types, let the default parameter binding handle
return null;
}
public class ObjectIdParameterBinding : HttpParameterBinding
{
public ObjectIdParameterBinding(HttpParameterDescriptor desc)
: base(desc)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
try
{
SetValue(actionContext, new ObjectId(actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string));
return Task.CompletedTask;
}
catch (FormatException)
{
throw new BadRequestException("Invalid ObjectId format");
}
}
}
}
And use it Without any additional attributes in controller:
[Route("{id}")]
public IHttpActionResult Get(ObjectId id)

asp.net MVC 1.0 and 2.0 currency model binding

I would like to create model binding functionality so a user can enter ',' '.' etc for currency values which bind to a double value of my ViewModel.
I was able to do this in MVC 1.0 by creating a custom model binder, however since upgrading to MVC 2.0 this functionality no longer works.
Does anyone have any ideas or better solutions for performing this functionality? A better solution would be to use some data annotation or custom attribute.
public class MyViewModel
{
public double MyCurrencyValue { get; set; }
}
A preferred solution would be something like this...
public class MyViewModel
{
[CurrencyAttribute]
public double MyCurrencyValue { get; set; }
}
Below is my solution for model binding in MVC 1.0.
public class MyCustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out valueResult);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueResult);
if (bindingContext.ModelType == typeof(double))
{
string modelName = bindingContext.ModelName;
string attemptedValue = bindingContext.ValueProvider[modelName].AttemptedValue;
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
try
{
result = double.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
else
{
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}
}
You might try something among the lines:
// Just a marker attribute
public class CurrencyAttribute : Attribute
{
}
public class MyViewModel
{
[Currency]
public double MyCurrencyValue { get; set; }
}
public class CurrencyBinder : DefaultModelBinder
{
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var currencyAttribute = propertyDescriptor.Attributes[typeof(CurrencyAttribute)];
// Check if the property has the marker attribute
if (currencyAttribute != null)
{
// TODO: improve this to handle prefixes:
var attemptedValue = bindingContext.ValueProvider
.GetValue(propertyDescriptor.Name).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
return base.GetPropertyValue(
controllerContext,
bindingContext, propertyDescriptor,
propertyBinder
);
}
}
public class HomeController: Controller
{
[HttpPost]
public ActionResult Index([ModelBinder(typeof(CurrencyBinder))] MyViewModel model)
{
return View();
}
}
UPDATE:
Here's an improvement of the binder (see TODO section in previous code):
if (!string.IsNullOrEmpty(bindingContext.ModelName))
{
var attemptedValue = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
In order to handle collections you will need to register the binder in Application_Start as you will no longer be able to decorate the list with the ModelBinderAttribute:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(MyViewModel), new CurrencyBinder());
}
And then your action could look like this:
[HttpPost]
public ActionResult Index(IList<MyViewModel> model)
{
return View();
}
Summarizing the important part:
bindingContext.ValueProvider.GetValue(bindingContext.ModelName)
A further improvement step of this binder would be to handle validation (AddModelError/SetModelValue)

Resources