This latest post in our series on building a project management application in XAF starts a sort of series within a series. Here we begin to look at adding the concept of statuses to our application. As this is a big topic, I don’t intend to bore you all to death by tackling it in one mega post. Instead, we’ll look at it over the next few posts. In this post however we’ll make a start. What we want to do here is to allow ProjectTasks to have a Status. Task Statuses should be configurable for different task types. Each status should also have an index, this will allow us to display them in a particular order. For example, an engineering task could have these statuses: Draft -> Planned -> Elaboration -> Development -> Documenting -> Completed. Also, some statuses should be considered “final”. This would mean that a whole task is assumed to be completed when such a status is specified. For a generic project task, the statuses could be: Draft -> Not Started -> In Progress -> Paused -> Completed.
Before we get started on our statuses though, there’s a piece of work we have to do first and that is to implement the functionality that will allow us to categorize tasks into functional areas. Each functional area will have a name and will be owned by someone. So let’s implement that now by, firstly, creating the FunctionalArea entity:
[DefaultClassOptions]
[NavigationItem("Settings")]
[DefaultProperty("Name")]
public class FunctionalArea : BaseObject {
public FunctionalArea(Session session) : base(session) { }
public string Name {
get { return GetPropertyValue<string>("Name"); }
set { SetPropertyValue<string>("Name", value); }
}
public Employee Owner {
get { return GetPropertyValue<Employee>("Owner"); }
set { SetPropertyValue<Employee>("Owner", value); }
}
}
Then we’ll add the FunctionalArea property to the ProjectTask:
public FunctionalArea FunctionalArea {
get { return GetPropertyValue<FunctionalArea>("FunctionalArea"); }
set { SetPropertyValue<FunctionalArea>("FunctionalArea", value); }
}
This will make the following update to our winforms and webforms application GUIs:

