I have a f:convertDateTime with a pattern of mm/dd/yyyy. However, people are able to enter 2/19/78 and it would be 0078 rather then 1978 or 2078. I want to force people to enter in all 4 digits.
I tried using a regexPattern validator, but that is complaining because it wants a string and not a Date object. Seems that the converters fire first and validators validate the converted value?
I guess I could write a custom converter or validator, but this seems like such a simple thing I figure I'm doing something wrong.
The javadocs for the convertor say it is strict in matching the pattern, but I'm not seeing that?
Any ideas or suggestions?
thanks!
It's only strict for days/months, not for years. Here's an extract of relevance from SimpleDateFormat javadoc which <f:convertDateTime> is using under the covers:
For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
It's by design indeed not possible to fire validators before converters. Essentially, this one should have thrown a ConverterException because the input is not in the proper format. I'd create a custom converter which validates the pattern beforehand. Something like this:
#FacesConverter("validatingPatternDateTimeConverter")
public class ValidatingPatternDateTimeConverter extends DateTimeConverter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
String regex = getMandatoryAttribute(component, "validateRegex");
String pattern = getMandatoryAttribute(component, "convertPattern");
if (value != null && !value.matches(regex)) {
throw new ConverterException(new FacesMessage(String.format("Invalid date, must be in pattern %s", pattern)));
}
setPattern(pattern);
return super.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
setPattern(getMandatoryAttribute(component, "convertPattern"));
return super.getAsString(context, component, value);
}
private String getMandatoryAttribute(UIComponent component, String name) {
String value = (String) component.getAttributes().get(name);
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException(String.format("<f:attribute name=\"%s\"> is missing.", name));
}
return value;
}
}
which is to be used as follows:
<h:inputText value="#{bean.date}">
<f:converter converterId="validatingPatternDateTimeConverter" />
<f:attribute name="validateRegex" value="\d{1,2}/\d{1,2}/\d{4}" />
<f:attribute name="convertPattern" value="MM/dd/yyyy" />
</h:inputText>
Related
I am creating a web application, where you have to read a list of objects / entities from a DB and populate it in a JSF <h:selectOneMenu>. I am unable to code this. Can someone show me how to do it?
I know how to get a List<User> from the DB. What I need to know is, how to populate this list in a <h:selectOneMenu>.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Based on your question history, you're using JSF 2.x. So, here's a JSF 2.x targeted answer. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem instances. This is fortunately not needed anymore in JSF 2.x.
Basic example
To answer your question directly, just use <f:selectItems> whose value points to a List<T> property which you preserve from the DB during bean's (post)construction. Here's a basic kickoff example assuming that T actually represents a String.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{bean.names}" />
</h:selectOneMenu>
with
#ManagedBean
#RequestScoped
public class Bean {
private String name;
private List<String> names;
#EJB
private NameService nameService;
#PostConstruct
public void init() {
names = nameService.list();
}
// ... (getters, setters, etc)
}
Simple as that. Actually, the T's toString() will be used to represent both the dropdown item label and value. So, when you're instead of List<String> using a list of complex objects like List<SomeEntity> and you haven't overridden the class' toString() method, then you would see com.example.SomeEntity#hashcode as item values. See next section how to solve it properly.
Also note that the bean for <f:selectItems> value does not necessarily need to be the same bean as the bean for <h:selectOneMenu> value. This is useful whenever the values are actually applicationwide constants which you just have to load only once during application's startup. You could then just make it a property of an application scoped bean.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Complex objects as available items
Whenever T concerns a complex object (a javabean), such as User which has a String property of name, then you could use the var attribute to get hold of the iteration variable which you in turn can use in itemValue and/or itemLabel attribtues (if you omit the itemLabel, then the label becomes the same as the value).
Example #1:
<h:selectOneMenu value="#{bean.userName}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>
with
private String userName;
private List<User> users;
#EJB
private UserService userService;
#PostConstruct
public void init() {
users = userService.list();
}
// ... (getters, setters, etc)
Or when it has a Long property id which you would rather like to set as item value:
Example #2:
<h:selectOneMenu value="#{bean.userId}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>
with
private Long userId;
private List<User> users;
// ... (the same as in previous bean example)
Complex object as selected item
Whenever you would like to set it to a T property in the bean as well and T represents an User, then you would need to bake a custom Converter which converts between User and an unique string representation (which can be the id property). Do note that the itemValue must represent the complex object itself, exactly the type which needs to be set as selection component's value.
<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
with
private User user;
private List<User> users;
// ... (the same as in previous bean example)
and
#ManagedBean
#RequestScoped
public class UserConverter implements Converter {
#EJB
private UserService userService;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return userService.find(Long.valueOf(submittedValue));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User ID", submittedValue)), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof User) {
return String.valueOf(((User) modelValue).getId());
} else {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
}
}
}
(please note that the Converter is a bit hacky in order to be able to inject an #EJB in a JSF converter; normally one would have annotated it as #FacesConverter(forClass=User.class), but that unfortunately doesn't allow #EJB injections)
Don't forget to make sure that the complex object class has equals() and hashCode() properly implemented, otherwise JSF will during render fail to show preselected item(s), and you'll on submit face Validation Error: Value is not valid.
public class User {
private Long id;
#Override
public boolean equals(Object other) {
return (other != null && getClass() == other.getClass() && id != null)
? id.equals(((User) other).id)
: (other == this);
}
#Override
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}
}
Complex objects with a generic converter
Head to this answer: Implement converters for entities with Java Generics.
Complex objects without a custom converter
The JSF utility library OmniFaces offers a special converter out the box which allows you to use complex objects in <h:selectOneMenu> without the need to create a custom converter. The SelectItemsConverter will simply do the conversion based on readily available items in <f:selectItem(s)>.
<h:selectOneMenu value="#{bean.user}" converter="omnifaces.SelectItemsConverter">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
See also:
Our <h:selectOneMenu> wiki page
View-Page
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
<f:selectItems value="#{page.names}"/>
</h:selectOneMenu>
Backing-Bean
List<SelectItem> names = new ArrayList<SelectItem>();
//-- Populate list from database
names.add(new SelectItem(valueObject,"label"));
//-- setter/getter accessor methods for list
To display particular selected record, it must be one of the values in the list.
Roll-your-own generic converter for complex objects as selected item
The Balusc gives a very useful overview answer on this subject. But there is one alternative he does not present: The Roll-your-own generic converter that handles complex objects as the selected item. This is very complex to do if you want to handle all cases, but pretty simple for simple cases.
The code below contains an example of such a converter. It works in the same spirit as the OmniFaces SelectItemsConverter as it looks through the children of a component for UISelectItem(s) containing objects. The difference is that it only handles bindings to either simple collections of entity objects, or to strings. It does not handle item groups, collections of SelectItems, arrays and probably a lot of other things.
The entities that the component binds to must implement the IdObject interface. (This could be solved in other way, such as using toString.)
Note that the entities must implement equals in such a way that two entities with the same ID compares equal.
The only thing that you need to do to use it is to specify it as converter on the select component, bind to an entity property and a list of possible entities:
<h:selectOneMenu value="#{bean.user}" converter="selectListConverter">
<f:selectItem itemValue="unselected" itemLabel="Select user..."/>
<f:selectItem itemValue="empty" itemLabel="No user"/>
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
Converter:
/**
* A converter for select components (those that have select items as children).
*
* It convertes the selected value string into one of its element entities, thus allowing
* binding to complex objects.
*
* It only handles simple uses of select components, in which the value is a simple list of
* entities. No ItemGroups, arrays or other kinds of values.
*
* Items it binds to can be strings or implementations of the {#link IdObject} interface.
*/
#FacesConverter("selectListConverter")
public class SelectListConverter implements Converter {
public static interface IdObject {
public String getDisplayId();
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
return component.getChildren().stream()
.flatMap(child -> getEntriesOfItem(child))
.filter(o -> value.equals(o instanceof IdObject ? ((IdObject) o).getDisplayId() : o))
.findAny().orElse(null);
}
/**
* Gets the values stored in a {#link UISelectItem} or a {#link UISelectItems}.
* For other components returns an empty stream.
*/
private Stream<?> getEntriesOfItem(UIComponent child) {
if (child instanceof UISelectItem) {
UISelectItem item = (UISelectItem) child;
if (!item.isNoSelectionOption()) {
return Stream.of(item.getValue());
}
} else if (child instanceof UISelectItems) {
Object value = ((UISelectItems) child).getValue();
if (value instanceof Collection) {
return ((Collection<?>) value).stream();
} else {
throw new IllegalStateException("Unsupported value of UISelectItems: " + value);
}
}
return Stream.empty();
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) return null;
if (value instanceof String) return (String) value;
if (value instanceof IdObject) return ((IdObject) value).getDisplayId();
throw new IllegalArgumentException("Unexpected value type");
}
}
I'm doing it like this:
Models are ViewScoped
converter:
#Named
#ViewScoped
public class ViewScopedFacesConverter implements Converter, Serializable
{
private static final long serialVersionUID = 1L;
private Map<String, Object> converterMap;
#PostConstruct
void postConstruct(){
converterMap = new HashMap<>();
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object object) {
String selectItemValue = String.valueOf( object.hashCode() );
converterMap.put( selectItemValue, object );
return selectItemValue;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String selectItemValue){
return converterMap.get(selectItemValue);
}
}
and bind to component with:
<f:converter binding="#{viewScopedFacesConverter}" />
If you will use entity id rather than hashCode you can hit a collision- if you have few lists on one page for different entities (classes) with the same id
Call me lazy but coding a Converter seems like a lot of unnecessary work. I'm using Primefaces and, not having used a plain vanilla JSF2 listbox or dropdown menu before, I just assumed (being lazy) that the widget could handle complex objects, i.e. pass the selected object as is to its corresponding getter/setter like so many other widgets do. I was disappointed to find (after hours of head scratching) that this capability does not exist for this widget type without a Converter. In fact if you supply a setter for the complex object rather than for a String, it fails silently (simply doesn't call the setter, no Exception, no JS error), and I spent a ton of time going through BalusC's excellent troubleshooting tool to find the cause, to no avail since none of those suggestions applied. My conclusion: listbox/menu widget needs adapting that other JSF2 widgets do not. This seems misleading and prone to leading the uninformed developer like myself down a rabbit hole.
In the end I resisted coding a Converter and found through trial and error that if you set the widget value to a complex object, e.g.:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... when the user selects an item, the widget can call a String setter for that object, e.g. setSelectedThing(String thingString) {...}, and the String passed is a JSON String representing the Thing object. I can parse it to determine which object was selected. This feels a little like a hack, but less of a hack than a Converter.
Hi I'm trying to create a custom component that extends UIInput. In that component I generate one html input, one html submit button and one line of text. The codes are as below:
#Override
public void decode(FacesContext context) {
Map requestMap = context.getExternalContext().getRequestParameterMap();
String clientId = getClientId(context);
char sep = UINamingContainer.getSeparatorChar(context);
String symbol = ((String) requestMap.get(clientId + sep + "inputfield"));
setSubmittedValue(symbol);
}
#Override
public void encodeEnd(FacesContext context) throws IOException {
String clientId = getClientId(context);
char sep = UINamingContainer.getSeparatorChar(context);
//-----------------------this generates an html input-------------------------
encodeInputField(context, clientId + sep + "inputfield");
//Now if I uncomment the next line it generates another html, whose value always stays the same as the first one
//encodeInputField(context, clientId + sep + "inputfield2");
encodeSubmitButton(context, clientId + sep + "submit");
encodeOutputField(context);
}
private void encodeInputField(FacesContext context, String clientId) throws IOException {
// Render a standard HTML input field
ResponseWriter writer = context.getResponseWriter();
writer.startElement("input", this);
writer.writeAttribute("type", "text", null);
writer.writeAttribute("name", clientId, "clientId");
Object value = getValue();
if (value != null) {
writer.writeAttribute("value", value.toString(), "value");
}
writer.writeAttribute("size", "6", null);
writer.endElement("input");
}
private void encodeSubmitButton(FacesContext context, String clientId) throws IOException {
// render a submit button
ResponseWriter writer = context.getResponseWriter();
writer.startElement("input", this);
writer.writeAttribute("type", "Submit", null);
writer.writeAttribute("name", clientId, "clientId");
writer.writeAttribute("value", "Click Me!", null);
writer.endElement("input");
}
private void encodeOutputField(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
//----------------weird value that comes out of nowhere-----------------------
String hellomsg = (String) getAttributes().get("value");
writer.startElement("p", this);
writer.writeText("You entered: " + hellomsg, null);
writer.endElement("p");
}
Now everything works fine but I don't understand where the value attribute in String hellomsg = (String) getAttributes().get("value"); comes. I tried to debug this program and the getAttributes() hashmap also contains weird entries and I couldn't find any entry whose key is "value".
Finally if I generate two html input then the second input's value always stays the same as the first one.
I also noticed that I can include a value in the tag, e.g. <mycc:cinput value="yes"> and when the page loads, the value of the html input generated is set to yes.
My doubt is: is every UIInput has a default value attribute? If so, is that attribute value always linked to whatever html input's value attribute? If so, is it always linked to the value attribute of the first html input generated?
Thanks in advance for reading such a long question. If possible can you guys let me know where I can find answers to issues like this? Im getting a headache browsing random google search results#_#
Thanks a lot!
Now everything works fine but I don't understand where the value attribute in String hellomsg = (String) getAttributes().get("value"); comes. I tried to debug this program and the getAttributes() hashmap also contains weird entries and I couldn't find any entry whose key is "value".
Read the javadoc of UIComponent#getAttributes(). To the point, it's really an abstract map. The get("value") doesn't really return an entry from the map, but it basically obtains the value property of the component itself by invoking its getValue() method, if any available (and it is, in case of UIInput). But if you're already sitting in the component itself, you don't need to invoke getAttributes().get("value"), you can just invoke the getValue() method directly.
Note thus that if you intend to put a custom attribute in the attribute map, then you should be using a different name, or, better, to make use of StateHelper.
See also:
JSF custom component: support for arguments of custom types, the attribute setter is never invoked
How to save state when extending UIComponentBase
This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 7 years ago.
I got this error many time.. i am using two h:selectonemenu in my JSF page, mediaList and Unitlist. while selecting any Media . my UnitList populate automatically, but some time it gives Validation Error: value is not valid;
My JSF code
<h:selectOneMenu id="media" value="#{workOrderMbean.selectedMedia}" converter="MediaConverter" onchange="submit()" valueChangeListener="#{workOrderMbean.onChangeMediaCombo}" immediate="true">
<f:selectItems value="#{workOrderMbean.mediaCombo}"/>
</h:selectOneMenu>
<h:selectOneMenu id="hUnit" value="#{workOrderMbean.selectedHeightUnit}" converter="UnitConverter" >
<f:selectItems value="#{workOrderMbean.unitCombo}"/>
</h:selectOneMenu>
onchane event of Mediacombo is
public void onChangeMediaCombo(ValueChangeEvent e) throws SearchBLException {
if (e.getNewValue() != null) {
Media media = (Media) e.getNewValue();
if (unitCombo != null && !unitCombo.isEmpty()) {
unitCombo.clear();
seclectedWidthUnit=new Unit();
selectedHeightUnit=new Unit();
}
unitCombo = ComboLoader.getUnitsComboByMediaid(media.getMediaId());
}
else
{
if (unitCombo != null && !unitCombo.isEmpty()) {
unitCombo.clear();
seclectedWidthUnit=null;
selectedHeightUnit=null;
}
unitCombo = ComboLoader.getUnitsComboByMediaid(-1);
}
}
i am also using converter for 'Unit'
my media converter is
#FacesConverter(value = "MediaConverter")
public class MediaConverter implements Converter{
MediaDAO mediadao=new MediaDAOImpl();
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Media media=null;
try {
media=mediadao.getMedia(Integer.parseInt(value));
} catch (SearchBLException ex) {
Logger.getLogger(MediaConverter.class.getName()).log(Level.SEVERE, null, ex);
}
return media;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
String str = "";
if (value instanceof Media) {
str = "" + ((Media) value).getMediaId();
}
return str;
}
}
The problem is most likely in your converter and model class (Media).
You don't show how you exactly do the conversion, but I guess you're converting to String by returning the Media's Id, and converting back to Media by getting a new instance from some place like a DB?
In that case, your Media class needs to implement a custom equals and hashcode method.
JSF compares if the value send by the user corresponds with the values in the list you bind to the selectitems. It uses equals for that, which by default compares object Ids (kind of memory references). Unless you have the exact same instances, this will always be false.
Instead of defining an equals method, you can alternatively let your converter get the model object you need from the same list as the selectitems come from. There was an article on http://jdevelopment.nl a while back about this.
I have a text box whose Text property has a TwoWay MultiBinding with UpdateSourceTrigger set to PropertyChanged. The first Binding is to a dependency property (Value) which has a PropertyChangedCallBack function that rounds the value to one decimal place.
The purpose of the text box is to perform the rounding as the user types rather than when the text box loses focus, hence why UpdateSourceTrigger is set to PropertyChanged.
The problem I am having is that if text is entered that does NOT result in Value changing, the Text property and Value become out of sync. Only if the rounding operation causes Value to change does Text get updated on the fly. E.g., if Text and Value are both 123.4 and the user types 1 after this, Value is rounded to the same value (123.4), but Text shows 123.41. However, if 9 is then typed after the 4, Value is rounded up to 123.5. And because of this actual change, Text is then updated to the same (123.5).
Is there any way of forcing a text box to update from its source even when the source hasn't changed since the last trigger? I have tried using BindingExpressionBase.UpdateTarget() but this only works when UpdateSourceTrigger is set to Explicit, which can't be used as Value no longer gets updated prior to a suitable time where UpdateTarget could be called (such as a TextInput handler). I have tried other methods such as explicitly updating the Text value from the bound Value, forcing an actual change to Value temporarily to invoke an update, but these "hacks" either don't work or cause other problems.
Any help would be greatly appreciated.
The code is below.
XAML snippet
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{local:NumberFormatConverter}"
UpdateSourceTrigger="Explicit"
Mode="TwoWay">
<Binding Path="Value"
RelativeSource="{RelativeSource AncestorType={x:Type Window}}"
Mode="TwoWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
C# snippet
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(MainWindow),
new FrameworkPropertyMetadata(0m,
new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
obj.SetValue(ValueProperty, Math.Round((decimal)args.NewValue, 1));
}
Converter class required
public class NumberFormatConverter : MarkupExtension, IMultiValueConverter
{
public static NumberFormatConverter Instance { private set; get; }
static NumberFormatConverter()
{
Instance = new NumberFormatConverter();
}
public override object ProvideValue(IServiceProvider serviceProvider_)
{
return Instance;
}
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var result = 0m;
if (value != null)
{
decimal.TryParse(value.ToString(), out result);
}
return new object[] { result };
}
#endregion
}
I did a little digging on the Internet, and it turns out this was broken in WPF 4. Someone with an almost-identical problem to me posted here:
http://www.go4answers.com/Example/textbox-shows-old-value-being-coerced-137799.aspx
'Answer 8' states this was broken in WPF 4 and suggests a solution, which is to actually use UpdateSourceTrigger="Explicit" but to handle the TextChanged event and call BindingExpression.UpdateSource() to force changes in the text box to be reflected in the underlying value as if UpdateSourceTrigger="PropertyChanged", as per this post:
Coerce a WPF TextBox not working anymore in .NET 4.0
I implemented this, but lo and behold there were further side effects, in particular that every keystroke caused the caret to jump to the start of the text box due to updating the source and raising a PropertyChanged event. And also, any leading or trailing zeros or decimal places entered with the intention of entering further digits would get wiped out immediately. So, a simple condition to check the parsed decimal value of the text box versus the underlying value resolved this.
The following event handler is all that was needed:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = (TextBox)e.Source;
MultiBindingExpression binding = BindingOperations.GetMultiBindingExpression(tb, TextBox.TextProperty);
decimal result = 0m;
decimal.TryParse(tb.Text, out result);
if ((decimal)GetValue(ValueProperty) != result && binding != null)
{
int caretIndex = tb.CaretIndex;
binding.UpdateSource();
tb.CaretIndex = caretIndex;
}
}
I'm working with the NerdDinner application trying to teach myself ASP.NET MVC. However, I have stumbled upon a problem with globalization, where my server presents floating point numbers with a comma as the decimal separator, but Virtual Earth map requires them with dots, which causes some problems.
I have already solved the issue with the mapping JavaScript in my views, but if I now try to post an edited dinner entry with dots as decimal separators the controller fails (throwing InvalidOperationException) when updating the model (in the UpdateModel() metod). I feel like I must set the proper culture somewhere in the controller as well, I tried it in OnActionExecuting() but that didn't help.
I have just revisited the issue in a real project and finally found a working solution. Proper solution is to have a custom model binder for the type decimal (and decimal? if you're using them):
using System.Globalization;
using System.Web.Mvc;
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
// Don't do this here!
// It might do bindingContext.ModelState.AddModelError
// and there is no RemoveModelError!
//
// result = base.BindModel(controllerContext, bindingContext);
string modelName = bindingContext.ModelName;
string attemptedValue = bindingContext.ValueProvider.GetValue(modelName)?.AttemptedValue;
// in decimal? binding attemptedValue can be Null
if (attemptedValue != null)
{
// Depending on CultureInfo, the NumberDecimalSeparator can be "," or "."
// Both "." and "," should be accepted, but aren't.
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
if (attemptedValue.IndexOf(wantedSeperator, StringComparison.Ordinal) == -1
&& attemptedValue.IndexOf(alternateSeperator, StringComparison.Ordinal) != -1)
{
attemptedValue = attemptedValue.Replace(alternateSeperator, wantedSeperator);
}
try
{
if (bindingContext.ModelMetadata.IsNullableValueType && string.IsNullOrWhiteSpace(attemptedValue))
{
return null;
}
result = decimal.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
return result;
}
}
Then in Global.asax.cs in Application_Start():
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
Note that code is not mine, I actually found it at Kristof Neirynck's blog here. I just edited a few lines and am adding the binder for a specific data type, not replacing the default binder.
Set this in your web.config
<system.web>
<globalization uiCulture="en" culture="en-US" />
You appear to be using a server that is setup with a language that uses comma's instead of decimal places. You can adjust the culture to one that uses the comma's in a way that your application is designed, such as en-US.
Can you parse the text using the invariant culture - sorry, I don't have the NerdDinner code in fornt of me, but if you are passing in dot-separated decimals than the parsing should be OK if you tell it to use the invariant culture. E.g.
float i = float.Parse("0.1", CultureInfo.InvariantCulture);
Edit. I suspect that this is a bug in the NerdDinner code by the way, along the same lines as your previous problem.
I have a different take on this, you might like it. What I don't like about the accepted answer is it doesn't check for other characters. I know there will be a case where the currency symbol will be in the box because my user doesn't know better. So yeah I can check in javascript to remove it, but what if for some reason javascript isn't on? Then extra characters might get through. Or if someone tries to spam you passing unknown characters through... who knows! So I decided to use a regex. It's a bit slower, tiny fraction slower - for my case it was 1,000,000 iterations of the regex took just under 3 seconds, while around 1 second to do a string replace on a coma and period. But seeing as I don't know what characters might come through, then I am happy for this slightest of performance hits.
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
string attemptedValue =
bindingContext.ValueProvider.GetValue(modelName).AttemptedValue;
if (bindingContext.ModelMetadata.IsNullableValueType
&& string.IsNullOrWhiteSpace(attemptedValue))
{
return null;
}
if (string.IsNullOrWhiteSpace(attemptedValue))
{
return decimal.Zero;
}
decimal value = decimal.Zero;
Regex digitsOnly = new Regex(#"[^\d]", RegexOptions.Compiled);
var numbersOnly = digitsOnly.Replace(attemptedValue, "");
if (!string.IsNullOrWhiteSpace(numbersOnly))
{
var numbers = Convert.ToDecimal(numbersOnly);
value = (numbers / 100m);
return value;
}
else
{
if (bindingContext.ModelMetadata.IsNullableValueType)
{
return null;
}
}
return value;
}
}
Basically, remove all characters that are not digits, for a string that isn't empty. Convert to decimal. Divide by 100. Return result.
Works for me.