TodoService
A custom service provides for all CRUD operations for the TodoItem
objects. It serves as an intermediary between the LINQ to SQL DataContext
for TodoItems
and viewmodel logic.
When the TodoService
is instantiated, a TodoDataContext
is created. TodoItem
objects can then be retrieved using the GetTodoItem
method, shown in the following excerpt:
public TodoItem GetTodoItem(int itemId)
{
TodoItem item = dataContext.TodoItems.Where(
todoItem => todoItem.Id == itemId).FirstOrDefault();
if (item == null)
{
throw new KeyNotFoundException(
string.Format("Item with key '{0}' was not found.", itemId));
}
return item;
}
Conversely, inserting a new TodoItem
into the database is done using the service’s AddOrUpdateItem
method.
If the TodoItem
has an Id
that is less than 1, it indicates that the TodoItem
does not already exist in the database. If greater than 0, it is indicative of an update. See the following excerpt:
public void AddOrUpdateItem(TodoItem todoItem)
{
ArgumentValidator.AssertNotNull(todoItem, "todoItem");
if (todoItem.Id <= 0)
{
dataContext.TodoItems.InsertOnSubmit(todoItem);
}
dataContext.SubmitChanges();
}
The third method of the TodoService
worth noting is GetTodoItems
, which retrieves all TodoItem
objects via the TodoItems
property of the data context, like so:
public IEnumerable<TodoItem> GetTodoItems()
{
return dataContext.TodoItems;
}
As you see in a moment, GetTodoItems
is used by the TodoListViewModel
.
With the TodoService
in place we now look at how the service is used by the app’s viewmodels
to display all to-do items and to create new to-do items.
TodoItemViewModel
The TodoItemView
page retrieves all TodoItem
objects for the user and displays them in a list. Its viewmodel relies
on the to-do service being passed as a constructor argument.
Retrieving the TodoItem
objects is performed in the viewmodel’s PopulateItems
method. The to-do service is used to retrieve the items, and then, using a LINQ expression, the items are grouped by the TodoItem.DueDate
property. This allows the list to be bound to a LongListSelector
in the view. See the following excerpt:
void PopulateItems()
{
try
{
IEnumerable<TodoItem> items = todoService.GetTodoItems();
IEnumerable<CustomGrouping<TodoItem>> groups
= from todoItem in items
orderby todoItem.DueDate
group todoItem by todoItem.DueDate.Date
into grouping
select new CustomGrouping<TodoItem>(
new DateGroupingKey(grouping.Key),
grouping.AsEnumerable());
GroupedTodoItems = groups.ToList();
}
catch (Exception ex)
{
Message = "Unable to retrieve items.";
Console.WriteLine(ex);
}
}
TodoItems
are grouped using a custom class called DateGroupingKey
, which allows us to provide some additional logic to the item groupings, namely an Overdue
property (see Listing 3).
This allows you to change the background for the grouping header to red
if the to-do items in the group have due dates occurring in the past.
Alternatively, you may choose to use a value
converter for determining whether the date is overdue. The example uses
a custom key class, however, because it better encapsulates the date
value and the logic for determining whether it is overdue.
LISTING 3. DateGroupingKey
Class
public class DateGroupingKey
{
public DateTime DateTime { get; private set; }
public DateGroupingKey(DateTime dateTime)
{
DateTime = dateTime;
Overdue = DateTime.Now > dateTime;
}
public bool Overdue { get; private set; }
}
The viewmodel’s Load
method commences population of the to-do items asynchronously, using a
thread from the thread pool. This prevents blocking the UI thread
during a potentially long-running operation.
A PeriodicTask
is registered with the ScheduledActionService
. If the PeriodicTask
has already been registered, it is first removed. This resets the task’s expiration date. See the following excerpt:
public void Load()
{
ThreadPool.QueueUserWorkItem(delegate { PopulateItems(); });
PeriodicTask periodicTask = new PeriodicTask(agentName)
{
Description = "Updates a tile.",
ExpirationTime = DateTime.Now.AddDays(14)
};
if (ScheduledActionService.Find(agentName) != null)
{
ScheduledActionService.Remove(agentName);
}
/* This can only be called when the app
* is running in the foreground. */
ScheduledActionService.Add(periodicTask);
}
The TodoListViewModel
constructor calls its Load
method after initializing various ICommands
(see Listing 4). NewItemCommand
uses the ViewModelBase
class’s Navigate
method to open the TodoItemView
page. EditItemCommand
also navigates to the TodoItemView
page, but passes the Id
of the TodoItem
, provided as a command argument.
LISTING 4. TodoListViewModel
Constructor
public TodoListViewModel(
ITodoService todoService, IDeviceProperties deviceProperties)
{
this.todoService = ArgumentValidator.AssertNotNull(
todoService, "todoService");
this.deviceProperties = ArgumentValidator.AssertNotNull(
deviceProperties, "deviceProperties");
editItemCommand = new DelegateCommand<int>(
todoItemId => Navigate(todoItemViewUrl + "?TodoItemId=" + todoItemId));
testAgentCommand = new DelegateCommand(
obj => ScheduledActionService.LaunchForTest(
agentName, TimeSpan.Zero));
backupDatabaseCommand = new DelegateCommand(obj => BackupDatabase());
restoreDatabaseCommand = new DelegateCommand(obj => RestoreDatabase());
Load();
}