Live Example: http://silverlight.adefwebserver.com/riatasks2businessvalidation/riatasksweb/
Manage Silverlight Business Rules In The Website
There are a lot of methods to provide validation in Silverlight. This article demonstrates a method to provide Business Rule Validation for a Silverlight application in the website that launches the Silverlight application. The reasons you would want to do this are:
- Multiple Silverlight applications sharing the same database will share the same validation rules.
- Normal ASP.NET applications using the same database will share the same validation rules.
- The validation rules are easily maintained.
Simple Silverlight Validation
This article picks up from where the blog post, Basic Silverlight View Model Validation, leaves off. In that blog, we covered simple validation on the client. We were only concerned with validating that a user entered data matched the underlying data type. In that example, we validated that a user either entered a valid date, or did not enter a date at all.
We could have created more validation rules, but, what if there were multiple View Models? We would need to duplicate the validation logic in each View Model. Even if we centralized the validation logic, it could not easily be shared among multiple Silverlight applications, or any ASP.NET websites that were using the same database.
The Silverlight Business Rules Validation Application
Let's look at the final application.
If you do not enter a Name or Description, the application will not save any data, and it will indicate the error message(s) in a scrollable box at the bottom of the screen.
If you enter a date, and it is in the past, it will not save any data, and it will also indicate an error message.
Yes, if you make all three errors at the same time, it will show all of them to you in the scrollable error box.
Website Business Rule Validation
The Silverlight application handles all the validation on the web server.
The flow chart above explains the process.
Fluent Validation
We first start with Fluent Validation. This will allow you to easily create business rules using Linq syntax.
You can download Fluent Validation from here.
We add its assembly to the Website project (not the Silverlight project):
Next, we create a folder called BusinessValidation and a file called BusinessRules.cs and add the following code to it:
using FluentValidation;
using System;
namespace RIATasks.Web
{
public class TaskValidator : AbstractValidator<Task>
{
public TaskValidator()
{
RuleFor(Task => Task.TaskName).NotEmpty()
.WithMessage("Please specify Task Name");
RuleFor(Task => Task.TaskDescription).NotEmpty()
.WithMessage("Please specify Task Description");
}
}
public class TaskInsertValidator : AbstractValidator<Task>
{
public TaskInsertValidator()
{
RuleFor(Task => Task.DueDate).Must(BeADateInTheFuture)
.WithMessage("Please specify a date that has not already passed");
}
private bool BeADateInTheFuture(DateTime? dtCurrentDate)
{
DateTime dtDateInTheFuture = DateTime.Now.AddDays(1);
return ((dtCurrentDate ?? dtDateInTheFuture) > DateTime.Now.AddDays(-1));
}
}
}
A few things to note:
TaskValidator()
will be used to validate only the Update
method.
TaskValidator()
and TaskInsertValidator()
will be used to validate the Insert
method.
- If the database fields change, this code will not compile (this is a very good thing!).
We then add a file, DataBaseParticalClasses.cs, and add the following code to it:
using FluentValidation;
using System;
using FluentValidation.Results;
using System.Collections.Generic;
using System.ComponentModel;
namespace RIATasks.Web
{
#region public partial class Task
public partial class Task
{
public List<string> Errors = new List<string>();
}
#endregion
}
This adds a Errors
column to the Tasks
table. This provides a property that will pass any errors back to the Silverlight application. You will want to add this for each of your database tables.
Add the following code to the file:
public partial class RIATasksDBDataContext
{
#region UpdateTask
partial void UpdateTask(Task instance)
{
TaskValidator validator = new TaskValidator();
ValidationResult results = validator.Validate(instance);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
if (instance.Errors.Count == 0)
{
this.ExecuteDynamicUpdate(instance);
}
}
#endregion
#region InsertTask
partial void InsertTask(Task instance)
{
TaskValidator validator = new TaskValidator();
TaskInsertValidator Insertvalidator = new TaskInsertValidator();
ValidationResult results = validator.Validate(instance);
ValidationResult Insertresults = Insertvalidator.Validate(instance);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
if (!Insertresults.IsValid)
{
foreach (var failure in Insertresults.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
if (instance.Errors.Count == 0)
{
this.ExecuteDynamicInsert(instance);
}
}
#endregion
}
This code implements the Update
and Insert
partial methods in the Linq to SQL class.
This code calls the validation code in the TaskValidator()
and TaskInsertValidator()
methods. If there are errors, they are added to the Errors
field, and the change to the database is not made.
If there are no errors, this.ExecuteDynamicUpdate(instance)
is called for an Update
, and this.ExecuteDynamicInsert(instance)
is called for an Insert
.
The Web Service
The Web Service methods need to be altered to process any errors.
Here is the altered Update
method:
#region UpdateTask
[WebMethod]
public Task UpdateTask(Task objTask)
{
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
var result = (from Tasks in DB.Tasks
where Tasks.TaskID == objTask.TaskID
where Tasks.UserID == GetCurrentUserID()
select Tasks).FirstOrDefault();
if (result != null)
{
result.TaskDescription = objTask.TaskDescription;
result.TaskName = objTask.TaskName;
result.DueDate = objTask.DueDate;
DB.SubmitChanges();
objTask.Errors = result.Errors;
}
}
catch (Exception ex)
{
objTask.Errors.Add(ex.Message);
}
return objTask;
}
#endregion
The Insert
method:
#region InsertTask
[WebMethod]
public Task InsertTask(Task objTask)
{
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
Task InsertTask = new Task();
InsertTask.TaskDescription = objTask.TaskDescription;
InsertTask.TaskName = objTask.TaskName;
InsertTask.UserID = GetCurrentUserID();
InsertTask.DueDate = objTask.DueDate;
DB.Tasks.InsertOnSubmit(InsertTask);
DB.SubmitChanges();
objTask.TaskID = InsertTask.TaskID;
objTask.Errors = InsertTask.Errors;
}
catch (Exception ex)
{
objTask.Errors.Add(ex.Message);
}
return objTask;
}
#endregion
The Silverlight Application
The only .cs file that needs to be modified in the Silverlight application is the TabControlModel.cs file. The previous Message
property has been changed to a List of type String, and a MessageVisibility
property has been added.
The Update
method is changed to this:
#region UpdateTask
private void UpdateTask(Task objTask)
{
TasksModel.UpdateTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
Task objResultTask = EventArgs.Result;
Message = objResultTask.Errors.ToList();
MessageVisibility = (Message.Count > 0) ?
Visibility.Visible : Visibility.Collapsed;
}
});
}
#endregion
The Insert
method is changed to this:
#region InsertTask
private void InsertTask(Task objTask)
{
TasksModel.InsertTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
CurrentTaskID = EventArgs.Result.TaskID;
Message = EventArgs.Result.Errors.ToList();
MessageVisibility = (Message.Count > 0) ?
Visibility.Visible : Visibility.Collapsed;
if (Message.Count == 0)
{
GetTasks();
}
}
});
}
#endregion
Display The Errors
The last step is to open up the project in Microsoft Expression Blend 4+ and drop a ListBox
on the View...
...and drag the Message
collection from the Data Context window...
...and drop it on the ListBox
to create the bindings to have it display the error messages.
Next, on the ListBox
, select the Advanced options next to Visibility:
Bind it to the MessageVisibility
property. This will hide the ListBox
when there are no errors.
Simple Validation
For any UI element that needs to be validated for type, meaning, you want to ensure that a Date
is entered into a Date
field, or an Integer
is entered into an number field, you can use this method: Basic Silverlight View Model Validation.
For everything else, you could use the method described here. While this demonstrates validating only the Linq to SQL classes, the same validation classes could be called in the web service methods. This will allow you to create complex business rules that span multiple tables.
What this primarily allows you to do is to put all your validation in one place, that can be consumed by multiple Silverlight Applications and ASP.NET websites.