Wednesday, May 4, 2011

Synchronous and Asynchronous Validations using the Silverlight 4.0 and the MVVM pattern

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.

1

For further information of this pattern visit the following link:

http://weblogs.asp.net/dwahlin/archive/2009/12/08/getting-started-with-the-mvvm-pattern-in-silverlight-applications.aspx

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.

2

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 EventHandlerGetDataUsingDataContractCompletedEventArgs>(personProxy_GetDataUsingDataContractCompleted);

}

}

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:

3

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.

4

Asp.net 4.0: To allow capture of html tags inside a textbox

By David E.

On rare occasions inside our web application it is necessary to allow the user to enter some content in html format. In previous versions this was possible, and it was the responsibility of the programmer to validate those labels to prevent a user from entering malicious code to our application.

With ASP.net 4.0 the information will now be validated automatically. If a user tries to add html tags to a web application, the result will be an exception. This validation feature can be disabled with a simple statement of the web.config file.

The first thing to do in Visual Studio 2010 is to create a new web application, then we can add a new web form and then create the basic controls that capture html tags and a button that causes a postback from the site.

<asp:TextBox ID="txtComments" runat="server" Rows="4" TextMode="MultiLine" ClientIDMode="Static" Width="400px">asp:TextBox>

<asp:Button ID="btnInsertScript" runat="server" Text="Insert Inline"

onclick="btnInsertScript_Click" />

On the CodeBehind of our page we can see that the html is actually captured and properly painted.

htmlCode = HttpUtility.HtmlEncode(txtComments.Text);

Response.Write(this.txtComments.Text);

When running the application and capturing the HTML within that textbox (and because even change the validation mode in the web config) we get the following exception.

1

It is important to note that this exception occurs inside the HttpRequest, ie., even if our code is prepared to handle exceptions with a try catch, this error occurs before executing any code of our site, including the Load. If you want to handle this exception in a custom way, you need to do it inside the Global.asax.cs file with the Application_Error method.

void Application_Error(object sender, EventArgs e)

{

// Code that runs when an unhandled error occurs

Response.Redirect("ErrorPage.htm");

}

This method of exception handling will control any exception that is not properly handled in the application.


If we want this automatic validation to be disabled to allow html input in our textbox, you need to add the following tag within node in the Web.config file:

<httpRuntime requestValidationMode="2.0" />

This will disable the validation and allow the capture of html:

2

Important note: This is a way to allow the user to enter html tags inside our application. However, you should never rely on any text captured by a user, it may contain malicious code that could cause failures in our application. It is best that when you are returning a captured text to the user interface, always use the HtmlEncode method:

HttpUtility.HtmlEncode(txtComments.Text);

In this way all html tags will be fully rendered on the screen and not interpreted by the browser. If you want to display the html that the user enters, it is best to use specific controls for this edition, or adequately control the code in which the HTML will be injected.

Hosting several FTP sites in IIS

By Alejandro Villarreal

I recently did a deployment for which several sites and components needed to be deployed to the same machine, and we didn’t want to provide access through FTP to the whole disk, but to the specific folders where the components had to be deployed. At a first glance this sounded pretty easy: we created the required sites in IIS (each with a different hostname in its HTTP binding), added bindings for FTP in each site, configured a different host name for each of those bindings (for each site, the same hostname that the HTTP binding had), set up security for FTP, and started the FTP service and sites. I must also say that we had another site with an FTP binding that was configured to accept all other connections, by using a blank hostname.

But when we tried to access each of the named sites through FTP, we ended up getting to the same folder no matter what hostname we used. That folder was the one that the “catch all” FTP site (the one with the blank hostname) was pointing to.

After a bit of research, we found out that this is a known limitation of FTP. Even though IIS 7.0 allows you to specify different hostnames for each FTP site, the protocol itself doesn’t natively support it, so when you try to authenticate to an FTP site with a user and password, it isn’t smart enough to determine who should process the authentication request; in a way, it “forgets” about the hostname that you’re trying to access, and this –as we discovered– can have unexpected results.

It turns out that there are 2 workarounds for this:

1. Change the way in which you specify credentials when connecting to the site.

If “myftpsite.com” is one of the virtual hostnames specified in an FTP binding in IIS, instead of doing something like this:

ftp://user@myftpsite.com

You should do something like this:

ftp://myftpsite.com|user@myftpsite.com

Prepending the hostname and a pipe to the username should let you connect to the correct site. This workaround, however, might be incompatible with a couple of FTP clients that might not allow using pipes in the username.

2. Set the “useDomainAsHostName” property in IIS to true, so it automatically does it. This can be done by running the following command:

%windir%\system32\inetsrv\appcmd.exe set config -section:system.ftpServer/serverRuntime /hostNameSupport.useDomainNameAsHostName:"True" /commit:apphost

The second option seems more practical (I can’t see a reason why not to use it), but any of the two will let you connect to a specific virtual FTP site among all those defined in IIS.