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

WCF by Example - Chapter V - Contexts

, 23 Oct 2012
Rate this:
Please Sign up or sign in to vote.
Global and Request context pattern for server side resources
Previous Next
Chapter IV Chapter VI

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 discusses the architect solution at a high level.

Sharpy - A Metro Project

23 Oct: I am currently working on a new project for a Metro application: Sharpy. I intend to use the patterns discussed in WCF by Example for the service side of the application; the goal is to demonstrate how similar is the development of Metro applications to the type of applications we have seen so far. There is not code available yet but hopefully this will change soon. I hope you like it. The article is in here.

Chapter Overview

In "Chapter I - Baseline", a draft version of the CustomerService was defined. In "Chapter III - Response", the service was re-factored so business warnings and exceptions are always available at the client side. In "Chapter IV - Transaction Manager", we discussed the need for managing transactions, warnings and exceptions; two components were introduced: the Transaction Manager Factory and the ServiceBase. We did not completely resolve how business warnings are logged during the business logic processing.

Currently our services have a reference to an instance of the Transaction Manager Factory, a different implementation of the factory is used depending on whether the application is running in-memory or NHibernate mode. This technical requirement is normally resolved using dependency injection. However there is no need for having a container managing the creation of the services when a single instance of the factory fulfills the requirement, even in a multi-request scenario. As a result, we will use a different approach, we will provide a global context so the server components can gain access to a single instance of the factory. In a later chapter, we will see how DI is used to determine the factory implementation exposed by the Global Context. 

We also indicated in previous chapter that there is a need for a sort of mechanism so services and business logic can record business warnings. In Chapter IV, a mechanism was defined on the transaction manager that checks if business warnings were created during the business processing. We will see how this aspect is implemented in this chapter by means of the BusinessNotifier, a new component that we are introducing in this chapter.

As a result of a new type of services that are required across the server layers, we will discuss the need for a container of "vertical" services. Some of these services are created once for the whole life of the application where others are created only for the request duration.

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

Contexts

As with traditional web applications and/or web services, we deal with incoming requests that require to access services and data in an isolated fashion, these services and data are created during the request processing and must be terminated when the request is serviced. In WCF, the Instance Context represents the context information for a service instance and it can be used to provide exactly what we need for our request requirements. We will see in a later chapter the implementation of the request context using WCF extensions. For the time, in this chapter we will define the baseline for our container and contexts.

Global Context

The Global context exposes classes and data that can be used by our server components, they are request agnostic and are kept alive along with the application. A good approach with these global resources and services is not keeping any sort of state if feasible, concurrency issues may arise if the design is not correct. Locking mechanisms may help with the concurrency but may incur in performance degradation.

The Transaction Manager factory is an ideal candidate for being stored in the global context. The factory class does not manage any state and one single instance satisfy the application requirements for the creation of transaction managers even in a multi-request scenario.

public interface IGlobalContext
{
    ITransFactory TransFactory { get; }        
}

A single implementation of the IGlobalContext is required:

public class GlobalContext
    : IGlobalContext
{
    static readonly Object LocatorLock = new object();
    private static GlobalContext InternalInstance;
    
    private GlobalContext() { }
    
    public static GlobalContext Instance()
    {
        if (InternalInstance == null)
        {
            lock (LocatorLock)
            {
                // in case of a race scenario ... check again
                if (InternalInstance == null)
                {
                    InternalInstance = new GlobalContext();;
                }
            }
        }
        return InternalInstance;
    }
    
    #region IGlobalContext Members
    
    public ITransFactory TransFactory { get; set; }
    
    #endregion
}

It is worth noting that the TransFactory property has a public setter, we will address this issue when we cover dependency injection in a later chapter.

Request Context

In contrast to the global context, the request context is fully aware of the incoming requests. Its main role is to provide resources and services to individual requests ensuring that the request does not interfere with other requests' resources. Request resources are terminated when the request is serviced.

The BusinessNotifier needs to store business warnings for the duration of the request, as multiple requests might process concurrently a BusinessNotifier instance needs to be created for each request. The Request Context provides a single point for acquiring request resources like the BusinessNotifier.

public interface IRequestContext
{
    IBusinessNotifier Notifier { get; }
}

It is worth noting that where others assign an NHibernate session to the request when this one is created, our approach for this concern is going to be different. The unit of work solution presented in Chapter IV relays on the services creating a Transaction Manager using a factory class. Remember that it is the role of the factory to provide a RepositoryLocator to the Transaction Manager. Services then delegate to the Transaction Manager to execute their commands, in this way commands are given the RepositoryLocator that is required for accessing the back-end repositories. There are few pieces involved in this process but the design decouples responsibilities in a sleek manner so we achieve a very flexible design which is easily testable with its factory in combination with our services look after this aspect in a very slick manner.

