New Posts New Posts RSS Feed: Verification, SLtoolkit-Dataform and the good old FormatException
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Verification, SLtoolkit-Dataform and the good old FormatException

 Post Reply Post Reply
Author
AuerRo View Drop Down
Newbie
Newbie
Avatar

Joined: 14-Mar-2011
Location: Tyrol, Austria
Posts: 21
Post Options Post Options   Quote AuerRo Quote  Post ReplyReply Direct Link To This Post Topic: Verification, SLtoolkit-Dataform and the good old FormatException
    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

Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post 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.
Back to Top
AuerRo View Drop Down
Newbie
Newbie
Avatar

Joined: 14-Mar-2011
Location: Tyrol, Austria
Posts: 21
Post Options Post Options   Quote AuerRo Quote  Post ReplyReply Direct Link To This Post 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!


Edited by AuerRo - 04-May-2011 at 12:54am
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post 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>

Back to Top
AuerRo View Drop Down
Newbie
Newbie
Avatar

Joined: 14-Mar-2011
Location: Tyrol, Austria
Posts: 21
Post Options Post Options   Quote AuerRo Quote  Post ReplyReply Direct Link To This Post 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?


Edited by AuerRo - 11-May-2011 at 3:56am
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post Posted: 11-May-2011 at 10:25am
Hi AuerRo;

Please upload your sample here.


Thanks!


Back to Top
AuerRo View Drop Down
Newbie
Newbie
Avatar

Joined: 14-Mar-2011
Location: Tyrol, Austria
Posts: 21
Post Options Post Options   Quote AuerRo Quote  Post ReplyReply Direct Link To This Post Posted: 12-May-2011 at 9:13am
I uploaded the sample. 

Its name is "SpotLight.zip".

Thanks
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post 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;
      }
    }

Back to Top
AuerRo View Drop Down
Newbie
Newbie
Avatar

Joined: 14-Mar-2011
Location: Tyrol, Austria
Posts: 21
Post Options Post Options   Quote AuerRo Quote  Post ReplyReply Direct Link To This Post 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!
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post 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 Community Feedback where you can give, as the name says, feedbacks :)
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post 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.
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down