Silverlight, WPF stuff

Tuesday 12 July 2011

 

Binding DTOs in Silverlight application and validating input


While working on an LOB Silverlight 4 application, I came across this issue, where by I wanted to databind my DTOs (from WCF services) directly to my View elements (I have implemented MVVM pattern in my project).

My project consists of WCF services (non RIA) being consumed by my SL app. The only problem though was that of validating the user-input (through different UI elements bound to DTOs).



[MetaDataType] is not supported by Silverlight 4/5, though there are few hacks possible:
 http://new.efficientcoder.net/2010/06/using-dataannotations-on-generated.html

However when you create Service References in VS2010, the generated classes are partial. Hence we can leverage it to somehow extend the generated classes to enable Validation.

I also came across a very good library developed by Jeremy Skinner for validating .NET objects. Though this library is primarily targeted at ASP.NET MVC, I used it for validating my DTOs on my Silverlight client.
http://fluentvalidation.codeplex.com/
The library is well documented and is very easy to learn.

Now back to my problem of validating DTOs.

The partial class generated by VS2010 (after creating service reference) looked like below:

[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")] 
[System.Runtime.Serialization.DataContractAttribute(Name="ParticipantDto", Namespace=http://schemas.datacontract.org/2004/07/BSA.CMS.Services.DataTransferObjects)] 
public partial class ParticipantDto : object, System.ComponentModel.INotifyPropertyChanged 
{ 
private string FileNumberField; 
private long IdField; 
... 
[System.Runtime.Serialization.DataMemberAttribute()] 
public string FileNumber 
{ 
get 
{ return this.FileNumberField; } 
set 
{ if ((object.ReferenceEquals(this.FileNumberField, value) != true)) { this.FileNumberField = value; this.RaisePropertyChanged("FileNumber"); } } 
} 
[System.Runtime.Serialization.DataMemberAttribute()] 
public long Id 
{ 
get 
{ return this.IdField; } 
set 
{ if ((this.IdField.Equals(value) != true)) { this.IdField = value; this.RaisePropertyChanged("Id"); } } 
} 
... 
}

I created (extended) the above class as below. This is boilerplate way to implement INotifyDataErrorInfo on a class

public partial class ParticipantDto : INotifyDataErrorInfo 
{
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    protected Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public void AddError(string propertyName, string error, bool isWarning)
    {
        if (!_errors.ContainsKey(propertyName)) _errors[propertyName] = new List<string>();
        if (!_errors[propertyName].Contains(error))
        {
            if (isWarning) _errors[propertyName].Add(error);
            else _errors[propertyName].Insert(0, error); NotifyErrorsChanged(propertyName);
        }
    }

    public void RemoveAllErrors()
    {
        string[] properties = new string[(_errors.Count)];
        int i = 0;
        foreach (KeyValuePair<string, List<string>> err in _errors)
        {
            properties[i] = err.Key; i++;
        }
        foreach (string property in properties)
        {
            _errors.Remove(property);
            NotifyErrorsChanged(property);
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (String.IsNullOrEmpty(propertyName) || !_errors.ContainsKey(propertyName)) return null;
        return _errors[propertyName];
    }

    protected void NotifyErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null)
        {
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }

    public bool HasErrors { get { return this._errors.Count > 0; } }
}


Then I created the validator class:


public class ParticipantValidator:AbstractValidator<ParticipantDto>
    {
        public ParticipantValidator()
        {
            RuleFor(participant => participant.FileNumber).NotEmpty().WithMessage("File number cannot be empty");
            RuleFor(participant => participant.Id).NotEmpty().WithMessage("Participant number cannot be empty").GreaterThan(0).WithMessage("Participant number should be greater than 1");
        }
    }

Note that I can create different Validator classes as needed and use them throughout my project, both on client and server side. And that's the beauty of doing validation this way. You won't have to repeat the validation code in all those ViewModels where you are using the DTOs.

Now this is how I validate in my ViewModel:


public class NewParticipantViewModel : ViewModelBase
    {
        private ICommand _okButtonCommand;
        private ParticipantServiceReference.ParticipantDto _participant;
        
        public ParticipantServiceReference.ParticipantDto Participant
        {
            get { return _participant; }
            set
            {
                _participant = value;
            }
        }      

        public NewParticipantViewModel(IRegionManager regionManager, IEventAggregator eventAggregator):base(regionManager,eventAggregator)
        {
            _participant = new ParticipantServiceReference.ParticipantDto();
        }

        public ICommand OKButtonCommand
        {
            get
            {
                if (_okButtonCommand == null)
                {
                    _okButtonCommand = new DelegateCommand<string>(onOKButtonCommandExecuted);
                }
                return _okButtonCommand;
            }
        }
        public void onOKButtonCommandExecuted(string args)
        {
            Participant.RemoveAllErrors();
            ParticipantValidator validator = new ParticipantValidator();
            ValidationResult results = validator.Validate(Participant);
            if (!results.IsValid)
            {
                IList<ValidationFailure> failures = results.Errors;
                foreach (ValidationFailure failure in failures)
                {
                    Participant.AddError(failure.PropertyName, failure.ErrorMessage, false);
                }
            }
        }
    }

The validation in View will work perfectly fine. Besides, you can also validate the whole DTO (with different conditions).


The only small glitch I found is that when an integer field in DTO is bound to a textbox in SL, the red validation marker around textbox won't appear if the textbox is cleared out. However it can be caught using ValidationOnException on UI element.

Cheers,
Dharmesh Kemkar
(www.kemkar.net)

Comments:

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

Archives

July 2011  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]