Task-It Series
This post is part of a series of blog posts and videos about the Task-It (task management) application that I have been building with Silverlight 4 and Telerik's RadControls for Silverlight 4. For a full index of these resources, please go here. One of the posts listed in the index provides a full source download for the application, and I will be referring to that source code in this post.
Leading up to displaying an error
As I was preparing to write Part 3 of my mini-blog post series about Events in Task-It I realized that should give a little background information about how to handle errors on the client when returning data from an asynchronous call to the server. In my case, I am using WCF RIA Services for getting, inserting, updating and deleting data.
The call to load data
In this example I'm going to use what is probably the most important data loading situation in Task-It, the loading of the task list. This code lives in the ListViewModel class under the ViewModels directory in the TaskIt.Modules.Tasks project. Keep in mind that this is the most complex data loading scenario in the app, because I may be loading tasks for a certain task category, all task categories, or a specific Project, Context or Contact.
Depending on which node the user clicked in the tree on the right side of the app I will call a different method, but notice that every call to a method in the DataProvider class, the last argument is OnTasksLoaded. That is the method that will be called when the data (or error) returns from the client.
public void LoadTasks()
{
Utils.ShowBusyIndicator();
Utils.DataContext.Tasks.Clear();
var isCalendarView = SelectedTaskView == TaskView.Calendar;
switch (SelectedEntityFilterArgs.EntityType)
{
case EntityType.TaskCategory:
if (SelectedEntityFilterArgs.ID == 0) // All categories
{
// TODO: Ross - Not sure about this
SelectedTaskCategory = null;
DataProvider.GetAllTasks(Utils.CurrentUserID, SelectedTaskStatus.Value, isCalendarView, OnTasksLoaded);
}
else
{
SelectedTaskCategory = TaskCategories.Where(c => c.ID == SelectedEntityFilterArgs.ID).First();
DataProvider.GetTasksByCategory(Utils.CurrentUserID, SelectedEntityFilterArgs.ID, SelectedTaskStatus.Value, isCalendarView, OnTasksLoaded);
}
break;
case EntityType.Project:
SelectedProject = Projects.Where(c => c.ID == SelectedEntityFilterArgs.ID).First();
DataProvider.GetTasksByProject(Utils.CurrentUserID, SelectedEntityFilterArgs.ID, SelectedTaskStatus.Value, isCalendarView, OnTasksLoaded);
break;
case EntityType.Context:
SelectedContext = Contexts.Where(c => c.ID == SelectedEntityFilterArgs.ID).First();
DataProvider.GetTasksByContext(Utils.CurrentUserID, SelectedEntityFilterArgs.ID, SelectedTaskStatus.Value, isCalendarView, OnTasksLoaded);
break;
default:
SelectedContact = Contacts.Where(c => c.ID == SelectedEntityFilterArgs.ID).First();
DataProvider.GetTasksByContact(Utils.CurrentUserID, SelectedEntityFilterArgs.ID, SelectedTaskStatus.Value, isCalendarView, OnTasksLoaded);
break;
}
}
And here is a partial view of what the RadTreeView (on the Tasks page) looks like...with a bit of my sample data in it:
The callback method
OK, so let's say that the user has clicked on of the nodes, LoadTasks has been called, and the response comes back to the client. Now let's look at the callback method.
void OnTasksLoaded(LoadOperation<
Task
> lo)
{
if (lo.HasError)
{
Utils.ShowErrorMessage(TasksErrorStrings.Message_LoadingTasksFailed, lo.Error.Message);
lo.MarkErrorAsHandled();
return;
}
this.OnPropertyChanged(p => p.Tasks);
this.OnPropertyChanged(p => p.CalendarTasks);
HasTasks = Tasks.Count() > 0;
Utils.HideBusyIndicator();
}
You'll notice tha the first thing I check is whether the LoadOperation has an error. If so, I am going to call a utility method that handles these errors, mark the LoadOperation as 'handled', and return. The end result of this will be a dialog that displays a message to the user.
One easy way to test this is to get to the Tasks page, stop your instance of SQLExpress, and then click on a node. That is how I generated this error.
Submit errors
Another scenario that is almost identical, but is worth showing is handling submit errors. For this example let's look at a common Task-It scenario, adding a new Task. Suppose we have click on the New button in the upper right part of the Tasks page, have entered the form that allows us to enter information for a new task, named our new task "My new task" and click Save.
Clicking Save will invoke the SaveCommand that is bound to the Save button, and lead us to the OnSave method in the TaskItViewModelBase class that lives in the ViewModels directory under the TaskIt project.
public void OnSave(EntityType entityType)
{
if (HasValidationErrors())
{
FireValidationErrorNotification();
return;
}
ValidationErrors.Clear();
Messenger.Default.Send(new SaveEntityArgs(entityType));
Utils.ShowBusyIndicator();
Utils.DataContext.SubmitChanges(OnSubmitCompleted, null);
}
The first thing I check for are validation errors, but that is a subject for another post. Assuming that there aren't any, the last line will call SubmitChanges on our DataContext object (that is the client side object WCF RIA Services code-generated for us), and when the response returns from the server it will call OnSubmitCompleted.
public virtual void OnSubmitCompleted(SubmitOperation so)
{
Utils.HideBusyIndicator();
if (so.HasError)
{
Utils.ShowErrorMessage(ErrorStrings.Message_SubmitFailed, so.Error.Message);
so.MarkErrorAsHandled();
return;
}
Utils.DataContext.RejectChanges();
Mode = ItemMode.None;
Messenger.Default.Send(new DetailsViewCompletedArgs());
}
This code should look familiar because it is almost identical to what we showed above with the LoadOperation. The only difference is that in this case we are checking to see if the SubmitOperation has an error. If it does, it will pop an error dialog similar to the one we saw above.
Wrap Up
Naturally it is not a good idea to just assume that a call to the server is going to succeed, so having this error handling code in place is critical. Of course, when you do it is important that you implement the error handling it in a very consistent manner. You'll notice that both my LoadOperation errors and SubmitOperation errors both call into the same utility method (Utils.ShowErrorMessage), which we'll look at in depth in my next post, Events in Task-It - Part 3.