For examples of the different approaches mentioned above, you may want to check the following links:

The Container

The container is just a helper wrapping the global and request contexts that helps locating request and global resources.

Previous
public class Container
{
    private static Container InternalInstace;
    
    private Container(){}
    
    private static Container Instance()
    {
        if (InternalInstace != null) return InternalInstace;
        InternalInstace = new Container();
        return InternalInstace;
    }
    
    public static IGlobalContext GlobalContext
    {
        get
        {
            return AppServices.GlobalContext.Instance();
        }
    }
    
    public static IRequestContext RequestContext { get; set; }
}

The GlobalContext property does not need to expose a setter as we will always use the same implementation. However, we mentioned that we require different implementations of the IRequestContext depending on if we are running in-memory or NHibernate mode. As mentioned above, we will resolve the public setter in a later chapter.

The BusinessNotifier

Business warnings are just string messages created to indicate the user of some incident or relevant situation that occurs during the business processing. The BusinessNotifier provides a method to the services and domain entities for adding new messages and the Transaction Manager checks for warnings and adds them to the response so they can be processed on the client side.

public class BusinessNotifier
    : IBusinessNotifier
{
    private readonly IList<BusinessWarning> WarningList = new List<BusinessWarning>();
    
    #region Implementation of IBusinessNotifier
    
    ...
    
    public IEnumerable<BusinessWarning> RetrieveWarnings()
    {
        if (!HasWarnings) return null;
        var results = WarningList.ToList();
        WarningList.Clear();
        return results;
    }
    
    #endregion
}

ServiceBase Re-factor

Now we can remove the Factory property in the ServiceBase class and obtain the transaction manager instance using the new Container class:

Before:

After:

Transaction Manager Changes

Now that the BusinessNotifier is available, we can enhance the Transaction Manager implementing the CheckForWarnings method that we left blank:

So the new implementation checks if warnings were added to the BusinessNotifier the method, if so it adds them to the response instance:

New Test

We have added some new core functionality so it is a good idea to increase the test coverage. Let's modify the FindAll service method in the CustomerService so a business warning is created if customer instances are not found:

public class CustomerService
        :ServiceBase, ICustomerService
    {
        
        ...

        public CustomerDtos FindAll()
        {
            return ExecuteCommand(locator => FindAllCommand(locator));
        }

        private CustomerDtos FindAllCommand(IRepositoryLocator locator)
        {
            var result = new CustomerDtos { Customers = new List<CustomerDto>() };
            locator.FindAll<Customer>().ToList()
                                .ForEach(c => result.Customers.Add(Customer_to_Dto(c)));

            if (result.Customers.Count() == 0) 
            { 
                Container.RequestContext.Notifier.AddWarning(
                    BusinessWarningEnum.Default, "No customer instances were found"); 
            }
            return result;
        }

        ...
    }

The new test then is:

[TestMethod]
public void CheckFindAllNotification()
{
    var result = Service.FindAll();
    Assert.IsTrue(result.Customers.Count == 0, "No customers were expected");
    Assert.IsTrue(result.Response.HasWarning, "Warning flag is not set");
    Assert.IsTrue(result.Response.BusinessWarnings.Count() == 1, 
	"One warning was only expected");
    Assert.AreSame(result.Response.BusinessWarnings.Single().Message, 
	"No customer instances were found");
    CreateCustomer();
    result = Service.FindAll();
    Assert.IsFalse(result.Response.HasWarning, "Warning flag is set");
}

Chapter Summary

With this chapter, we finish setting up the baseline of our server side components. We are now ready to start working in our rich client as we have established a comprehensive infrastructure on the server side for the client to work. We will see how the client can interact with our services and business domain classes without the need of having NHibernate nor WCF working. This approach aligns with Agile methodologies so we can start our business exploration and provide quick feedback to our customers without having the expensive back-end infrastructure in place.

The next chapter introduces the rich client and the basics of the MVVM using WPF. We will need another three or four chapters to define the basic infrastructure of the rich client.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Enrique Albert
Software Developer (Senior)
Ireland Ireland
No Biography provided
Follow on   Twitter

Comments and Discussions

 
QuestionContainer implementation PinmemberDenis Kozlov15-Mar-13 6:31 
GeneralMy vote of 5 Pinmemberrittersh20-Oct-10 23:06 
GeneralRe: My vote of 5 PinmemberEnrique Albert31-Oct-10 21:01 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 23 Oct 2012
Article Copyright 2010 by Enrique Albert
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid