Chapter 6. Requests for Information

In the last chapter, I dove into some of the important domain logic for the SmartCA application by covering Submittal Transmittals. In this chapter, I will continue that trend by introducing another important new concept to the domain, the Request for Information (RFI). As you will see, the RFI is similar to a Submittal Transmittal in that they share a lot of the same classes: this will also prompt some refactoring.

The Problem

Contractors can have many questions throughout a project that may concern documents, construction, materials, and so on. In the old days, these questions were answered with a phone call or an informal conversation with the architect in charge. Nowadays, however, it is necessary to document every request and reply between project contractors and the firm that is running the project, which in this case is Smart Design. This documentation is necessary because significant costs and complications may arise during the question/answer process, and the RFI can be used as a tool to shape the project's direction.

Some of the uses of RFIs do not have cost implications, such as a simple non-change request for more information about something shown in the specifications. They can also be used to let the architect know about an occurrence of something on the job site, or to let the architect know about latent or unknown conditions. The most important rule for an RFI is that it must contain all of the necessary information and not be too brief. If a contractor has a question for the architect, the architect needs to know exactly what the question is so that it may be answered properly.

Each RFI needs to be numbered in the sequence issued, per project. The RFI number is later used as a reference for members of the project when the architect answers the questions or resolves the issues. The RFI is a time-sensitive document, and it must include the date that it was sent, as well as the date that a response is needed. It is important that there are no duplicate RFI numbers per project and that there are no gaps between RFI numbers. RFI numbers can be reused across other projects.

The Design

In the SmartCA domain, an RFI contains several important business concepts that must be closely followed. In the next few sections, I will be designing the domain model, determining the RFI Aggregate and its boundaries, and designing the Repository for RFIs.

Designing the Domain Model

As stated earlier, the most important parts of the RFI are the Date Received, Date Requested By, Date to Field, Question, and Answer properties. Since these are properties, it is a little bit difficult to model their expected behavior in a diagram. This can be remedied by using a Specification (Evans, Domain-Driven Design, Tackling Complexity in the Heart of Software, 225) class to specify the rules for these properties, and actually make the specification part of the domain. This helps convey to the business domain experts what the intended logic is instead of burying it inside of the Request for Information class.

Figure 6.1 shows a drawing showing the relationships among the classes that combine to make up a Request for Information.

RFI Aggregate.

Figure 6.1. RFI Aggregate.

Obviously, the root of this aggregate is the Request for Information class. Note the relationships to the Question/Answer Specification, Date to Field Specification, and RFI Number Specification. These relationships make it very clear to the domain experts that there are rules being modeled for these important concepts.

The relationship to the Status class shows exactly what state the RFI is in, such as completed, pending an architect review, and so on. The relationship to the "From" class represents who the RFI is from, and to go along with who it is from is what Contract is associated with the RFI. The relationship to the Specification Section is not as important for an RFI as it was for a Submittal Transmittal. It is quite possible that the RFI may not require a reference to a Specification Section, as the RFI could be requesting information about something else that may have nothing to do with a Specification Section, such as an incident.

The next important part of the diagram is the RFI's relationship to the Routing Item. This is how Smart Design knows to whom each RFI has been routed for action, and the Discipline of that person, such as architect, engineer, or construction administrator. Just like the Submittal Transmittal Aggregate, there is a Copy To relationship from an RFI which represents the list of Recipients who need to be copied on all correspondence having to do with the RFI.

Defining the RFI Aggregate

As you can see from the diagram of the RFI Aggregate in Figure 6.2, there are a lot of moving parts.

As you can see from the diagram of the RFI Aggregate in Figure 6.2, there are a lot of moving parts

Classes constituting the RFI Aggregate.

Figure 6.2. Classes constituting the RFI Aggregate.

Notice how I am starting to make use of some of the other Entities introduced in previous chapters, such as the ProjectContact class, which is used to represent the To property of the Submittal class, the Recipient property of the RoutingItem class, and the Contact property of the CopyTo class. Also, the ProjectContact class is used in the RFI's From property to represent the person originating the RFI.

Defining the Aggregate Boundaries

RFI Aggregate boundaries.

Figure 6.3. RFI Aggregate boundaries.

The RequestForInformation class has its own identity and is definitely the root of its own Aggregate (see Figure 6.3). All of the other classes in the diagram, except for ProjectContact, Company, and SpecificationSection, belong to the RFI Aggregate. As shown in earlier chapters, ProjectContact belongs to the Project Aggregate, Company is the root of its own Aggregate, and the SpecificationSection class is part of the Submittal Aggregate.

Designing the Repositoryx

Since the RequestForInformation class is its own Aggregate root, it will have its own Repository, as shown in Figure 6.4.

Since the RequestForInformation class is its own Aggregate root, it will have its own Repository, as shown in Figure 6.4.

RFI Aggregate Repository.

Figure 6.4. RFI Aggregate Repository.

Although the Project Aggregate, Company Aggregate, and Submittal Aggregate are part of the RFI Aggregate, I will not be covering their respective Repositories here because they have already been covered in the previous chapters. I will only be covering the RFI Repository in this chapter.

The IRequestForInformationRepository interface provides access to instances of RFI Repositories. Here is the IRequestForInformationRepository interface:

using System;
using System.Collections.Generic;
using SmartCA.Infrastructure.RepositoryFramework;
using SmartCA.Model.Projects;

namespace SmartCA.Model.RFI
{
    public interface IRequestForInformationRepository
        : IRepository<RequestForInformation>
    {
        IList<RequestForInformation> FindBy(Project project);
    }
}

Its only unique method, FindBy, should be called fairly often, as almost all of the time RFIs will only be looked at on a per-project basis.

Writing the Unit Tests

In this section, I will be writing some unit tests of what I expect of the Submittal Repository implementation. As noted before, these tests will compile correctly, but they will also fail until I write the code for the Repository implementation later on in the Solution section.

Please note that there will be more unit tests in the accompanying code for this chapter, but for brevity's sake I am showing the tests that I think are important here.

The FindRfisByProjectTest Method

The purpose of the FindSubmittalsByProjectTest method is to validate that the correct number of Submittal instances have been returned by the Submittal Repository for a given Project.

/// <summary>
/// A test for FindBy(Project project)
/// </summary>
[DeploymentItem("SmartCA.sdf"), TestMethod()]
public void FindRfisByProjectTest()
{
    // Get a Project reference
    Project project =
        ProjectService.GetProject("5704f6b9-6ffa-444c-9583-35cc340fce2a");

    // Find all of the RFI's for the Project
    IList<RequestForInformation> rfis = this.repository.FindBy(project);

   // Verify that at least one RFI was returned
   Assert.IsTrue(rfis.Count > 0);
 }

This method starts out by getting a Project instance from the ProjectService class. It then calls the FindBy method on the repository to get the list of RFI's for the given Project instance. The method finishes by checking that the repository returned at least one RequestForInformation.

