Click here to Skip to main content
Click here to Skip to main content
Go to top

ToDo List using ASP.NET MVC

, 15 Sep 2009
Rate this:
Please Sign up or sign in to vote.
Creating task list using MVC
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:

index.png

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.

data-project.png

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:

/// <summary>
/// Insert the param to database.
/// </summary>
/// <param name="item">TaskItem object to be inserted.</param>
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();
                  
    //add the item
    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() {
      /* Add a click handler to the rows - this could be used as a callback */
      $("#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
      });
    });

    /* Get the rows which are currently selected */
    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 the priorities list.
      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:

  //Edit Action: GET Method 
  TaskItem taskItem = _taskItemSvc.GetItem(id);
  return View(new TaskItemViewModel(taskItem, taskItem.Priority));
  
  //Edit Action: POST Method 
  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:

// GET Method
public ActionResult Accomplish(int id)
{
  TaskItem taskItem = _taskItemSvc.GetItem(id);
  return View(new TrashedTaskItemViewModel(taskItem, taskItem.Priority));
}

// POST Method
[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

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

A. Bhandari a.k.a Amit Bhandari
Software Developer (Senior)
India India
I: working as software engineer.
My Blog: http://blog.abstractcomputing.in

Comments and Discussions

 
GeneralMy vote of 1 Pinmembermrkt27-Dec-12 6:22 
GeneralMy vote of 1 Pinmembersamthec10-Jun-12 10:04 
GeneralProblems & Solutions. PinmemberMember 31638983-Aug-10 11:50 
GeneralSchema.sql missing from download zip Pinmembernick crowther14-Sep-09 10:14 
AnswerRe: Schema.sql missing from download zip PinmemberA. Bhandari a.k.a Amit Bhandari15-Sep-09 16:45 
Generalgood article PinmemberDonsw25-Jul-09 5:58 
GeneralSource download PineditorSmitha Vijayan31-May-09 11:18 
GeneralRe: Source download PinmemberA. Bhandari a.k.a Amit Bhandari31-May-09 19:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 15 Sep 2009
Article Copyright 2009 by A. Bhandari a.k.a Amit Bhandari
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid