Click here to Skip to main content
15,861,172 members
Articles / Web Development / IIS

WCF by Example - Chapter I - Baseline

Rate me:
Please Sign up or sign in to vote.
4.83/5 (35 votes)
19 Dec 2012CPOL5 min read 125.2K   172   20
First draft version of the model, repository, and services.
PreviousNext
IntroductionChapter 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. In later chapters we discuss how to evolve the original design described in this articles and how we can streamline the persistence components so when additional new entities are required, there is very little effort in getting this side of the solution working. 

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:

C#
public class Customer
{
    protected Customer() { }

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

Couple things to observe; properties are protected so they can only be changed by action methods; the class does not provide a public constructor, in this way we strive in setting up classes where their state can only be changed by using well known methods declared within the entity. 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 control the creation of new instances. We will justify this approach when we cover persistence in a Chapter II - Repositories

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, instead DTOs are provided to the client. 

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 the following conventions:

  • Input parameters will be primitives or data transfer objects
  • All the service methods are functions, that is, they all return some minimal information back to the client.
We must keep our service signatures simple so WCF serialization problems are avoided. We also want to provide a communication pattern between the client and server without relying on a specific WCF mechanism, we will see in Chapter III - Response how this aspect is resolved. 

Also, service contracts (interfaces) are declared in a common assembly as both the server and client components require them, we will not use the Web References wizard feature in the client side to generate WCF proxies in the client side. The same is true for the DTOs where they are shared across both the server and client components. 

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

C#
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:

C#
[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 fulfilling the decoupling aspect.

At this point, the following repository definition is sufficient:

C#
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 a mechanism to facilitate the creation of customer instances: 

C#
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, so when the customer instance is returned, the persistence context is fully aware of the new instance, in fact the Id is available after the save method is invoked.

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:

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

    #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:

C#
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 the next later chapter: Chapter II - Repositories , we will see how the above code evolves and one single implementation for all entity types alone is only required. 

Our First Test

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

C#
[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:

C#
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:

C#
[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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionclass RepositoryEntityStore<tentity> Pin
Member 34156439-Jul-12 21:54
Member 34156439-Jul-12 21:54 
GeneralRe: class RepositoryEntityStore Pin
Enrique Albert9-Jul-12 22:31
Enrique Albert9-Jul-12 22:31 

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

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