The AddRfiTest Method

The purpose of the AddRfiTest method is to test adding a new RFI to the RFI Repository:

/// <summary>
///A test for Add(RequestForInformation item)
///</summary>
[DeploymentItem("SmartCA.sdf"), TestMethod()]
public void AddRfiTest()
{
    // Create a new RequestForInformation
    Guid projectKey = new Guid("5704f6b9-6ffa-444c-9583-35cc340fce2a");
    RequestForInformation rfi = new RequestForInformation(projectKey, 2);
    IList<ItemStatus> statuses = SubmittalService.GetItemStatuses();
    rfi.From = ProjectService.GetProject(projectKey).Contacts[0];
    rfi.Status = statuses[0];
    rfi.Contractor = CompanyService.GetAllCompanies()[0];
    IList<SpecificationSection> specSections =
SubmittalService.GetSpecificationSections();
    rfi.SpecSection = specSections[0];

    // Add the RFI to the Repository
    this.repository.Add(rfi);

    // Commit the transaction
    this.unitOfWork.Commit();

    // Reload the RFI and verify it's number
    RequestForInformation savedRfi = this.repository.FindBy(rfi.Key);
    Assert.AreEqual(2, savedRfi.Number);

    // Clean up
    this.repository.Remove(savedRfi);
    this.unitOfWork.Commit();
}

This test is a little more complicated than the last test. It starts out by creating a Project Key value, and then passes the Project Key value as well as an RFI number into the constructor of the RequestForInformation class. Now that I have an initialized the RequestForInformation instance, the next step is to set the From property of the RequestForInformation instance with a ProjectContact instance. The next property that needs to be set is the Status property. The Status property is set to the value of the first ItemStatus in the list of all ItemStatus instances. I then set the Contractor property with a Company instance that is retrieved by the CompanyService class. Last, I get the list of all Specification Sections from the SubmittalService class and set the RFI's SpecSection property to the first value in the list of Specification Sections.

The next step is to add the RFI to the repository, and then to commit the transaction by calling the Commit method on the IUnitOfWork instance. The Commit method is important because that method calls back into the RFI Repository to tell it to write the RFI's data to the data store.

Once the RFI has been saved, it is then reloaded and the RFI's Number property is checked to verify that the Add and Commit methods worked properly. The last task that the method needs to perform is to remove the RFI. Removing the RFI that was just created leaves the data store in the same state as it was in before the method started, which is important for the rest of the tests that may depend on a known state of the data store.

The UpdateRfiTest Method

The purpose of the UpdateTest method is to find an RFI and update it with a different DateReceived property value, and then verify that the change was persisted properly.

/// <summary>
///A test for Updating an RFI
///</summary>
[DeploymentItem("SmartCA.sdf"), TestMethod()]
public void UpdateRfiTest()
{
    IList<RequestForInformation> rfis = this.repository.FindAll();

    // Change the RFI's DateReceived value
DateTime dateReceived = DateTime.Now;
    rfis[0].DateReceived = dateReceived;

    // Update the Repository
    this.repository[rfis[0].Key] = rfis[0];

    // Commit the transaction
    this.unitOfWork.Commit();

    // Verify that the change was saved
    IList<RequestForInformation> refreshedRfis = this.repository.FindAll();
    Assert.AreEqual(dateReceived.Date,
        refreshedRfis[0].DateReceived.Value.Date);
}

In this method I start out by getting the entire list of RFIs from the data store. I then proceed to change the DateReceived property value on the first RFI in the list, and then call the indexer method of the IRequestForInformationRepository. After the call to the indexer, I then use the IUnitOfWork interface to commit the transaction. Last, I verify that the change actually made it to the data store by reloading the same RFI and checking to see if its DateReceived property value is the same calendar date that I just assigned to the RFI earlier in the method.

The RemoveRfiTest Method

The purpose of the RemoveRfiTest method is to test the process of removing an RFI from the data store.

/// <summary>
///A test for Remove(RequestForInformation item)
///</summary>
[DeploymentItem("SmartCA.sdf"), TestMethod()]
public void RemoveRfiTest()
{
    IList<RequestForInformation> rfis = this.repository.FindAll();

    // Remove the RFI from the Repository
    this.repository.Remove(rfis[0]);

    // Commit the transaction
    this.unitOfWork.Commit();


     // Verify that there is now one less RFI in the data store
     IList<RequestForInformation> refreshedRfis = this.repository.FindAll();
     Assert.AreEqual(0, refreshedRfis.Count);

     // Reset the state
     this.AddRfiTest();
}

The first line of this method should look familiar; I am getting the entire list of RFIs from the data store. I then remove the first RFI in the list from the repository. After removing the RFI from the repository, I then use the IUnitOfWork interface to commit the transaction. Next, I verify that the change actually made it to the data store by using the repository to find all of the RFI instances and making sure there is now one less RFI than before. Last, I call the AddRfiTest method to add the RFI I just deleted back into the data store in order to reset the original state of the data store.

The Solution

Now that I have finished going over the design the RFI domain model, Aggregate, and repository, it's time to do my favorite thing: write some code! In this section I will be implementing these designs, as well as implementing the ViewModel and the View for RFIs.

The RFI Class Private Fields and Constructors

There are two constructors for the RFI class, and they both take a projectKey parameter of type System.Object and a number (integer) parameter. Every RFI must have a number and belong to a Project, so that is why those arguments are in both constructors.

Again, as in the last chapter, I am using a key value for a Project instead of a full blown Project instance, since I can always get to the Project via the ProjectService class. The second constructor takes a key argument of type System.Object, thus following the existing pattern for creating instances of existing Entity classes.

using System;
using SmartCA.Infrastructure.DomainBase;
using SmartCA.Model.Submittals;
using SmartCA.Model.Employees;
using SmartCA.Model.Projects;
using System.Collections.Generic;
using System.Text;
using SmartCA.Model.Companies;

namespace SmartCA.Model.RFI
{
    public class RequestForInformation : EntityBase
    {
        private object projectKey;
        private int number;
        private DateTime transmittalDate;
        private ProjectContact from;
        private int totalPages;
        private Delivery deliveryMethod;
        private string otherDeliveryMethod;
        private string phaseNumber;
        private bool reimbursable;
        private bool final;
        private List<CopyTo> copyToList;
        private DateTime? dateReceived;
        private DateTime? dateRequestedBy;
        private Company contractor;
private SpecificationSection specSection;
        private List<RoutingItem> routingItems;
        private string question;
        private string description;
        private string contractorProposedSolution;
        private bool change;
        private int cause;
        private int origin;
        private ItemStatus status;
        private DateTime? dateToField;
        private string shortAnswer;
        private string longAnswer;
        private string remarks;
        private RequestForInformationNumberSpecification numberSpecification;
        private RequestForInformationDateSpecification dateToFieldSpecification;
        private RequestForInformationQuestionAnswerSpecification
questionAnswerSpecification;

