By David Espino.
In every application that we develop, it is always an excellent practice to validate the entry data of an opt-in form. In common web applications, Visual Studio provides several mechanisms and controls to perform such validations. In the case of Silverlight 4.0 we can find some existing interfaces that allow us to do such validations when developing applications using the MVVM pattern.
Introduction to the MVVM pattern.
This design pattern allows applications to divide them into separate layers and provides additional benefits such as implicit validation, ease of including unit tests and an increase in the reuse of components.
The pattern defines three main parts, the Model, the View and the View-Model. The image below briefly explains the objectives of each of the layers.
For further information of this pattern visit the following link:
Synchronous and Asynchronous Validations
There are validations that can take place when the user captures the information (or fails to capture) in length, structure or valid characters of the information captured. These validations can be done synchronously. However, when trying to validate whether data is duplicated in the database, it is necessary Silverlight to link to a repository of data through a service handler or WebClient. These validations are executed after the user captures the information and after running the service function asynchronously.
Procedure: To implement synchronous validations we are going to use IdataErrorInfo interface and INotifyDataErrorInfo for asynchronous validations.
This will require several elements:
1. ValidationHandler class will be working with notifications when an error occurs. This class contains a dictionary called BrokenRules which will contain the errors that are occurring in the model.
public class ValidationHandler
{
public Dictionary<string, string> BrokenRules { get; set; }
public event EventHandler IsValidated;
public void OnIsValidatedChanged()
{
if (IsValidated != null)
{
this.IsValidated(this, new EventArgs());
}
}
public ValidationHandler()
{
BrokenRules = new Dictionary<string, string>();
}
public string this[string property]
{
get
{
return this.BrokenRules[property];
}
}
public bool BrokenRuleExists(string property)
{
return BrokenRules.ContainsKey(property);
}
public bool ValidateRule(string property, string message, Func<bool> ruleCheck)
{
if (!ruleCheck())
{
if (!BrokenRuleExists(property))
{
this.BrokenRules.Add(property, message);
}
else
{
this.BrokenRules[property] = message;
}
this.OnIsValidatedChanged();
return false;
}
else
{
RemoveBrokenRule(property);
this.OnIsValidatedChanged();
return true;
}
}
public void RemoveBrokenRule(string property)
{
if (this.BrokenRules.ContainsKey(property))
{
this.BrokenRules.Remove(property);
}
}
}
A base class for View-Model of the application that implements the INotifyPropertyChanged Interface
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
A service that will do an Asynchronous validation of an object called "Person" whose business logic would be on finding a data repository if the captured Id is duplicated or not. For purposes of this example, this logic is omitted. Do not forget the policy files required by Silverlight to consume the service.
public class PersonServiceObject : IPersonService
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public bool GetDataUsingDataContract(PersonType composite)
{
return false;
}
}
[ServiceContract]
public interface IPersonService
{
[OperationContract]
string GetData(int value);
[OperationContract]
bool GetDataUsingDataContract(PersonType composite);
// TODO: Add your service operations here
}
[DataContract]
public class PersonType
{
int id = 0;
string nombre = "Hello ";
[DataMember]
public int Id
{
get { return id; }
set { id = value; }
}
[DataMember]
public string Nombre
{
get { return nombre; }
set { nombre = value; }
}
}
Next, we need to add a referral to the service in our Silverlight application to use the service. We do it from "Add Service reference" which is in the Project Explorer.
Now, with the reference of the service we can create our model to be validated.
public class PersonViewModel: ViewModelBase, IDataErrorInfo, INotifyDataErrorInfo
{
private string nombre;
private int id;
private ValidationHandler validator = new ValidationHandler();
private PersonService.PersonServiceClient personProxy = new PersonService.PersonServiceClient();
public int Id
{
get { return id; }
set {
id = value;
RaisePropertyChanged("Id");
bool valid = validator.ValidateRule("Id", "Id del cliente no válido ", () => (id > 100));
personProxy.GetDataUsingDataContractAsync(new PersonService.PersonType { Id = value, Nombre = this.Nombre });
}
}
public string Nombre
{
get { return nombre; }
set {
nombre = value;
RaisePropertyChanged("Nombre");
}
}
public PersonViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
this.personProxy.GetDataUsingDataContractCompleted += new EventHandler
}
}
void personProxy_GetDataUsingDataContractCompleted(object sender, PersonService.GetDataUsingDataContractCompletedEventArgs e)
{
if (e.Error == null)
{
bool valid = validator.ValidateRule("Id", "Id del cliente duplicado. ", () => (e.Result == true));
OnErrorsChanged("Id");
}
else
{
MessageBox.Show("No fue posible obtener la validación en el servicio.");
}
}
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (this.validator.BrokenRuleExists(columnName))
{
return this.validator[columnName];
}
return null;
}
}
#endregion
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
IEnumerable<string> errors = null;
if (propertyName == null)
{
errors = validator.BrokenRules.Select(dict => dict.Value);
}
else
{
if (validator.BrokenRuleExists(propertyName))
{
errors = validator.BrokenRules.Select(dict => dict.Value);
}
}
return errors;
}
public bool HasErrors
{
get { return validator.BrokenRules.Keys.Count > 0; }
}
protected void OnErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
#endregion
}
The line bool valid = validator.ValidateRule("Id", "Id del cliente no válido ", () => (id > 100));
Will cause when you assign the property, it is going to be confirmed that the data captured is greater than 100, otherwise an error will add to the dictionary of errors. At the end a call is going to be made to the service for validating asynchronously using the method:
personProxy.GetDataUsingDataContractAsync(new PersonService.PersonType { Id = value, Nombre = this.Nombre });
Upon returning from the service, the validation will be done again with the result of the service and invokes a method that fires the event ErrorsChanged, typical of INotifyDataErrorInfo interface to notify whether or not there was an asynchronous error.
Once you have the model we must now add this model as a model for a hearing to test our validation:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ic="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:local="clr-namespace:AplicacionPrueba"
mc:Ignorable="d"
x:Class="AplicacionPrueba.MainView"
d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<local:PersonViewModel x:Key="MainViewModelDataSource" />
UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource MainViewModelDataSource}}" d:DataContext="{d:DesignData /SampleData/MainViewModelSampleData.xaml}">
<StackPanel>
<TextBlock Text="ID:">TextBlock>
<TextBox x:Name="txtNombre" Text="{Binding Path=Id, Mode=TwoWay, ValidatesOnDataErrors=True}" >TextBox>
StackPanel>
Grid>
UserControl>
At that view we will add our View-Model as a resource to be used, in addition to make a Bind to the control in which we are going to capture the Id for the change of ownership. If we try to be less than 100, the control will display an error:
If we try to proof a value greater than 100, it will trigger the asynchronous validation, and after a while you will receive an error of duplicity. In this example it will always mark the error because the method of service is not doing the validation, but in fact is always returning false as the value of validation.

No comments:
Post a Comment