Verification, SLtoolkit-Dataform and the good old FormatException
Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=2648
Printed Date: 30-Jul-2026 at 2:24am
Topic: Verification, SLtoolkit-Dataform and the good old FormatException
Posted By: AuerRo
Subject: Verification, SLtoolkit-Dataform and the good old FormatException
Date Posted: 02-May-2011 at 4:52am
|
Hi, i have the following problem (which might not even be related to DevForce...)
In my SL4-MVVM-App with latest DevForceSL, I'd like to check application-wide if there are unsaved entities (works great) or erroneous entities (that's the problem).
DevForce Verification in the Silverlight-Toolkit-Dataform works fine, except for the case where I have a numeric property (eg. typeof decimal), and I insert an alphanumeric value (eg. "500a") in the View's TextBox. A FormatException is thrown by the Dataform, I get the Error displayed in the Dataform (just like a normal Verification-Error) but the value (or the Validation/Verification-Error) is not handed over to the entity in the VM, and (as consequence) EntityAspect.ValidationErrors doesn't change.
The real fun was, when inserting "ValidatesOnExceptions=True, NotifyOnValidationError=True" in the Datafield's Binding, nothing changed. Neither of these events fire: Entity.PropertyChanged, Entity.EntityAspect.PropertyChanged and ((INotifyDataErrorInfo)Entity).ErrorsChanged; Weirdest thing is, if I change the input back to a valid value (eg. from "500a" to "500", ((INotifyDataErrorInfo)Entity).ErrorsChanged fires. I'm completely confused!
Question is: How do I catch the Error in the ViewModel? Is there a recommended way to handle this with DevForce?
Thanks for any hint! Roland
|
Replies:
Posted By: DenisK
Date Posted: 03-May-2011 at 6:58pm
Hi Roland;
By default, System.FormatException that is thrown by DataForm is not sent over to the ViewModel. The exception is being trapped at the View only. That is why you don't see various Entity level events being fired.
If you really want to capture the exception on the VM, you can do one of 2 things:
1. Create an arbitrary string property and bind that to a TextBox. That string property in return can be assigned to the numeric property after your validation.
or
2. Create your own value converter class which you can manage on your VM.
I think you'll realize that this might not be as straightforward as you'd hope so I guess the question is whether you really feel it is necessary to capture the FormatException on the VM.
Regarding the INotifyDataErrorInfo.ErrorsChanged event, that might or might not be a bug. I was able to reproduce the issue here and what I'm seeing is that any kind of property change whether it results in an error or not, will trigger the ErrorsChanged event. This sound incorrect but I will confirm and get back to you as soon as possible.
|
Posted By: AuerRo
Date Posted: 03-May-2011 at 11:27pm
Thanks for this detailed answer!
Well, I don't feel it's necessary at all to capture the Exception on the VM, but those functional specs tell me to, and unfortunately customer's king. ;-)
I think I'll take your first suggestion (arbitrary string prop), because it seems to be at least a bit more straightforward. Especially because I can format the value the way it should be AND localized (which is not possilbe with XAML-inline-StringFormat).
Regarding the ErrorsChanged event: Maybe my quick shot, because i might remember reading something in the Rsrc-Center saying this event does not mean that errors changed, but gives you the ability to by checking the eventargs.
Once again thank you very much!
-Edit:
Ok, after trying I'm stuck again. I added a string-property to my Model and added a DecimalRangeVerifier:
[Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name = "Payment", AutoGenerateField = true)] [DecimalRangeVerifier(MinValue = 0d, MaxValue = 1000000d)]] [DataMember] public string PaymentFormatted { get { return string.Format(CultureInfo.CurrentCulture, "{0:c}", PropertyMetadata.Payment.GetValue(this)); } set { decimal decValue; var valOk = decimal.TryParse(value, NumberStyles.Currency, CultureInfo.CurrentCulture, out decValue); if (valOk) PropertyMetadata.Payment.SetValue(this, decValue); }
But how do I get the Verification running? I also tried VerifierEngine.ExecuteBeforeSet in the setter and a VerifierException was thrown, but I do not want to throw an exception, obviously I want the Exception to show up in the Dataform. The DefaultVerifierOptions are unchanged, the ErrorNotificationMode is "Notify".
I hope you can help me out of this mess one more time!
|
Posted By: DenisK
Date Posted: 05-May-2011 at 2:25pm
|
Hi Roland;
Have you made sure the following, "NotifyOnValidationError=true", is set on the TextBox? You also shouldn't need to call VerifierEngine.ExecuteBeforeSet in the setter as the validation is automatically handled.
If that doesn't do the trick, I might have to ask you to upload a small sample solution.
FYI, I did a quick simple example of this to show how it works.
Note: Other unnecessary details are not being shown.
Employee Model
public partial class Employee : Entity {
[DecimalRangeVerifier(MinValue = 0d, MaxValue = 100d)] [DataMember] public string PaymentFormatted { get { return PropertyMetadata.Payment.GetValue(this); } set { PropertyMetadata.Payment.SetValue(this, value); } }
public partial class PropertyMetadata { public static readonly DataEntityProperty<Employee, string> Payment = new DataEntityProperty<Employee, string> ("PaymentFormatted", true, false, ConcurrencyStrategy.None, false, false); }
}
ViewModel
_mainMgr.Employees.Take(1).ExecuteAsync(op => { AnEmployee = op.Results.Cast<Employee>().FirstOrDefault(); AnEmployee.PaymentFormatted = "0"; });
public Employee AnEmployee { get { return _anEmployee; } set { _anEmployee = value; RaisePropertyChanged("AnEmployee"); } }
View (xaml)
<StackPanel Grid.Row="3" DataContext="{Binding AnEmployee}"> <TextBox Name="txtOutput" Text="{Binding PaymentFormatted, Mode=TwoWay, NotifyOnValidationError=True, FallbackValue='Binding has failed'}" Height="25" Width="150" VerticalScrollBarVisibility="Auto" />
</StackPanel>
|
|
Posted By: AuerRo
Date Posted: 11-May-2011 at 2:51am
|
After extensive work on this problem I still got no solution.
Regarding your answer, I tend to say that we do not have the same initial situation.
In my Entity, there is the computed property "Payment", which is decimal. As a workaround, I created the property PaymentFormatted, which is string. If I try to configure my code like you posted, I get errors because I can't set the Payment in the PaymentFormatted-Setter or the other way in the Getter. And I can't set the Payment-PropertyMetadata, as it is already computed.
Maybe you just mixed up the names, so I created a completely independent property "PaymentFormatted" (so your solution, just every "Payment" changed to "PaymentFormatted"). Still the same error.
I even created a new, smaller project, that is easier to oversee, to isolate the problem. But I got the same error. I will upload this sample and send you a message.
Thanks for your help anyway!
EDIT: @DenisK: It seems that I can't send you a message, your Inbox is full. Could you provide me with your e-mail?
|
Posted By: DenisK
Date Posted: 11-May-2011 at 10:25am
|
Hi AuerRo;
Please upload your sample here.
ftp://www.ideablade.com/DevForce2Support - ftp://www.ideablade.com/DevForce2Support
Thanks!
|
Posted By: AuerRo
Date Posted: 12-May-2011 at 9:13am
|
I uploaded the sample.
Its name is "SpotLight.zip".
Thanks
|
Posted By: DenisK
Date Posted: 12-May-2011 at 8:27pm
|
AuerRo;
Thanks for the sample. Sorry for the misunderstanding. Yes, my code snippet was only to show how to add a custom property and bind that to a TextBox as well as apply a DF validation to it.
Here's another code snippet that should show how to solve a scenario similar to yours. However, please note that this code snippet is not perfect and could very much be improved depending on your specific needs.
I'm only going to post the Model part of the code as nothing really changes on the View and the ViewModel. And instead of using an Employee entity, I'm now using an OrderDetail entity with UnitPrice decimal property as an example. I also created a custom verifier to have a cleaner control over the validation.
Model
public partial class OrderDetail : Entity {
[DataMember] public string UnitPriceFormatted { get { return PropertyMetadata.UnitPriceFormatted.GetValue(this); } set { PropertyMetadata.UnitPriceFormatted.SetValue(this, value); bool error = this.EntityAspect.ValidationErrors.Any(vr => vr.TriggerMemberName == PropertyMetadata.UnitPriceFormatted.Name); if (!error) { var decimalValue = Convert.ToDecimal(value); UnitPrice = decimalValue; } } }
public partial class PropertyMetadata { public static readonly DataEntityProperty<OrderDetail, string> UnitPriceFormatted = new DataEntityProperty<OrderDetail, string>("UnitPriceFormatted", true, false, ConcurrencyStrategy.None, false, false); }
DevForce Validation
public class VerifierProvider : IVerifierProvider {
public IEnumerable<Verifier> GetVerifiers(object verifierProviderContext) { List<Verifier> verifiers = new List<Verifier>(); verifiers.Add(GetDecimalRangeFormattedVerifier());
return verifiers; } }
private static Verifier GetDecimalRangeFormattedVerifier() { Verifier v = new DecimalRangeFormattedVerifier(typeof(OrderDetail), PropertyMetadata.UnitPriceFormatted.Name); v.ErrorMessageInfo.ErrorMessage = "You have entered an invalid decimal number";
return v; }
public class DecimalRangeFormattedVerifier : PropertyValueVerifier {
public DecimalRangeFormattedVerifier(Type pType, String propertyName, String displayName = null, bool? shouldTreatEmptyStringAsNull = null) : base(new PropertyValueVerifierArgs(pType, propertyName, false, displayName, shouldTreatEmptyStringAsNull)) { }
public DecimalRangeFormattedVerifier(PropertyValueVerifierArgs args) : base(args) { }
protected override VerifierResult VerifyValue(object itemToVerify, object valueToVerify, TriggerContext triggerContext, VerifierContext verifierContext) {
var aString = (string)valueToVerify; decimal result; var isDecValueOk = Decimal.TryParse(aString, out result); VerifierResult vr = new VerifierResult(isDecValueOk);
return vr; } }
|
|
Posted By: AuerRo
Date Posted: 17-May-2011 at 5:27am
|
Thank you very much for this guidance!
I would really vote for providing such code samples in the resource center!
|
Posted By: DenisK
Date Posted: 17-May-2011 at 11:16am
|
Your welcome. Thank you for your vote. We're always improving our code samples and this is already on our ToDo list to add.
And in case you haven't noticed, we have a new forum section called http://www.ideablade.com/forum/forum_topics.asp?FID=49&title=community-feedback - Community Feedback where you can give, as the name says, feedbacks :)
|
Posted By: DenisK
Date Posted: 23-May-2011 at 12:22pm
Regarding the INotifyDataErrorInfo.ErrorsChanged event, that might or might not be a bug. I was able to reproduce the issue here and what I'm seeing is that any kind of property change whether it results in an error or not, will trigger the ErrorsChanged event. This sound incorrect but I will confirm and get back to you as soon as possible.
|
I've submitted a bug report on this. Please reply to this topic should anyone think that this needs to be fixed immediately. Thank you.
|
|