        public RequestForInformation(object projectKey, int number)
            : this(null, projectKey, number)
        {
        }

        public RequestForInformation(object key, object projectKey,
            int number) : base(key)
        {
            this.projectKey = projectKey;
            this.number = number;
            this.transmittalDate = DateTime.Now;
            this.from = null;
            this.totalPages = 1;
            this.deliveryMethod = Delivery.None;
            this.otherDeliveryMethod = string.Empty;
            this.phaseNumber = string.Empty;
            this.reimbursable = false;
            this.final = false;
            this.copyToList = new List<CopyTo>();
            this.dateReceived = null;
            this.dateRequestedBy = null;
            this.contractor = null;
            this.specSection = null;
            this.routingItems = new List<RoutingItem>();
            this.question = string.Empty;
            this.description = string.Empty;
            this.contractorProposedSolution = string.Empty;
            this.change = false;
            this.cause = 0;
            this.origin = 0;
            this.status = null;
            this.dateToField = null;
            this.shortAnswer = string.Empty;
            this.longAnswer = string.Empty;
this.remarks = string.Empty;
            this.numberSpecification = new
RequestForInformationNumberSpecification();
            this.dateToFieldSpecification = new
RequestForInformationDateSpecification();
            this.questionAnswerSpecification = new
RequestForInformationQuestionAnswerSpecification();
            this.Validate();
        }

All of the data for the RequestForInformation class is initialized and validated in the second constructor, which is called by the first constructor.

The RFI Properties

The properties of the RequestForInformation class are very similar to those of the Submittal class, so I am only going to show the differences here. Most of the properties in this class are fairly straightforward.

public DateTime? DateRequestedBy
        {
            get { return this.dateRequestedBy; }
            set { this.dateRequestedBy = value; }
        }

        public int DaysLapsed
        {
            get
            {
                int daysLapsed = 0;
                if (this.dateReceived.HasValue &&
                    this.dateToField.HasValue)
                {
                    daysLapsed =
this.dateToField.Value.Subtract(this.dateReceived.Value).Days;
                }
                return daysLapsed;
            }
        }

        public Company Contractor
        {
            get { return this.contractor; }
            set { this.contractor = value; }
        }

