Due to some technical problems with my current host, the live demo will not be working. However, the source-code is provided along with for your reference. Soon, I will update the links on my blog! Updated with D/b schema download file.
Introduction
It has been quite a while since MVC has been released. In this article, I will sample out an application for managing tasks (to-do lists) using ASP.NET MVC. The first alpha version is compiled against native features of MVC so as to give a glimpse of the native features.
Future releases will have jQuery plug-in and complete DAL based ToDoList. You can watch a live demo and download the code here.
The list (Index action) of TodoItem has been updated using the jquery dataTable
plug-in. Thanks to Matt Berseth, for iTunes theme for GridView
which has been modified and adapted for the dataTable
. The screenshot for the same is shown below:
Background
Before we can start off with coding, get a hold of the MVC pattern and download the VS 2008 extras from ASP.NET. You can get a kick-start from various videos and tutorials available from ASP.NET website.
The previous versions, available here, just used native MVC, i.e., no Data layer was added to the project. Now, if you look at the source, you will see that the code is more organized (quite a few comments though!). But, the Controller doesn't even know what it is doing, just that it knows of entity (TaskItem
). What's more to this update for this article, the Using Code section
is almost re-written all over according to this release.
Before proceeding further, let's have a look at what the solution has to offer! Below is a quick look at the Data project.
Using the Code
The entity has been renamed to TaskItem
from ToDoItem
, though the controller name has been kept the same so as to keep the URLs the same. Let's have a look at the code from our entity class in Model.
public partial class TaskItem
{
public int ItemId { get; set; }
public string Title { get; set; }
public int Priority { get; set; }
public DateTime StartDate { get; set; }
public DateTime? DueDate { get; set; }
public int? PercentCompleted { get; set; }
public bool? TrackProgress { get; set; }
public bool? RemindOnDelay { get; set; }
#region object overrides
public override bool Equals(object obj)
{
if (obj is TaskItem)
{
TaskItem item = (TaskItem)obj;
return item.ItemId == this.ItemId;
}
return base.Equals(obj);
}
public override string ToString()
{
return this.Title;
}
public override int GetHashCode()
{
return this.ItemId.GetHashCode();
}
#endregion
}
The credit for this IQuerable
pattern implementation goes to Rob Conery. If you want a more detailed tutorial on IQueryable
and the pattern used for todolist
, Rob has got video tutorials published on his blog for MVC Storefront application. You can watch them here.
To this partial class, we add validation logic so that there is no more validation on the Controller side, rather everything is accomplished at the Data layer only. The following code checks for any violations (validation errors) in the code.
public partial class TaskItem
{
public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (string.IsNullOrEmpty(Title))
yield return new RuleViolation("Task title is required.", "Title");
if (Priority <= 0 || Priority > 3)
yield return new RuleViolation("Priority must lie between 1 to 3.", "Priority");
if (DueDate != null && DueDate < StartDate)
yield return new RuleViolation("Due date must be greater than start date.",
"Due Date");
if (PercentCompleted != null && (PercentCompleted <= 0 || PercentCompleted > 100))
yield return new RuleViolation
("Please specify Percentage completed between 1 to 100.",
"Percentage Completed");
yield break;
}
}
The function GetRuleViolations
will help us adding validation checks for the entity operations in the data layer. Thanks to Scott Guthrie et. al. for authoring Professional ASP.NET MVC (a great book!).
We can check for validation errors in operations very easily as shown in the InsertTaskItem
function below:
public void InsertTaskItem(TaskItem item)
{
if (!item.IsValid)
throw new ArgumentException();
using (LinqTaskItemDataContext db = new LinqTaskItemDataContext())
{
SqlRepository.TaskItem dbItem = db.TaskItems
.Where(x => x.ItemID == item.ItemId)
.SingleOrDefault();
bool isNew = false;
if (dbItem == null)
{
dbItem = new DevOrigin.ToDoList.Data.SqlRepository.TaskItem();
isNew = true;
}
int itemId = (from taskItem in db.TaskItems
select taskItem.ItemID).Max();
dbItem.ItemID = ++itemId;
dbItem.Title = item.Title;
dbItem.Priority = item.Priority;
dbItem.StartDate = item.StartDate;
dbItem.DueDate = item.DueDate;
dbItem.PercentCompleted = item.PercentCompleted;
dbItem.TrackProgress = item.TrackProgress;
dbItem.RemindOnDelay = item.RemindOnDelay;
if (isNew)
db.TaskItems.InsertOnSubmit(dbItem);
db.SubmitChanges();
}
}
Now, the Service layer project just implements ITaskItemService
interface. The Service class uses the ITaskItemRepository
methods to provide services to its user.
As you can see in Controller methods, the code has just reduced to a matter of one or two lines. All the implementation is hidden in the Data layer and thus can easily be changed without affecting the layers working over it until the output remains the same.
Oh, did I forget to mention how to get that pretty look for data in Index controller. That's easy, huh! I used dataTable
jQuery plug-in to get that thing and then a bit of styling to make it look pretty. Let's look at the code in Index view for dataTable
.
<script type="text/javascript" charset="utf-8">
var oTable;
var giRedraw = false;
$(document).ready(function() {
$("#taskList tbody").click(function(event) {
$(oTable.fnSettings().aoData).each(function() {
$(this.nTr).removeClass('row_selected');
});
$(event.target.parentNode).addClass('row_selected');
});
oTable = $('#taskList').dataTable({
"bAutoWidth": false,
"bFilter": false
});
});
function fnGetSelected(oTableLocal) {
var aReturn = new Array();
var aTrs = oTableLocal.fnGetNodes();
for (var i = 0; i < aTrs.length; i++) {
if ($(aTrs[i]).hasClass('row_selected')) {
aReturn.push(aTrs[i]);
}
}
return aReturn;
}
</script>
The highlighted code will actually render the dataTable
in the View. Also, you will notice that a handler has been registered which is used to select the row when the user clicks on any row inside the table. And there is an unused function which is for future use, fnGetSelected
which returns the selected row. You can get a full reference of dataTable
in the accompanying website.
The ViewModel Pattern
Now, let's come to the ViewModel
pattern which is required for managing Priority column. Before, I can discuss some part of the code, let me take you through the newly added code.
public class TaskItemViewModel
{
public TaskItem TaskItem { get; private set; }
public SelectList PriorityList { get; private set; }
private List<Priority> Priorities = new List<Priority>();
private void Initialize()
{
Priorities.Add(new Priority { Id = 1, Name = "General" });
Priorities.Add(new Priority { Id = 2, Name = "Urgent" });
Priorities.Add(new Priority { Id = 3, Name = "Critical" });
}
public string GetPriorityByName(int index)
{
foreach (Priority priority in Priorities)
if (priority.Id == index)
return priority.Name;
return null;
}
public int GetPriorityById(int index)
{
foreach (Priority priority in Priorities)
if (priority.Id == index)
return priority.Id;
return 0;
}
public TaskItemViewModel(TaskItem taskItem, int selectedIndex)
{
Initialize();
TaskItem = taskItem;
PriorityList =
new SelectList(Priorities, "Id", "Name", selectedIndex);
}
class Priority
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Again, the credit for understanding this pattern goes to the great book of Scott Gu et. al. (Professional ASP.NET MVC).
What I've done is initialized the TaskItemViewModel
class with the TaskItem
object and the Priority
values and then based on the selected index, the SelectList
is returned with the help of property. Two helper methods, GetPriorityByName
and GetPriorityById
are also provided. Let's see the usage of the ViewModel
class in the controller. Consider the Edit action as an example:
TaskItem taskItem = _taskItemSvc.GetItem(id);
return View(new TaskItemViewModel(taskItem, taskItem.Priority));
TaskItem itemToUpdate = new TaskItem();
try
{
UpdateModel(itemToUpdate, collection.AllKeys);
_taskItemSvc.UpdateTaskItem(itemToUpdate);
return RedirectToAction("Index");
}
catch
{
foreach (var issue in itemToUpdate.GetRuleViolations())
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
TaskItem taskItem = _taskItemSvc.GetItem(id);
return View(new TaskItemViewModel(taskItem, taskItem.Priority));
}
Now, the priorities are named in Index action too using the same ViewModel
class. But what we are left with that there is no way we can set the task's status to Done!
Before setting the item's status as "done", we need to move that item to the trash list so that we can easily manage the trashed task items and active items. So, all I have done is a few modifications in the Data layer to include a new Model, modifications in Service layer to call the Data layer functions. And finally, we have the accomplish
action which implements all the stuff. Here is the code for the controller action:
public ActionResult Accomplish(int id)
{
TaskItem taskItem = _taskItemSvc.GetItem(id);
return View(new TrashedTaskItemViewModel(taskItem, taskItem.Priority));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Accomplish(int id, string confirmButton)
{
try
{
_taskItemSvc.AccomplishTaskItem(id);
return RedirectToAction("Index");
}
catch
{
TaskItem taskItem = _taskItemSvc.GetItem(id);
return View(new TrashedTaskItemViewModel(taskItem, taskItem.Priority));
}
}
As you can see, I've created a ViewModel
class for TrashedTaskItem
too to manage View.
<%if (item.TaskItem.IsAccomplished == true)
{
Response.Write("<tr class='accomplished'>");
}
else
{
Response.Write("<tr>");
}%>
<td>
<%if (item.TaskItem.IsAccomplished == true)
{ %>
<% Response.Write("done"); %>
<%}
else
{ %>
<%= Html.ActionLink("accomplish", "Accomplish",
new { id = item.TaskItem.ItemId })%>
<%} %>
| <%= Html.ActionLink("edit", "Edit", new { id = item.TaskItem.ItemId })%>
</td>
The above code has been taken from Index
action. It adds a class accomplished
to the table-row if the status of task-item is set as done and if not, the accomplish link is shown otherwise.
When you try to create a new task, you get a list of items that you need to enter which are: Title, Priority, Start Date, Due Date, % Completed, Track Progress, and Show Reminder.
Interestingly, "Show Reminder" or ReminderOnDelay, will send out an e-mail to the owner of the task automatically if it lags behind by a given set of configuration criteria though it is not implemented, but it can easily be done. And ToDoList
will show you how to do that.
The next plan is to add enhancements and remaining functionality to the todolist
.
Configuration
Once you download the code from the website, you will find schema.sql file with the help of which you will be able to configure the database. After the database is configured, update the web.config file so that the connection string is appropriately reflected.
History
- 0.1.4.2 (alpha): Fixed Index action, added the trash task-item functionality which maintains the accomplishment status of the task
- 0.1.3.2 (alpha): Fixed taskitem-priority with implementation of
ViewModel
pattern
- 0.1.3 (alpha): Updated with Repository and Queryable pattern. Details of Repository and Queryable pattern are available on MSDN, Scott Gu's blog, Rob Corney's blog
- v0.1.2 (alpha): Updated jQuery plugs for Detail action
- v0.1 (alpha): Initial release