Now, with that done, let’s focus on our statuses. The easiest way to implement this would be as an enumeration:
public enum TaskStatus
{
Draft,
NotStarted,
InProgress,
Paused,
Completed
}
Of course doing this has an number of advantages: it’s quick to implement; the status property on the task is automatically initialised to the initial enumeration value and the last value can easily be considered to be the “final” status, as we spoke about above. However, implementing it this way does also have a number of disadvantages: we can’t use different status sets for different types or categories of tasks and a user could easily move from one status to another status when that status change is invalid; for example, from “draft” to “complete”.
Because of this, we will implement the status as a business class:
public class TaskStatus : BaseObject {
public TaskStatus(Session session) : base(session) { }
public TaskStatus(Session session, TaskStatusSet owner, string name)
: base(session) {
Guard.ArgumentNotNull(owner, "owner");
Guard.ArgumentNotNullOrEmpty(name, "name");
Owner = owner;
Name = name;
}
public string Name {
get { return GetPropertyValue<string>("Name"); }
set { SetPropertyValue<string>("Name", value); }
}
[Association]
public TaskStatusSet Owner {
get { return GetPropertyValue<TaskStatusSet>("Owner"); }
set { SetPropertyValue<TaskStatusSet>("Owner", value); }
}
}
We also spoke of the idea of a TaskStatus belonging to a TaskStatusSet, so let’s go ahead and implement that too:
public class TaskStatusSet : BaseObject {
public TaskStatusSet(Session session) : base(session) { }
public TaskStatusSet(Session session, string name)
: base(session) {
Guard.ArgumentNotNullOrEmpty(name, "name");
Name = name;
}
public string Name {
get { return GetPropertyValue<string>("Name"); }
set { SetPropertyValue<string>("Name", value); }
}
[Association, Aggregated]
public XPCollection<TaskStatus> Items {
get { return GetCollection<TaskStatus>("Items"); }
}
}
Notice a one-to-many association between these two classes. The TaskStatusSet class has the aggregated Items collection holding the TaskStatus objects which form a particular TaskStatusSet. In the ProjectTask class we introduce the TaskStatusSet property. The Status property is marked with the DataSourceProperty attribute, so that only a status from the current status set can be selected:
[DataSourceProperty("StatusSet.Items")]
public TaskStatus Status {
get { return GetPropertyValue<TaskStatus>("Status"); }
set { SetPropertyValue<TaskStatus>("Status", value); }
}
public TaskStatusSet StatusSet {
get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
}
Now when we launch the app and create a new project task we’ll see that the Status and the StatusSet fields are empty and uninitialized since we haven’t created any objects of these types:
Let’s fix this by extending the ProjectTask class so that it synchronizes its status list with the statuses from the database. In the future we’ll have different kinds of task, all inheriting from some form of task base class; when that time comes we’ll refactor this code into that base class, but for now, we’ll implement this code in the ProjectTask class:
public static void UpdateDatabase(Session session) {
TaskStatusSet statusSet = session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
if(statusSet == null) {
statusSet = new TaskStatusSet(session, StatusSetName);
statusSet.Save();
}
List<string> currentStatuses = new List<string>(Enum.GetNames(typeof(ProjectTaskStatuses)));
foreach(TaskStatus storedStatus in statusSet.Items) {
if(currentStatuses.Contains(storedStatus.Name)){
currentStatuses.Remove(storedStatus.Name);
}
}
if(currentStatuses.Count != 0) {
foreach(string statusName in currentStatuses) {
session.Save(new TaskStatus(session, statusSet, statusName));
}
}
}
public enum ProjectTaskStatuses {
Draft,
NotStarted,
InProgress,
Paused,
Completed
}
Then we’ll add a call to this method in the Updater class:
public class Updater : ModuleUpdater {
public Updater(Session session, Version currentDBVersion) : base(session, currentDBVersion) { }
public override void UpdateDatabaseAfterUpdateSchema() {
base.UpdateDatabaseAfterUpdateSchema();
//…
ProjectTask.UpdateDatabase(Session);
}
}
Which makes the following changes to the GUI:
Well this works, sort of, but it’s not perfect is it? We’ll fix some of the issues now and leave others for future posts. When a ProjectTask is created the StatusSet field is initialized, let’s create a test for this:
[TestFixture]
public class ProjectTaskTests : BaseXpoTest {
public override void SetUp() {
base.SetUp();
ProjectTask.UpdateDatabase(Session);
Session.CommitChanges();
}
[Test]
public void StatusSet_HasValueAfterConstruction() {
ProjectTask task = new ProjectTask(Session);
Assert.IsNotNull(task.StatusSet);
Assert.AreEqual(ProjectTask.StatusSetName, task.StatusSet.Name);
}
}
To make this test pass, we have to make the following change to he ProjectTask class:
public override void AfterConstruction() {
base.AfterConstruction();
this.StatusSet = FindTaskStatusSet(Session);
}
private static TaskStatusSet FindTaskStatusSet(Session session) {
return session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
}
Also, it makes no sense that the ProjectTask.Status field is visible in UI. To hide it we’ll apply a couple of attributes to it (VisibleInDetailView and VisibleInListView):
[VisibleInDetailView(false), VisibleInListView(false)]
public TaskStatusSet StatusSet {
get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
}
The StatusSet field should only be initialized via the constructor, so we’ll mark the setter as private:
[VisibleInDetailView(false), VisibleInListView(false), Persistent]
public TaskStatusSet StatusSet {
get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
}
And, to be on the safe side, we’ll accompany this latest change with a test:
[Test]
public void StatusSet_HasValueAfterReloadFromDatabase() {
ProjectTask task;
TaskStatusSet taskStatusSet;
using (UnitOfWork unitOfWork = new UnitOfWork()) {
task = new ProjectTask(unitOfWork);
taskStatusSet = task.StatusSet;
Assert.IsNotNull(taskStatusSet);
unitOfWork.CommitChanges();
}
using (UnitOfWork unitOfWork = new UnitOfWork()) {
ProjectTask newTask = unitOfWork.GetObjectByKey<ProjectTask>(task.Oid);
TaskStatusSet newTaskStatusSet = unitOfWork.GetObjectByKey<TaskStatusSet>(taskStatusSet.Oid);
Assert.IsNotNull(newTaskStatusSet);
Assert.AreEqual(newTaskStatusSet, newTask.StatusSet);
}
}
When we run this test we see that XPO (the underlying ORM which XAF uses to persist our entities) has stopped persisting the StatusSet property’s value? Why? What happened there? Well it’s because we marked the setter as private, d’oh! Not to worry, we can easily fix this by applying the Persistent attribute:
[VisibleInDetailView(false), VisibleInListView(false), Persistent]
public TaskStatusSet StatusSet {
get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
}
The last issue that we are going to fix in this post is that the Status field doesn’t have a value assigned by default. To correct this, we need to extend the StatusSet class, to define the initial and final state. So that we can use the initial state as the default:
public TaskStatus FirstStatus {
get { return GetPropertyValue<TaskStatus>("FirstStatus"); }
set { SetPropertyValue<TaskStatus>("FirstStatus", value); }
}
public TaskStatus LastStatus {
get { return GetPropertyValue<TaskStatus>("LastStatus"); }
set { SetPropertyValue<TaskStatus>("LastStatus", value); }
}
And to initialize these properties, we’ll extend the ProjectTask class’ UpdateDatabase method:
public static void UpdateDatabase(Session session) {
TaskStatusSet statusSet = session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
if(statusSet == null) {
statusSet = new TaskStatusSet(session, StatusSetName);
statusSet.FirstStatus = new TaskStatus(session, statusSet, ProjectTaskStatuses.Draft.ToString());
statusSet.LastStatus = new TaskStatus(session, statusSet, ProjectTaskStatuses.Completed.ToString());
statusSet.Save();
} //…
}
Finally, to set the initial Status when a ProjectTask is created, we need to update the AfterConstruction method as follows:
public override void AfterConstruction() {
base.AfterConstruction();
this.StatusSet = FindTaskStatusSet(Session);
this.Status = this.StatusSet.FirstStatus;
}
Phew, well that was quite a lot to fit into one blog post so I think we’ll end it there. In the coming posts we’re going to be resolving the following issues with statuses: currently, statuses are sorted by their name when displayed, while they should be logically ordered; a user can change any status to whatever other status he likes (e.g. Completed -> NotStarted) and lastly, the “New” and “Clear” actions shouldn’t be available in the lookup when changing the status. As always, you can
download the source code to this project. Until next time, happy XAFing!