        public string Question
        {
            get { return this.question; }
            set { this.question = value; }
        }
public string Description
{
     get { return this.description; }
     set { this.description = value; }
}

public string ContractorProposedSolution
{
    get { return this.contractorProposedSolution; }
    set { this.contractorProposedSolution = value; }
}

public bool Change
{
    get { return this.change; }
    set { this.change = value; }
}

public int Cause
{
    get { return this.cause; }
    set { this.cause = value; }
}

public int Origin
{
    get { return this.origin; }
    set { this.origin = value; }
}

public string ShortAnswer
{
    get { return this.shortAnswer; }
    set { this.shortAnswer = value; }
}

public string LongAnswer
{
    get { return this.longAnswer; }
    set { this.longAnswer = value; }
}

public RequestForInformationNumberSpecification NumberSpecification
{
    get { return this.numberSpecification; }
}

public RequestForInformationDateSpecification DateToFieldSpecification
{
    get { return this.dateToFieldSpecification; }
}
public RequestForInformationQuestionAnswerSpecification
QuestionAnswerSpecification
        {
            get { return this.questionAnswerSpecification; }
        }

The DaysLapsed Property

This read-only property represents the difference in time from when the RFI was received to when it was sent to the field.

The NumberSpecification Property

This property is designed to model the business rules about the proper numbering of RFIs. The NumberSpecification property is represented by the RequestForInformationNumberSpecification class. Its only job is to validate that the RFI adheres to the numbering rules, which are, if you remember, that all RFIs must be numbered consecutively within a Project, and there cannot be duplicate RFI numbers within a Project.

using System;
using SmartCA.Infrastructure.Specifications;
using System.Collections.Generic;
using SmartCA.Model.Projects;
using System.Linq;

namespace SmartCA.Model.RFI
{
    public class RequestForInformationNumberSpecification
        : Specification<RequestForInformation>
    {
        public override bool IsSatisfiedBy(RequestForInformation candidate)
        {
            bool isSatisfiedBy = true;

            // Make sure that the same RFI number has not been used for the
            // current project, and that there are no gaps between RFI numbers

            // First get the project associated with the RFI
            Project project = ProjectService.GetProject(candidate.ProjectKey);

            // Next get the list of RFIs for the project
            IList<RequestForInformation> requests =
RequestForInformationService.GetRequestsForInformation(project);

            // Determine if the RFI number has been used before
            isSatisfiedBy = (requests.Where(rfi =>
rfi.Number.Equals(candidate.Number)).Count() < 1);

            // See if the candidate passed the first test
            if (isSatisfiedBy)
{
                // First test passed, now make sure that there are no gaps
                isSatisfiedBy = (candidate.Number - requests.Max(rfi =>
rfi.Number) == 1);
            }

            return isSatisfiedBy;
        }
    }
}

This code starts out by getting the list of RFIs for the current Project, which is the Project that is associated with the RFI. Once it has the list of RFIs, it then uses a LINQ query to determine whether the count of RFIs in the list that matches the candidate RFI's Number property is less than one. If the count is less than one, then the test passes.

The next test is to make sure that the candidate RFI will not introduce any numbering gaps within RFIs of the current Project. This is done with another LINQ query to get the highest RFI number (Max) in the list; then that number is subtracted from the candidate RFI's Number property. If the result equals one, then the test passes.

The DateToFieldSpecification Property

This property is designed to model the business rule about the dates associated with RFIs. The DateToFieldSpecification property is represented by the RequestForInformationDateSpecification class. Its only job is to validate that the RFI has both a date received value and a date requested by value.

using System;
using SmartCA.Infrastructure.Specifications;

namespace SmartCA.Model.RFI
{
    public class RequestForInformationDateSpecification
        : Specification<RequestForInformation>
    {
        public override bool IsSatisfiedBy(RequestForInformation candidate)
        {
            // Each RFI must have a date received and a date
            // that the response is needed
            return (candidate.DateReceived.HasValue &&
                candidate.DateRequestedBy.HasValue);
        }
    }
}

This code is much simpler than the first Specification class, as it only needs to perform two simple Boolean checks for the two dates.

The QuestionAnswerSpecification Property

This property is designed to model the business rule question and answer associated with RFIs. The QuestionAnswerSpecification property is represented by the RequestForInformationQuestionAnswerSpecification class. Its only job is to validate that the RFI has a question entered and either a short answer or a long answer entered.

using System;
using SmartCA.Infrastructure.Specifications;

namespace SmartCA.Model.RFI
{
    public class RequestForInformationQuestionAnswerSpecification
        : Specification<RequestForInformation>
    {
        public override bool IsSatisfiedBy(RequestForInformation candidate)
        {
            // The RFI must have a question and answer

            // The answer could be the short answer or
            // the long answer
            return (!string.IsNullOrEmpty(candidate.Question) &&
                (!string.IsNullOrEmpty(candidate.ShortAnswer) ||
                !string.IsNullOrEmpty(candidate.LongAnswer)));
        }
    }
}

This code is also performing Boolean comparisons by ensuring that the Question property is valid and that either the ShortAnswer or the LongAnswer property is valid.

The RFI Repository Implementation

After going over the IRequestForInformationRepository interface in the Design section, it is now time to explain how the RequestForInformation class is actually persisted to and from the data store by the RFI Repository. In this section, I will be writing the code for the RFI Repository.

The BuildChildCallbacks Method

If you have been following along, you know that the application's Template Method pattern implementation that I have been using in the repositories for getting Entity Root instances, the BuildChildCallbacks method, must be overridden in the RequestForInformationRepository.

#region BuildChildCallbacks

protected override void BuildChildCallbacks()
{
    this.ChildCallbacks.Add(ProjectFactory.FieldNames.ProjectContactId,
        this.AppendFrom);
    this.ChildCallbacks.Add("CopyToList",
        delegate(RequestForInformation rfi, object childKeyName)
{
             this.AppendCopyToList(rfi);
        });
    this.ChildCallbacks.Add("RoutingItems",
        delegate(RequestForInformation rfi, object childKeyName)
        {
                    this.AppendRoutingItems(rfi);
        });
}

#endregion

The AppendFrom Callback

The first entry made in the ChildCallbacks dictionary is for the AppendFrom method. Thanks to the ProjectService class's GetProjectContact method, this method's code is very simple:

private void AppendFrom(RequestForInformation rfi, object fromProjectContactKey)
{
    rfi.From = ProjectService.GetProjectContact(rfi.ProjectKey,
        fromProjectContactKey);
}

The AppendCopyToList and AppendRoutingItems Callbacks

You have probably noticed that the AppendCopyToList and AppendRoutingItems callbacks look identical to those from the Submittal Repository. Well, you are right! This signals me that I need to do some refactoring of classes and methods. In order to prevent code duplication, I have identified the "area" that needs refactoring, and that "area" is any code that deals with the transmittal aspect of a Submittal or an RFI. I will cover this refactoring in the next few paragraphs.

The Transmittal Refactoring

The whole reason for needing to do a refactoring was the RFI Repository was just about ready to have the same code as the Submittal Repository, and that code was for handling the CopyTo list and the RoutingItems list associated with the RFI. Looking at this a little bit further, it seems as if there is more in common between Submittals and RFIs. They both happen to be a document transmittal, and there is certain data around that transmittal that is common. The first step to refactor this was to put everything they have in common into an interface, and I decided to name this interface the ITransmittal interface.

using System;
using System.Collections.Generic;
using SmartCA.Model;

namespace SmartCA.Model.Transmittals
{
    public interface ITransmittal
    {
        object ProjectKey { get; }
        DateTime TransmittalDate { get; set; }
        int TotalPages { get; set; }
        Delivery DeliveryMethod { get; set; }
string OtherDeliveryMethod { get; set; }
        string PhaseNumber { get; set; }
        bool Reimbursable { get; set; }
        bool Final { get; set; }
        IList<CopyTo> CopyToList { get; }
    }
}

This interface contains all of the common properties associated with a document Transmittal, including the associated Project.

You may have noticed that there are no Routing Items in this interface, and that is because when I did my analysis of the existing application, I noticed that not all transmittals were always routable. To account for transmittals that could be routed, I created another interface, the IRoutableTransmittal interface:

using System;
using System.Collections.Generic;

namespace SmartCA.Model.Transmittals
{
    public interface IRoutableTransmittal : ITransmittal
    {
        IList<RoutingItem> RoutingItems { get; }
    }
}

This interface simply adds to the existing ITransmittal interface and adds a property for the Routing Items. The next step in the refactoring was to modify the Submittal and RequestForInformation classes to implement these interfaces. Luckily, these classes already contain all of these properties, so it was a very simple refactoring to change the class signatures:

public class RequestForInformation : EntityBase, IRoutableTransmittal

public class Submittal : EntityBase, IRoutableTransmittal

Remember, in the .NET Framework, you can only inherit from one base class, but you can implement as many interfaces as you like.

The next step was to add a new Repository to the inheritance chain so that the Transmittal-related behavior could be shared by both the SubmittalRepository and the RequestForInformationRepository classes. So I made a new Repository and called it SqlCeTransmittalRepository<T>. This Repository is an abstract class that inherits from SqlCeRepositoryBase<T>.

Here is the signature for the Repository:

public abstract class SqlCeTransmittalRepository<T> : SqlCeRepositoryBase<T>
        where T : EntityBase, ITransmittal

The next thing to add to this new class was the pass-through constructors to the SqlCeRepositoryBase<T> class:

#region Constructors

protected SqlCeTransmittalRepository()
    : this(null)
{
}

protected SqlCeTransmittalRepository(IUnitOfWork unitOfWork)
    : base(unitOfWork)
{
}

#endregion

Figure 6.5 shows a diagram of what the new RFI Aggregate Repository inheritance chain looks like now.

Figure 6.5 shows a diagram of what the new RFI Aggregate Repository inheritance chain looks like now.

Newly refactored RFI Aggregate Repository.

Figure 6.5. Newly refactored RFI Aggregate Repository.

The next function that I needed to perform was splitting out the code that deals with the CopyTo instances.

The AppendCopyToList Method

This method is now refactored to take a type of EntityBase and ITransmittal for its arguments:

protected void AppendCopyToList(T transmittal)
{
    StringBuilder builder = new StringBuilder(100);
    builder.Append(string.Format("SELECT * FROM {0}CopyList",
        this.EntityName));
    builder.Append(string.Format(" WHERE {0} = '{1}';",
        this.KeyFieldName,
        transmittal.Key));
    using (IDataReader reader = this.ExecuteReader(builder.ToString()))
    {
        while (reader.Read())
        {
            transmittal.CopyToList.Add(TransmittalFactory.BuildCopyTo(
                transmittal.ProjectKey, reader));
           }
        }
   }

This code is essentially the same as the code from the old version of the SubmittalRepository, but instead of being hard-coded to a Submittal instance or a RequestForInformation instance, it now only relies upon EntityBase and ITransmittal. The queries have been changed to be more generic as well. An important thing to note about this change is that I needed also to refactor the SqlCeRepositoryBase<T> class a little bit, with the addition of the EntityName and KeyFieldName properties. Also, all classes deriving from SqlCeRepositoryBase<T> now need to implement the following new abstract methods:

protected abstract string GetEntityName();
protected abstract string GetKeyFieldName();

Here is an example of how the RequestForInformationRepository class is implementing the methods:

#region GetEntityName

 protected override string GetEntityName()
 {
     return "RequestForInformation";
 }

     #endregion

     #region GetKeyFieldName

     protected override string GetKeyFieldName()
{
    return RequestForInformationFactory.FieldNames.RequestForInformationId;
}

#endregion

These follow the same Template Method Pattern that the SqlCeRepositoryBase<T> class has been implementing all along, as two more calls have been added to its constructor:

protected SqlCeRepositoryBase(IUnitOfWork unitOfWork)
    : base(unitOfWork)
{
    this.database = DatabaseFactory.CreateDatabase();
    this.entityFactory = EntityFactoryBuilder.BuildFactory<T>();
    this.childCallbacks = new Dictionary<string, AppendChildData>();
    this.BuildChildCallbacks();
    this.baseQuery = this.GetBaseQuery();
    this.baseWhereClause = this.GetBaseWhereClause();
    this.entityName = this.GetEntityName();
    this.keyFieldName = this.GetKeyFieldName();
 }

The DeleteCopyToList Method

This method has been refactored very similarly to the AppendCopyToList method.

protected void DeleteCopyToList(T transmittal)
{
   string query = string.Format("DELETE FROM {0}CopyList {1}",
       this.EntityName, this.BuildBaseWhereClause(transmittal.Key));
   this.Database.ExecuteNonQuery(
       this.Database.GetSqlStringCommand(query));
}

The only change in this method is that, just like the AppendCopyToList method, it has been made more generic and relies upon the EntityBase class as well as the ITransmittal interface in order to build its SQL query.

The InsertCopyToList and InsertCopyTo Methods

These methods keep following the same pattern in their refactoring as the others:

protected void InsertCopyToList(T transmittal)
{
    foreach (CopyTo copyTo in transmittal.CopyToList)
    {
        this.InsertCopyTo(copyTo, transmittal.Key);
    }
}
private void InsertCopyTo(CopyTo copyTo, object key)
{
   StringBuilder builder = new StringBuilder(100);
   builder.Append(string.Format("INSERT INTO {0}CopyList ({1},{2},{3}) ",
       this.EntityName,
       this.KeyFieldName,
       TransmittalFactory.FieldNames.ProjectContactId,
       TransmittalFactory.FieldNames.Notes));
   builder.Append(string.Format("VALUES ({0},{1},{2});",
       DataHelper.GetSqlValue(key),
       DataHelper.GetSqlValue(copyTo.Contact.Key),
       DataHelper.GetSqlValue(copyTo.Notes)));

   this.Database.ExecuteNonQuery(
       this.Database.GetSqlStringCommand(builder.ToString()));
}

Like the other methods, these methods are also aided by the new interface and the new properties and abstract methods of the SqlCeRepositoryBase<T> class.

The next piece of functionality that needed refactoring was the notion of routable Transmittals. As shown earlier, the IRoutableTransmital interface shows that a routable Transmittal is one that has RoutingItems associated with it.

public interface IRoutableTransmittal : ITransmittal
{
    IList<RoutingItem> RoutingItems { get; }
}

So the next logical step was to add a new abstract Repository to house the necessary code for RoutingItems. That class is the SqlCeRoutableTransmittalRepository<T> class, and its signature is shown here:

public abstract class SqlCeRoutableTransmittalRepository<T> :
        SqlCeTransmittalRepository<T>
        where T : EntityBase, IRoutableTransmittal

I am not going to show its pass-through constructors, since they are almost identical to the constructors in the SqlCeTransmittalRepository<T> class. As you might expect, this class is intended to isolate the code that deals with RoutingItem instances. It has three protected methods in it, AppendRoutingItems, DeleteRoutingItems, and InsertRoutingItems, and two private methods, DeleteRoutingItem and InsertRoutingItem. I am not going to show the code for these methods, as they are nearly identical to the old methods, and they have simply been modified, like all of the other newly refactored methods, to be more generic. Instead of depending upon the ITransmittal interface, the new methods in the SqlCeRoutableTransmittalRepository<T> class depend upon the IRoutableTransmittal interface.

Unit of Work Implementation

Following the same steps that I have shown before to implement the Unit of Work pattern, I need to override the following three methods: PersistNewItem(RequestForInformation item), PersistUpdatedItem(RequestForInformation item), and PersistDeletedItem(RequestForInformation item).

The PersistNewItem Method

The first method override for the RequestForInformationRepository's Unit of Work implementation is the PersistNewItem method:

protected override void PersistNewItem(RequestForInformation item)
        {
            StringBuilder builder = new StringBuilder(100);
            builder.Append(string.Format("INSERT INTO RequestForInformation
({0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},
{18},{19},{20},{21},{22},{23},{24},{25}) ",
            RequestForInformationFactory.FieldNames.RequestForInformationId,
            ProjectFactory.FieldNames.ProjectId,
            RequestForInformationFactory.FieldNames.RequestForInformationNumber,
            RequestForInformationFactory.FieldNames.TransmittalDate,
            RequestForInformationFactory.FieldNames.ProjectContactId,
            RequestForInformationFactory.FieldNames.TotalPages,
            RequestForInformationFactory.FieldNames.DeliveryMethod,
            RequestForInformationFactory.FieldNames.OtherDeliveryMethod,
            RequestForInformationFactory.FieldNames.PhaseNumber,
            RequestForInformationFactory.FieldNames.Reimbursable,
            RequestForInformationFactory.FieldNames.Final,
            RequestForInformationFactory.FieldNames.DateReceived,
            RequestForInformationFactory.FieldNames.DateRequestedBy,
            CompanyFactory.FieldNames.CompanyId,
            SubmittalFactory.FieldNames.SpecificationSectionId,
            RequestForInformationFactory.FieldNames.Question,
            RequestForInformationFactory.FieldNames.Description,
            RequestForInformationFactory.FieldNames.ContractorProposedSolution,
            RequestForInformationFactory.FieldNames.NoChange,
            RequestForInformationFactory.FieldNames.Cause,
            RequestForInformationFactory.FieldNames.Origin,
            RequestForInformationFactory.FieldNames.ItemStatusId,
            RequestForInformationFactory.FieldNames.DateToField,
            RequestForInformationFactory.FieldNames.ShortAnswer,
            RequestForInformationFactory.FieldNames.LongAnswer,
            RequestForInformationFactory.FieldNames.Remarks
            ));
            builder.Append(string.Format("VALUES
({0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},
{18},{19},{20},{21},{22},{23},{24},{25});",
                DataHelper.GetSqlValue(item.Key),
                DataHelper.GetSqlValue(item.ProjectKey),
                DataHelper.GetSqlValue(item.Number),
                DataHelper.GetSqlValue(item.TransmittalDate),
                DataHelper.GetSqlValue(item.From.Key),
                DataHelper.GetSqlValue(item.TotalPages),
                DataHelper.GetSqlValue(item.DeliveryMethod),
                DataHelper.GetSqlValue(item.OtherDeliveryMethod),
                DataHelper.GetSqlValue(item.PhaseNumber),
                DataHelper.GetSqlValue(item.Reimbursable),
                DataHelper.GetSqlValue(item.Final),
                DataHelper.GetSqlValue(item.DateReceived),
                DataHelper.GetSqlValue(item.DateRequestedBy),
DataHelper.GetSqlValue(item.Contractor),
                DataHelper.GetSqlValue(item.SpecSection),
                DataHelper.GetSqlValue(item.Question),
                DataHelper.GetSqlValue(item.Description),
                DataHelper.GetSqlValue(item.ContractorProposedSolution),
                DataHelper.GetSqlValue(item.Change),
                DataHelper.GetSqlValue(item.Cause),
                DataHelper.GetSqlValue(item.Origin),
                DataHelper.GetSqlValue(item.Status.Id),
                DataHelper.GetSqlValue(item.DateToField),
                DataHelper.GetSqlValue(item.ShortAnswer),
                DataHelper.GetSqlValue(item.LongAnswer),
                DataHelper.GetSqlValue(item.Remarks)));

            this.Database.ExecuteNonQuery(
                this.Database.GetSqlStringCommand(builder.ToString()));

            // Now do the child objects
            this.InsertCopyToList(item);
            this.InsertRoutingItems(item);
        }

The code builds up a large insert statement composed of the values from the RequestForInformation instance and then executes the query using the Microsoft Enterprise Library's Database object. After the insert statement has been executed, I have to account for inserting the CopyTo and RoutingItem instances for the RFI. I do this by calling the newly refactored InsertCopyToList and InsertRoutingItems methods, which all take an IRoutableTransmittal instance (which the RFI class implements) as their only argument.

The PersistUpdatedItem Method

PersistUpdatedItem first does an update to the RequestForInformation table:

protected override void PersistUpdatedItem(RequestForInformation item)
        {
            StringBuilder builder = new StringBuilder(100);
            builder.Append("UPDATE RequestForInformation SET ");

            builder.Append(string.Format("{0} = {1}",
                RequestForInformationFactory.FieldNames
.RequestForInformationNumber,
                DataHelper.GetSqlValue(item.Number)));

            builder.Append(string.Format(",{0} = {1}",
                RequestForInformationFactory.FieldNames.TransmittalDate,
                DataHelper.GetSqlValue(item.TransmittalDate)));
**************************************************************************
builder.Append(string.Format(",{0} = {1}",
                RequestForInformationFactory.FieldNames.Remarks,
                DataHelper.GetSqlValue(item.Remarks)));

            builder.Append(" ");
builder.Append(this.BuildBaseWhereClause(item.Key));

            this.Database.ExecuteNonQuery(
                this.Database.GetSqlStringCommand(builder.ToString()));

            // Now do the child objects

            // First, delete the existing ones
            this.DeleteCopyToList(item);
            this.DeleteRoutingItems(item);

            // Now, add the current ones
            this.InsertCopyToList(item);
            this.InsertRoutingItems(item);
        }

I have omitted several lines of repetitive code building the SQL update statement in the middle of the code in order save you from the boring code. The removed lines are represented by a single line of asterisks.

The second part of the method then uses the newly refactored DeleteCopyToList and DeleteRoutingItems helper methods to delete all of the child objects of the RFI, and then uses the also newly refactored InsertCopyToList and InsertRoutingItems helper methods to add the existing child objects from the RFI to the database.

PersistDeletedItem

As I was writing the last method in RequestForInformationRepository to override, PersistDeletedItem, I realized that I could refactor that back into the base classes as well. Originally, the code I wrote looked like this:

protected override void PersistDeletedItem(RequestForInformation item)
{
    // Delete the child objects first
    this.DeleteCopyToList(item);
    this.DeleteRoutingItems(item);

    // Now delete the RFI
    string query = string.Format("DELETE FROM RequestForInformation {0}",
       this.BuildBaseWhereClause(item.Key));
    this.Database.ExecuteNonQuery(
        this.Database.GetSqlStringCommand(query));
}

After analyzing the code, I saw another opportunity to refactor it back into the SqlCeRepositoryBase<T> and SqlCeRoutableTransmittalRepository<T> classes. I will start with the SqlCeRepositoryBase<T> implementation:

protected override void PersistDeletedItem(T item)
{
    // Delete the Entity
    string query = string.Format("DELETE FROM {0} {1}",
this.entityName,
                 this.BuildBaseWhereClause(item.Key));
             this.Database.ExecuteNonQuery(
                 this.Database.GetSqlStringCommand(query));
        }

This was made possible by the Template Method Pattern implemented in the earlier refactoring for the EntityName and EntityKey properties of the SqlCeRepositoryBase<T> class. This method is now generic enough to delete any Entity from the database. So the logical question now is, "what about when the Entity has children that must be deleted first?" The answer comes in overriding the PersistDeletedItem method in derived classes. I took this a step further with the concept of deleting Transmittals and added functionality in the SqlCeRoutableTransmittalRepository<T> class to do just this:

protected override void PersistDeletedItem(T transmittal)
{
    // Delete the child objects first
    this.DeleteCopyToList(transmittal);
    this.DeleteRoutingItems(transmittal);

   // Delete the transmittal entity
   base.PersistDeletedItem(transmittal);
}

This is great because now I can delete whatever child objects I want to delete first, and then call the base class, in this case SqlCeRepositoryBase<T>, to do the rest. So in the SubmittalRepository, the implementation now becomes:

protected override void PersistDeletedItem(Submittal item)
{
    // Delete the child objects first
    this.DeleteTrackingItems(item);

    // Now delete the submittal and its associated
    // transmittal objects
    base.PersistDeletedItem(item);
}

Because Tracking Items are not part of the ITransmittal interface, I needed to delete these first, and then by calling the base class, in this case the SqlCeRoutableTransmittalRepository<T> class, I am able to delete the rest of the child objects (the CopyToList and the RoutingItems) as well as the Entity itself. What is even better is that in the RequestForInformationRepository class (and in a few other repositories), the need to override the PersistDeletedItem method goes away completely!

The RFI Service Implementation

Still in this application, the only Service classes I have implemented up to this point are all Service classes that live in the domain model layer and are acting as facades to their respective Repository interfaces.

The RequestForInformationService class is responsible for retrieving and saving RequestForInformation instances.

using System;
using SmartCA.Infrastructure;
using SmartCA.Infrastructure.RepositoryFramework;
using SmartCA.Model.Projects;
using System.Collections.Generic;

namespace SmartCA.Model.RFI
{
    public static class RequestForInformationService
{
    private static IRequestForInformationRepository repository;
        private static IUnitOfWork unitOfWork;

