Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WCF by Example - Chapter I - Baseline

0.00/5 (No votes)
7 Jul 2010 1  
First draft version of the model, repository, and services.
Previous Next
Introduction Chapter II

The Series

WCF by example is a series of articles that describe how to design and develop a WPF client using WCF for communication and NHibernate for persistence purposes. The series introduction describes the scope of the articles and the project.

Chapter Overview

This is the first chapter of the series. This chapter sets the baseline for the eDirectory solution; we will introduce some core solution concepts as the Customer entity, the definition of the repository, a customer service, and our first couple tests.

The source code for this chapter can be found at CodePlex change set 67232. The latest code for the eDirectory solution is found at CodePlex.

The Model - Customer Entity

As we mentioned in our introduction, we will keep our model very simple, only one entity for the moment will be required. The customer class is declared as:

public class Customer
{
    protected Customer() { }

    public long Id { get; protected set; }
    public string FirstName { get; protected set; }
    public string LastName { get; protected set; }
    public string Telephone { get; protected set; }
}

Couple things to observed; properties are protected so they can only be changed by action methods and the class does not provide a public constructor. We should not assume that our classes are going to be used in the proper way so making the constructor protected provides a nice way to controll the creation of new instances. We will justify this approach when we cover persistence in a later chapter.

Also, entities are defined in the eDirectory.Domain assembly which will be used only by our server side components; however, the client does not use the domain classes.

The Customer Services

The eDirectory provides the following customer services:

Function Description
GetById Retrieves a customer by providing a unique ID
ListAll Returns all customer instances
CreateNewCustomer Creates a new customer instance
UpdateCustomer Amends an existing customer instance

Services are defined using WCF conventions. Input parameters will be primitives or data transfer objects; all the service methods are functions. We must keep our service signatures simple to avoid serialisation problems with WCF. We also want to provide a communication pattern between the client and server without relying on a specific WCF mechanism, we will see in later chapters how this aspect is resolved.

Also, service contracts (interfaces) are declared in a common assembly as both the server and client components require them. The same is true for the DTOs.

At this stage, we need our first DTO which looks very similar to our Customer entity:

public class CustomerDto
{
    public long CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Telephone { get; set; }
}

Now, we can define our first service contract:

[ServiceContract(Namespace = "http://wcfbyexample/customerservices/")]
public interface ICustomerService
{
    [OperationContract]
    CustomerDto CreateNewCustomer(CustomerDto customer);

    [OperationContract]
    CustomerDto GetById(long id);

    [OperationContract]
    CustomerDto UpdateCustomer(CustomerDto customer);

    [OperationContract]
    List<customerdto> FindAll();
}

Let's try to create a mechanism for creating customer instances.

Repository

Domain entities should be decoupled from the persistence back-end mechanism. The repository pattern provides the required persistence functionality to our domain entities.

At this point, the following repository definition is sufficient:

public interface IRepository<tentity />
{
    #region CRUD operations
        
    TEntity Save(TEntity instance);
    void Update(TEntity instance);
    void Remove(TEntity instance);
        
    #endregion

    #region Retrieval Operations

    TEntity GetById(long id);
    IQueryable<tentity /> FindAll();
        
    #endregion
}

The most important aspects to notice are that we have defined a generic repository and that the FindAll method returns an IQueryable instance.

Create Factory Method

At this point, we can go back to our domain class and provide an easy mechanism for creating customer instances:

public static Customer Create(IRepository<customer /> repository, CustomerDto operation)
{
    var instance = new Customer
                            {
                                FirstName = operation.FirstName,
                                LastName = operation.LastName,
                                Telephone = operation.Telephone
                            };

    repository.Save(instance);
    return instance;
}

See how the DTO instance is used to populate the properties of a new customer instance. Then the save method is executed against the passed repository. Instead of using a reference to the repository implementation in our entities, we will pass an instance of the repository to our entity methods and factories. This approach will evolve as we cover future aspects of the application. Another aspect to notice here is that the factory method creates persisted customer instances.

In-Memory Repository Implementation

At this point, we could start doing some TDD, we could mock repositories and so on, but providing an in-memory repository implementation is relatively inexpensive and leverages testing and business exploration. As the In-Memory persistor will never be used in a production environment, we want to declare this implementation in a different assembly: eDirectory.Naive. We define an abstract class for in-memory persistors with the common functionality:

public abstract class RepositoryEntityStore<tentity>
        :IRepository<tentity>
{
    protected readonly IDictionary<long,> RepositoryMap = new Dictionary<long,>();

    #region IRepository<tentity> Members

    public abstract TEntity Save(TEntity instance);

    public abstract void Update(TEntity instance);

    public abstract void Remove(TEntity instance);

    public TEntity GetById(long id)
    {
        return RepositoryMap[id];
    }

    public IQueryable<tentity> FindAll()
    {
        return RepositoryMap.Values.AsQueryable();
    }

    #endregion
}

Then we create the Customer implementation:

public class RepositoryCustomer
        : RepositoryEntityStore<customer>
{
    public override Customer Save(Customer instance)
    {
        ...
    }

    public override void Update(Customer instance)
    {
        ...
    }

    public override void Remove(Customer instance)
    {
        ...
    }

    private void GetNewId(Customer instance)
    {
        ...
    }

    private readonly IDictionary<type,> Setters = new Dictionary<type,>();

    private MethodInfo GetSetter(Type type)
    {
        ...
    }
}

There is plenty of room for improvement in the above code, but it is worth noting how the entities are stored in a hashed map using the entity ID. As I mentioned in the series introduction, we have full control over our database design so we have chosen to have a numeric primary key in all our tables which eases the domain entities definition. In later chapters, we will see how the above code evolves and one single implementation for all entity types alone is required.

Our First Test

Let's create a customer using the factory and then we check that the ID and first name are correct:

[TestClass]
public class CustomerTests
{
    [TestMethod]
    public void CreateCustomer()
    {
        // need to create an instance of the repository
        var repository = new RepositoryCustomer();
        var dto = new CustomerDto
                                    { 
                                        FirstName = "Joe",
                                        LastName = "Bloggs",
                                        Telephone = "9999-8888"
                                    };

        var customer = Customer.Create(repository, dto);
        Assert.IsFalse(customer.Id == 0, "Customer Id should have been updated");
        Assert.AreSame(customer.FirstName, dto.FirstName, "First Name are different");
    }
}

Customer Service Implementation

We are now ready to create our first service implementation:

public class CustomerService
        :ICustomerService
{
    public IRepository<customer> Repository { get; set; }
        
    #region ICustomerService Members

    public Common.Dto.Customer.CustomerDto CreateNewCustomer(CustomerDto dto)
    {            
        var customer = Customer.Create(Repository, dto);
        return new CustomerDto
                            {
                                CustomerId = customer.Id,
                                FirstName = customer.FirstName,
                                LastName = customer.LastName,
                                Telephone = customer.Telephone
                            };
    }
    ...
}

Let's create a test that executes this service:

[TestClass]
public class CustomerServiceTests
{
    [TestMethod]
    public void CreateCustomer()
    {
        // need to create an instance of the repository
        var service = new CustomerService { Repository = new RepositoryCustomer() };
        var dto = new CustomerDto
                            {
                                FirstName = "Joe",
                                LastName = "Bloggs",
                                Telephone = "9999-8888"
                            };

        var customer = service.CreateNewCustomer(dto);
        Assert.IsFalse(customer.CustomerId == 0, 
            "Customer Id should have been updated");
        Assert.AreSame(customer.FirstName, dto.FirstName, 
                       "First Name are different");
    }
}

Chapter Conclusion

We have covered a lot of aspects in this article. In the first place, we set up a few projects: Common, Domain, and Naive. We described the basics about designing domain entities and their relation to the repository. We concluded the chapter using the service to create a customer instance and wrote our first couple tests.

In the next chapter, we will discuss some of the proposed patterns used so far, we will identify a few aspects that could be improved, and we will introduce the Repository Locator concept.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here