        static RequestForInformationService()
        {
            RequestForInformationService.unitOfWork = new UnitOfWork();
            RequestForInformationService.repository =
                RepositoryFactory.GetRepository<IRequestForInformationRepository,
                RequestForInformation>(RequestForInformationService.unitOfWork);
        }

        public static IList<RequestForInformation>
            GetRequestsForInformation(Project project)
        {
            return RequestForInformationService.repository.FindBy(project);
        }

        public static void SaveRequestForInformation(RequestForInformation rfi)
        {
            RequestForInformationService.repository[rfi.Key] = rfi;
            RequestForInformationService.unitOfWork.Commit();
        }
    }
}

This class is mainly just acting as a façade in front of the IRequestForInformationRepository instance. There is nothing really new in this Service class compared to the other ones.

The RFI ViewModel Classes

Following the same patterns for all ViewModel classes as before, the RequestForInformationViewModel class adapts the RFI Aggregate from the domain model to the UI. When I started coding the ViewModel for the RFI I noticed that there was a lot in common between the SubmittalViewModel and the RequestForInformationViewModel, so I did another major refactoring and created a new abstract ViewModel class called, you guessed it, the TransmittalViewModel<T> class.

The TransmittalViewModel<T> Class

This class is very similar to the SqlCeTransmittalRepository<T> and SqlCeRoutableTransmittalRepository<T> classes that I showed earlier. It is the same concept again, which is to refactor common functionality into an abstract base class and have future classes that share the same functionality inherit them from the new base class. In this case, just like with the Repository classes and the SqlCeRepositoryBase<T> class, there is already an abstract base class that my ViewModel classes inherit from, and that is the ViewModel class. The TransmittalViewModel<T> class will be extending this class and it will be abstract as well. Here is the signature for the class:

public abstract class TransmittalViewModel<T> : ViewModel
        where T : EntityBase, IRoutableTransmittal

This is following the same pattern as the SqlCeTransmittalRepository<T> and SqlCeRoutableTransmittalRepository<T> classes, since it is a generic class and is using constraints to make sure that the generic class is an EntityBase that implements the IRoutableTransmittal interface.

The Constructor

My goal with this class was to lift all of the Transmittal behavior out of the SubmittalViewModel class and put it into this class. Therefore, the constructor code you see below should look very much like the old SubmittalViewModel code, with all references to anything named "submittal" changed to "transmittal."

#region Constructors

public TransmittalViewModel()
    : this(null)
{
}

public TransmittalViewModel(IView view)
    : base(view)
{
    this.currentTransmittal = null;
    this.transmittalList = this.GetTransmittals();
    this.transmittals = new CollectionView(this.transmittalList);
    this.specificationSections
        = SubmittalService.GetSpecificationSections();
    this.itemStatuses = SubmittalService.GetItemStatuses();
    this.mutableCopyToList = new BindingList<MutableCopyTo>();
    this.routingItems = new BindingList<RoutingItem>();
    this.deliveryMethods = new CollectionView(
                               Enum.GetNames(typeof(Delivery)));
    this.disciplines = SubmittalService.GetDisciplines();
    this.saveCommand = new DelegateCommand(this.SaveCommandHandler);
    this.newCommand = new DelegateCommand(this.NewCommandHandler);
    this.deleteCopyToCommand =
        new DelegateCommand(this.DeleteCopyToCommandHandler);
    this.deleteRoutingItemCommand =
        new DelegateCommand(this.DeleteRoutingItemCommandHandler);
 }

#endregion

I was able to reuse almost everything in the old constructor, except for the Tracking Items, which are not part of what I have defined for a Transmittal. I decided for now to leave the calls in to the SubmittalService, although that class is also a candidate for refactoring.

Notice how the GetTransmittals method is called in order to initialize the list of Transmittals. This is an abstract method of the TransmittalViewModel<T> class, and thus I am once again using the Template Method pattern. I will show more on this method later.

The Properties

The CurrentTransmittal property is extremely similar to the old CurrentSubmittal property of the SubmitalViewModel class:

public T CurrentTransmittal
        {
            get { return this.currentTransmittal; }
            set
            {
                if (this.currentTransmittal != value)
                {
                    this.currentTransmittal = value;
                    this.OnPropertyChanged(Constants
.CurrentTransmittalPropertyName);
                    this.OnPropertyChanged("Status");
                    this.saveCommand.IsEnabled = (this.currentTransmittal != null);
                    this.PopulateTransmittalChildren();
                }
            }
       }

The only difference between this code and the SubmitalViewModel CurrentTransmittal property code is that this code is more generic. Don't you just love Generics? I bet you can't tell that I do!

The PopulateSubmittalChildren method has been changed to the PopulateSubmittalChildren method. Here is the old method:

private void PopulateSubmittalChildren()
{
    this.PopulateMutableCopyToList();
    this.PopulateRoutingItems();
    this.PopulateTrackingItems();
}

And here is the new method:

protected virtual void PopulateTransmittalChildren()
{
    this.PopulateMutableCopyToList();
    this.PopulateRoutingItems();
}

The only difference in this method is that it no longer tries to populate the Tracking Items data, and that is exactly why I made it virtual, because derived classes may need to override this method in order to populate their own child objects as necessary.

The PopulateMutableCopyToList method has been changed from this:

private void PopulateMutableCopyToList()
{
     if (this.currentSubmittal != null)
     {
         this.mutableCopyToList.Clear();
         foreach (CopyTo copyTo in this.currentSubmittal.CopyToList)
         {
             this.mutableCopyToList.Add(new MutableCopyTo(copyTo));
         }
         this.OnPropertyChanged(Constants.MutableCopyToListPropertyName);
     }
}

To this:

private void PopulateMutableCopyToList()
{
    if (this.currentTransmittal != null)
    {
        this.mutableCopyToList.Clear();
        foreach (CopyTo copyTo in this.currentTransmittal.CopyToList)
        {
            this.mutableCopyToList.Add(new MutableCopyTo(copyTo));
        }
        this.OnPropertyChanged(Constants.MutableCopyToListPropertyName);
    }
}

I am not going to show the PopulateRoutingItems method because it follows the exact same pattern as the PopulateMutableCopyToList method.

The Command Handler Methods

Refactoring the Command Handler methods was a little bit trickier than some of the other methods in the TransmittalViewModel class. The SaveCommandHandler and the NewCommandHandler methods both had to be marked as virtual, and that is because I could only pull so much out of them into this, and the rest that is specific to the derived class must be overridden.

For example, the NewCommandHandler went from this:

private void NewCommandHandler(object sender, EventArgs e)
{
   Submittal newSubmittal = new Submittal(
                                this.currentSubmittal.SpecSection,
                                this.currentSubmittal.ProjectKey);
newSubmittal.SpecSectionSecondaryIndex = "01";

            this.currentSubmittal = null;
            this.mutableCopyToList.Clear();
            this.routingItems.Clear();
            this.trackingItems.Clear();
            this.CurrentObjectState = ObjectState.New;
            this.OnPropertyChanged(
                Constants.CurrentSubmittalPropertyName);

            this.submittalsList.Add(newSubmittal);
            this.submittals.Refresh();
            this.submittals.MoveCurrentToLast();
        }

To this:

protected virtual void NewCommandHandler(object sender, EventArgs e)
{
    this.currentTransmittal = null;
    this.mutableCopyToList.Clear();
    this.routingItems.Clear();
    this.CurrentObjectState = ObjectState.New;
    this.OnPropertyChanged(
        Constants.CurrentTransmittalPropertyName);
}

The code that initializes the new Entity (i.e., the Submittal class in the first example above) had to be removed and must be overridden in the derived ViewModel. I also needed to remove the code that added the Submittal to the list of Submittals because I needed that to happen last in the derived class, after this code executes.

The GetTransmittals Template Pattern Method

As seen in the constructor, the GetTransmittals abstract method is called in order to initialize the list of Transmittals for the class. Here is the signature of this method:

protected abstract List<T> GetTransmittals();

This is great because by doing this I am delegating the derived class to get the right list of objects, yet I can still code against that list in my base class. Combining the Template Method pattern with Generics is a great thing!

The RequestForInformationViewModel Class

Now the fruits of our ViewModel refactoring labor start to pay off. The code inside of the RequestForInformationViewModel and SubmittalViewModel classes has been reduced significantly. Here is what the signature of the RequestForInformationViewModel class looks like when deriving from the TransmittalViewModel<T> class:

public class RequestForInformationViewModel
     : TransmittalViewModel<RequestForInformation>

Notice how the generic parameter from the TransmittalViewModel<T> class is replaced by the RequestForInformation class.

The Constructor

The constructors for the RequestForInformationViewModel class now are mostly pass-through. Here is the old SubmittalViewModel constructor:

#region Constructors

public SubmittalViewModel()
    : this(null)
{
}

public SubmittalViewModel(IView view)
    : base(view)
{
    this.currentSubmittal = null;
    this.submittalsList = new List<Submittal>(
                              SubmittalService.GetSubmittals(
                              UserSession.CurrentProject));
    this.submittals = new CollectionView(this.submittalsList);
    this.specificationSections
        = SubmittalService.GetSpecificationSections();
    this.submittalStatuses = SubmittalService.GetSubmittalStatuses();
    this.toList = UserSession.CurrentProject.Contacts;
    this.mutableCopyToList = new BindingList<MutableCopyTo>();
    this.routingItems = new BindingList<RoutingItem>();
    this.trackingItems = new BindingList<TrackingItem>();
    this.fromList = EmployeeService.GetEmployees();
    this.trackingStatusValues = new CollectionView(
                                    Enum.GetNames(typeof(ActionStatus)));
    this.deliveryMethods = new CollectionView(
                               Enum.GetNames(typeof(Delivery)));
    this.disciplines = SubmittalService.GetDisciplines();
    this.saveCommand = new DelegateCommand(this.SaveCommandHandler);
    this.newCommand = new DelegateCommand(this.NewCommandHandler);
    this.deleteCopyToCommand =
        new DelegateCommand(this.DeleteCopyToCommandHandler);
    this.deleteRoutingItemCommand =
        new DelegateCommand(this.DeleteRoutingItemCommandHandler);
    this.deleteTrackingItemCommand =
        new DelegateCommand(this.DeleteTrackingItemCommandHandler);
}

#endregion

Here are the new constructors for the RequestForInformationViewModel class:

#region Constructors

public RequestForInformationViewModel()
    : this(null)
{
}

public RequestForInformationViewModel(IView view)
    : base(view)
{
    this.toList = UserSession.CurrentProject.Contacts;
    this.fromList = UserSession.CurrentProject.Contacts;
}

#endregion

That's quite a reduction in code! The toList and fromList private fields are not contained in the base class and therefore need to be initialized here.

The Properties

There are not many properties left to implement in the RequestForInformationViewModel class. Here is all of the code for the properties:

#region Properties

public IList<ProjectContact> ToList
{
    get { return this.toList; }
}

public IList<ProjectContact> FromList
{
    get { return this.fromList; }
}

#endregion

The Command Handler Methods

The only command handler methods that I need to override in the RequestForInformationViewModel class are the NewCommandHandler and SaveCommandHandler methods. The DeleteCommandHandler method is completely taken care of by the base class.

Because the NewCommandHandler method in the base class was marked as virtual, I am still able to use it as well as add my own functionality:

protected override void NewCommandHandler(object sender, EventArgs e)
{
    base.NewCommandHandler(sender, e);
    RequestForInformation newRfi = new RequestForInformation(
                                 this.CurrentTransmittal.ProjectKey,
                                 this.CurrentTransmittal.Number + 1);
    this.TransmittalList.Add(newRfi);
    this.Transmittals.Refresh();
    this.Transmittals.MoveCurrentToLast();
}

Notice how on the first line of the method I call the same method in the base class. This allows me to reuse the common code yet gives me the flexibility to do my own housekeeping when creating the new RequestForInformation instance.

The GetTransmittals Template Pattern Method

As I mentioned before, the GetTransmittals method is overridden in the derived classes because only they know where to get their data; the base class does not need to know about that:

#region GetTransmittals

        protected override List<RequestForInformation> GetTransmittals()
        {
            return new
List<RequestForInformation>(RequestForInformationService.GetRequestsForInformation(
                                      UserSession.CurrentProject));
        }

        #endregion

In this case of the RequestForInformationViewModel class's override, I am simply calling out to the RequestForInformationService to get the list of RequestForInformation instances.

The RFI View

The View for RFIs is almost exactly identical to that for Submittals. Figure 6.6 shows what the form looks like at run time.

The RFI View.

Figure 6.6. The RFI View.

Following the same pattern as before, the form is split into two parts: the one on the left is for selecting an RFI to edit, and the one on the right is for editing the selected RFI. The New button adds a new RFI to the list. The Save and Cancel buttons both deal with the currently selected RFI.

I really do not need to show any of the XAML code for this form because there is really not much in it that is different from the Submittal form.

Summary

In this chapter, I introduced the concept of a Request for Information (RFI) in the construction industry and then used that concept to model the RFI Aggregate. I also introduced a new concept into the domain, called the Specification Pattern. This made some of the business rule modeling very clear by bringing business rules out from underneath class methods and placing them into their own Specification classes. I then defined the boundaries for the RFI Aggregate, as well as implementing all of the necessary domain model and Infrastructure classes necessary to work with those classes. I also did some major refactoring in this Chapter for the repositories and ViewModels dealing with the new concept of Transmittals.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.34.0