Chapter 5. Instancing

WHAT'S IN THIS CHAPTER?

  • Using different InstanceContextModes

  • Explaining sessions

  • Defining ConcurrencyModes

  • Controlling the Service Life time

  • Using Throttling

  • Introducing Best Practices

  • Implementing throttling

Your proxy on the client side (typically derived from ClientBase<T>) communicates with objects on the server side via the so-called channel stack. The service host accommodates these objects on the server side. As you will have learned in previous chapters, SOAP messages are exchanged between the proxy and the object on the server side. The InstanceContextMode determines whether the client proxy now communicates with one and the same instance of the server object during a session (PerSession), or whether a new instance is created every time the server object is used (PerCall) and is then deleted again as soon as the method call is ended, or whether, last but not least, only one single server instance is created (Single), irrespective of the number of clients.

There is an assumption in classical object-oriented applications that an instance of a class is created and used for work or communication for a certain length of time. If certain properties are set for the object beforehand, they remain in place while there is a valid reference to the object, and the methods which are called up can work with the values which are set beforehand. If the Dispose method is called and the object reference is set to null, important resources are released, and the object can be deleted from the garbage collector during the next cycle. This is a similar type of instance management possible with WCF through PerSession mode.

However, the situation is rather different in distributed development — references do not point to local objects, they point to remote objects on a server or, in another application domain. Therefore, a different approach to classical, local programming is taken to create scalable environments which are as efficient as possible in terms of utilizing resources. The server objects are generally instantiated for the short duration of a method call and are released again immediately afterward. Values which are used for processing the method are either transferred again each time the method is called, or they are loaded from a persistent storage, such as a database. Consequently, it does not matter on which server the objects are created in a load-balanced environment; scaling with thousands of clients does not present a problem either (in this scenario, the database may become a bottleneck). Deleted object references must not be explicitly released either — if a client application crashes, objects are not orphaned because they are destroyed immediately after each call. This type of instance management is achieved by PerCall mode in WCF.

Warning

It is also occasionally advantageous to have only one instance of a server object, irrespective of the number of clients. This is achieved in Single mode. However, you should only choose this variant if you really only have to work with a single central resource.

INSTANCECONTEXTMODE

When instantiating the server object, WCF distinguishes between three different variants: PerCall, PerSession, and Single.

Your choice of instantiation is only visible on the server side and is not reflected in the WSDL document. Because the client doesn't know whether it will receive the same instance whenever the method is called, and remembers all previously set values, or if the instance is newly created each time, you should take particular care in the design of your operations.

As already mentioned, the instantiation behavior is an implementation detail which only concerns the server; it is controlled by the InstanceContextMode property of the ServiceBehavior attribute. You will hereby assign this attribute to your implementation, not your service contract.

You can see an example of how this attribute is used in Listing 5-1.

Example 5.1. [ServiceBehavior]

[ServiceBehavior(
      InstanceContextMode = InstanceContextMode.PerCall)
    ]
    public class CarRentalService: ICarRentalService, IDisposable
    {

The individual InstanceContextModes are now explained in detail using various examples and their advantages, disadvantages, and possible uses are described.

PerCall

The proxy on the client side forwards method calls to the server. Every time a method is called, a new instance of the server object is created (default constructor is called) and as soon as the method is processed, this new instance is released again. If the service object implements the IDisposable interface, the Dispose method is called automatically after the result is sent to the proxy.

Note

However, the connection between the proxy and the server is only closed when you call your proxy's Close method.

The object is released again after every call, which makes this variant extremely scalable. However, bear in mind that the content of your instance variables is lost after every call, and that you have to transfer it every time, or buffer the values and reload the required data every time you call the method. In addition to the advantage of scalability, there is also no need to worry about threading problems because each call is assigned a separate instance.

Listing 5-2 illustrates two ways in which a client can supply the required data to the server object.

Example 5.2. PerCall state management

double price = 0;
Guid confirmationID;
CarRentalProxy.RentalServiceClient carRentalClient = new
CarRentalProxy.
RentalServiceClient();

Console.WriteLine("Version 1");
price= carRentalClient.CalculatePriceV1(DateTime.Now,
DateTime.Now.AddDays(5),
"Graz", "Wien");
Console.WriteLine("Price to Wien {0}", price);
confirmationID = carRentalClient.ConfirmV1(DateTime.Now,
DateTime.Now.AddDays(5),
"Graz", "Wien", price);
Console.WriteLine("ConfirmationID {0}",confirmationID );

Console.WriteLine("Version 2");
CarRentalProxy.PriceCalculationResponse resp =
carRentalClient.CalculatePriceV2(
DateTime.Now, DateTime.Now.AddDays(5), "Graz", "Wien");
Console.WriteLine("Price to Wien {0}", resp.Price );
confirmationID = carRentalClient.ConfirmV2(resp.RequestID );
Console.WriteLine("ConfirmationID {0}", confirmationID);

carRentalClient.ReportCrash(DateTime.Now,"Hartberg",confirmationID);

carRentalClient.Close();

As you can see in Listing 5-2, the CalculatePrice method is called first with parameters such as PickupDate and ReturnDate.

In variant 1, only the calculated price is returned. Should you then wish to call up the Confirm method, all the parameters, including the value returned beforehand, must be transferred again.

In variant 2, a requestID is returned along with the calculated price. This request ID can be used, for example, to perform a Confirm action immediately. In variant 2 it is important for the Confirm method to reload the original values using the transferred RequestID; these values are generally reloaded from a database.

The ReportCrash method works in the same way as variant 2. In this case, a unique confirmation ID is also transferred to the method.

Listing 5-3 demonstrates the service implementation for the PerCall scenario.

Example 5.3. Service Implementation

using System;
using Wrox.CarRentalService.Contracts;
using System.ServiceModel;

namespace Wrox.CarRentalService.Implementations.Europe
{
    [ServiceBehavior(InstanceContextMode=  InstanceContextMode.PerCall )]
    public class CarRentalService: ICarRentalService, IDisposable
    {

        public CarRentalService()
        {
            Console.WriteLine("CarRentalService Constructor");
        }

        public double CalculatePriceV1(DateTime pickupDate,
            DateTime returnDate,
            string pickupLocation, string returnLocation)
        {
            double price = 0;
            if (returnLocation.Equals("Wien"))
                price=120;
            else
                price =100;

            return price ;
        }

        public Guid ConfirmV1(DateTime pickupDate, DateTime returnDate,
            string pickupLocation, string returnLocation, double price)
        {
           Guid confirmationNumber = Guid.NewGuid();
           //save confirmation to database
            return confirmationNumber;
        }

        public PriceCalculationResponse CalculatePriceV2(
               DateTime pickupDate, DateTime returnDate,
string pickupLocation, string returnLocation)
        {
            PriceCalculationResponse resp = null;
            resp = new PriceCalculationResponse();
            Guid requestId = Guid.NewGuid();
            resp.RequestID = requestId;

            if (returnLocation.Equals("Wien"))
                resp.Price =120;
            else
                resp.Price = 100;
            //Save request to database
            return resp  ;
        }

        public Guid  ConfirmV2(Guid requestID)
        {
            //load request from Database
            Guid confirmationNumber = Guid.NewGuid();
           //save confirmation to database
            return confirmationNumber;
        }

        public void ReportCrash(DateTime date,
                    String location, Guid confirmationID)
        {
            //load values from database
            Console.WriteLine(@"
             Crash reported Date {0} Location {1}
             Confirmation ID {2}",
             date,location,confirmationID );
        }
        public void Dispose()
        {
            Console.WriteLine("CarRentalService disposed...");
        }
    }
}

Singleton

As the name suggests, there is only one single instance in this variant, irrespective of how many client proxies there are. The object is only instantiated once, and its service life is linked to the ServiceHost.

The primary problem posed by this variant is scalability. The standard threading behavior in WCF is ConcurrencyMode.Single, which means that at any given time the service object can be blocked by a single thread for its exclusive use for the duration of the method call. The object is only released when the entire method has been processed. All other method calls are filed in a queue.

Warning

To ease this problem somewhat, you can change the default ConcurrencyMode manually to ConcurrencyMode.Multiple, although you are then also responsible for synchronizing the threads.

The code in Listing 5-4 illustrates the use of the InstanceContextMode.Single. The default ConcurrencyMode has also been repeated here explicitly for better legibility.

Example 5.4. InstanceContextMode.Single

namespace Wrox.CarRentalService.Implementations.Europe
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Single   )]
    public class CarRentalServiceAT : ICarRentalService, IDisposable
    {
        public int Count { get; set; }
        public CarRentalServiceAT()
        {
            Console.WriteLine("CarRentalService Constructor ");
        }

        public double CalculatePrice(
          DateTime pickupDate, DateTime returnDate,
          string pickupLocation, string returnLocation)
        {

            Count++;
            double returnValue = 0;
            if (returnLocation.Equals("Graz"))
                returnValue = 100;
            else
                returnValue = 150;
            Console.WriteLine("total number of price calculations {0}",
            Count);

            return returnValue;
        }

        public void Dispose()
        {
            Console.WriteLine("CarRentalService disposed ");
        }
    }
}

Listing 5-5 shows that the ServiceHost class has an overloaded variant to which a ready-made Singleton object can be transferred. The Singleton object can also be reached via the SingletonInstance property of the ServiceHost.

Example 5.5. ServiceHost and Singleton

using System.ServiceModel;
...

CarRentalServiceAT myService = new CarRentalServiceAT();
myService.Count = 20;

ServiceHost carRentalHost = null;
carRentalHost = new ServiceHost(myService);

carRentalHost.Open();
CarRentalServiceAT singleton = null;
carRentalHost.SingletonInstance as CarRentalServiceAT;
singleton.Count = 0;

PerSession

PerSession means that each client proxy communicates with its own single instance on the server side. The object on the server side remains in place as long as the client does not call the proxy's Close method, or the session timeout (default 10 minutes) has already been reached.

After the connection has been explicitly ended by calling the Close method or the PerSession server object has been deleted by the timeout, the proxy can no longer be used. When you attempt to call up a method, you get a CommunicationException.

The advantage of PerSession instantiation is that values which have been set are retained on the server side without any additional measures. However, the disadvantage with this is scalability because every active connection naturally also takes up memory space on the server, regardless of whether the client currently requires it. If the client crashes or the Close method is not called, the values are also held in memory until the end of the session. The code in Listing 5-6 demonstrates the use of InstanceContextMode.PerSession.

Example 5.6. InstanceContextMode.PerSession

using System;
using Wrox.CarRentalService.Contracts;
using System.Diagnostics;
using System.ServiceModel;

namespace Wrox.CarRentalService.Implementations.Europe
{
    [ServiceBehavior(InstanceContextMode=  InstanceContextMode.PerSession)]
    public class CarRentalService: ICarRentalService, IDisposable
    {
        private DateTime pickupDate, returnDate;
        private String pickupLocation, returnLocation;
        private  string  confirmationNumber;
        public CarRentalService()
        {
            Console.WriteLine("CarRentalService Constructor");
        }
        public void SetValues(DateTime pickupDate, DateTime returnDate,
           string pickupLocation, string returnLocation)
        {
            this.pickupDate = pickupDate;
            this.returnDate = returnDate;
this.pickupLocation = pickupLocation;
            this.returnLocation = returnLocation;
        }

        public double CalculatePrice()
        {
            double price = 0;
            if (returnLocation.Equals("Wien"))
                price=120;
            else
                price =100;

            return price ;
        }

        public string  Confirm()
        {
            confirmationNumber = OperationContext.Current.SessionId ;

            return confirmationNumber;
        }

        public void Dispose()
        {

            Console.WriteLine("CarRentalService disposed...");
        }
    }
}

As shown in Listing 5-7, when the first call is made, the values are set and the private instance variables can be used in subsequent calls without transferring twice from the client to the server. The confirmation ID corresponds to the SessionId.

Example 5.7. PerSession Client

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Wrox.CarRentalService.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {

            double price = 0;
            CarRentalProxy.RentalServiceClient carRentalClient = null;
            carRentalClient = CarRentalProxy.RentalServiceClient();
carRentalClient.SetValues(
                              DateTime.Now, DateTime.Now.AddDays(5),
                              "Graz", "Wien");
            price = carRentalClient.CalculatePrice();
            Console.WriteLine("Price to Wien {0}", price);

            carRentalClient.SetValues(
                              DateTime.Now, DateTime.Now.AddDays(5),
                              "Graz", "Villach");
            price = carRentalClient.CalculatePrice();
            Console.WriteLine("Price to Villach {0}", price);

            string confirmNumber = carRentalClient.Confirm();
            Console.WriteLine("Reservation ID {0}", confirmNumber);
            carRentalClient.Close();
            try
            {
                //order is already confirmed and the proxy is closed
                carRentalClient.SetValues(DateTime.Now,
                DateTime.Now.AddDays(5), "Graz", "Salzburg");
                price = carRentalClient.CalculatePrice();
                Console.WriteLine("Price to Salzburg {0}", price);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message );
            }
        }
    }
}

PerSession requires you to use a protocol which supports session. For example, you can use the netTcpBinding with the connection-oriented transfer protocol TCP which allows the server to allocate the incoming message to the correct server object. The basicHttpBinding, for example, is unsuitable because the transfer protocol HTTP is connectionless and cannot allocate the incoming message to a dedicated server instance.

However, there are some bindings which use the transfer protocol HTTP and provide additional support in terms of security or reliability. In this case, WCF uses the ID from the security or reliability protocol to establish a unique connection between the proxy and the server object.

Listing 5-8 illustrates the use of wsHttpBinding, which supports both security and reliability. If you only activate security now, for example, WCF uses the security identifier for client-server mapping and for the SessionId, as shown in Figure 5-1.

Example 5.8. wsHttpBinding

<bindings>
 <wsHttpBinding>
  <binding name="wsReliable" >
   <reliableSession enabled="false"/>
   <security mode="Message"/>
</binding>
 </wsHttpBinding>
</bindings>
<service
behaviorConfiguration="MetaDataBehavior" name="Wrox.CarRentalService.
Implementations.Europe.CarRentalService">
<endpoint
   address=""
   binding="wsHttpBinding" bindingConfiguration="wsReliable"
   contract="Wrox.CarRentalService.Contracts.ICarRentalService"
/>
</service>
FIGURE 5-1

Figure 5.1. FIGURE 5-1

Warning

Given the fact that the combination InstanceContextMode.PerSession and binding basicHttpBinding does not lead to a runtime error and does not produce the required behavior — because the server instance behaves as it does in PerCall mode — it is imperative to request or decline a protocol which supports sessions explicitly. This is done with the SessionMode property in the service contract, as shown in Listing 5-9.

Example 5.9. SessionMode.Required to Enforce a Suitable Binding

[ServiceContract(
     Namespace = "http://wrox/CarRentalService/2009/10",
     SessionMode = SessionMode.Required ,
     Name = "RentalService")]
public interface ICarRentalService. . .

Should you attempt to load this service with the binding basicHttpBinding, the following runtime error occurs when the service definition is read by the service host:

Unhandled Exception: System.InvalidOperationException: Contract requires Session,
but Binding 'BasicHttpBinding' doesn't support it or isn't configured
properly to support it.

On the other hand, the combination of SessionMode.NotAllowed and netTcpBinding leads to the following error message:

Unhandled Exception: System.InvalidOperationException: Contract does not
allow Session, but Binding 'NetTcpBinding' does not support
Datagram or is not configured properly to support it.

Regardless of the previous configurations, the Reliability property should be kept activated at all times in a PerSession scenario.

SERVICE LIFE

As you have already seen, you can release the server instance explicitly by calling the Close method or wait for the default timeout. However, in connection with sessions it is also often important to call up the methods in a certain order. For example, the SetValues method must be called first, and only then can the price be calculated. Furthermore, when you call up the Confirm method, the contract has been concluded between the customer and the car rental company, and the server object can then be deleted.

Use the properties IsInitiating and IsTerminating to determine the order in which methods have to be called. IsInitiating means that this method can start a session or is involved in an existing session. IsTerminating means that the session is ended if this method is called up; where necessary, the Dispose method for the server object is called, and the proxy is unusable. Setting these properties also affects the generated WSDL document, as shown in Listing 5-10, which means that it can also be seen and used by the client.

Example 5.10. IsInitiating and IsTerminating and the Produced WSDL Document

<wsdl:operation
   msc:isInitiating="false"
   msc:isTerminating="false"
   name="CalculatePrice"
>

If, for example, you define an operation contract as shown in Listing 5-11, the method cannot be called first. However, should the client attempt to call up this method, the following error message is generated:

Unhandled Exception: System.InvalidOperationException:
The operation 'CalculatePrice' cannot be the first operation
to be called because IsInitiating is false.

Example 5.11. IsInitiating and IsTerminating

[OperationContract(IsInitiating = false , IsTerminating = false)]
double CalculatePrice();

Consequently, the final service contract for the car hire example is shown in Listing 5-12.

Example 5.12. Car Hire Service Contract

[ServiceContract(
   Namespace = "http://wrox/CarRentalService/2009/10",
   SessionMode = SessionMode.Required ,
   Name = "RentalService")]
public interface ICarRentalService
{
  [OperationContract(IsInitiating=true, IsTerminating =false)]
  void SetValues(DateTime pickupDate, DateTime returnDate,
    string pickupLocation, string returnLocation);

  [OperationContract(IsInitiating = false , IsTerminating = false)]
  double CalculatePrice();

  [OperationContract(IsInitiating = false , IsTerminating = true )]
  string  Confirm();
}

A further advanced possibility for influencing the service life of the service instance is to use the OperationBehavior attribute together with the ReleaseInstanceMode property. In principle, every server instance is surrounded by a context which maintains the actual connection with the client proxy. In certain scenarios it may be advantageous to maintain the context (container for the instance), re-create the instance contained in it before a method is called up, or delete it after a method is called. The ReleaseInstanceMode property is actually only used in connection with PerSession scenarios if the user wishes to retain the session but not the instance.

In Listing 5-13, the SetValues method has been assigned the ReleaseInstanceMode.BeforeCall. As a result of this, the existing instance is deleted and recreated again before every time this method is called.

Example 5.13. ReleaseInstanceMode.BeforeCall

[OperationBehavior(ReleaseInstanceMode=ReleaseInstanceMode.BeforeCall)]
public void SetValues(DateTime pickupDate, DateTime returnDate,
           string pickupLocation, string returnLocation)

In addition, a further method has been incorporated into the service contract, as shown in Listing 5-14, with the name Reset and has been assigned the ReleaseInstanceMode.AfterCall. The instance is now deleted every time the client calls the Reset method.

Example 5.14. ReleaseInstanceMode.AfterCall

[OperationBehavior(ReleaseInstanceMode=ReleaseInstanceMode.AfterCall )]
public void Reset()

Other options would be ReleaseInstanceMode.BeforeCall (default) and ReleaseInstanceMode.BeforeAndAfterCall.

In principle, this instance management option should only be applied in special exceptions — if you require explicit control over the lifetime of a server instance for reasons of performance or scalability.

SessionId

The SessionId, displayed in the form of a GUID, is exchanged between the client and the server and uniquely signifies a connection between the client (proxy) and the server (context). It originates either from the transfer protocol session (TCP) or from a logical session id if you are using wsHttpBinding, for example. The SessionId is primarily used for logging and protocol purposes and can be applied irrespective of whether you are in PerCall, PerSession, or Single mode.

Therefore, the SessionID is always accessible whenever you are using a protocol or a binding which supports session. For example, you can use the TCP protocol, which is connection-oriented anyway, or you can use wsHttpBinding, which uses the connectionless HTTP transfer protocol but carries a logical session ID above it.

The uppermost session ID is used if, for example, you have activated wsHttpBinding to be secure and reliable. In this scenario the exchanged security identifier is used as a session ID. The connection between the session ID and the security identifier can also be seen in Figure 5-2.

FIGURE 5-2

Figure 5.2. FIGURE 5-2

In any event it is important to activate either reliable sessions or security with the binding being used.

Therefore, the session ID can always be accessed sensibly whenever reliability and/or security are involved. In addition to these restrictions, it is also important for communication to happen beforehand between the proxy and the server for the exchange of the ID. This can be done by opening the proxy manually, which is recommended, or at least by calling one method. If you access the SessionId prior to communication or use a protocol which does not support any sessions, you will either receive an incorrect SessionId or an error message.

You can access the SessionId on the client side via the proxy's InnerChannel property. This SessionId is provided to you on the server side by the operation context's Current attribute.

Warning

Also bear in mind that if you have activated SessionMode.NotAllowed on the server side, you can access the SessionId, even though it remains blank.

The following conclusion can be drawn from the code in Listing 5-15 and the console output display in Listing 5-16.

Example 5.15. Client Side SessionId

namespace Wrox.CarRentalService.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {

            CarRentalProxy.RentalServiceClient carRentalClient = null;
            carRentalClient = new CarRentalProxy.RentalServiceClient();

            double price =0;

            try
            {
               Console.WriteLine(
                    carRentalClient.InnerChannel.SessionId);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message ); ;
            }

            carRentalClient.Open();
            price = carRentalClient.CalculatePrice(
                DateTime.Now, DateTime.Now.AddDays(5), "Graz", "Wien");
            Console.WriteLine("Price {0}", price);
            Console.WriteLine(carRentalClient.InnerChannel.SessionId);

            carRentalClient.Close();
            try
            {
                Console.WriteLine(
                    carRentalClient.InnerChannel.SessionId);
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.Message); ;
            }

            carRentalClient = new CarRentalProxy.RentalServiceClient();

            price = carRentalClient.CalculatePrice(
              DateTime.Now,DateTime.Now.AddDays(5),"Graz","Villach");
            Console.WriteLine("Price {0}",price );

            Console.WriteLine(carRentalClient.InnerChannel.SessionId);

            price = carRentalClient.CalculatePrice(
              DateTime.Now,DateTime.Now.AddDays(5),"Graz", "Villach");
            Console.WriteLine("Price {0}", price);

            Console.WriteLine(carRentalClient.InnerChannel.SessionId);
Console.WriteLine("try it again ... demo for Single");
            Console.ReadLine();
            price = carRentalClient.CalculatePrice(
              DateTime.Now,DateTime.Now.AddDays(5),"Graz","Villach");
            Console.WriteLine("Price {0}", price);

            Console.WriteLine(carRentalClient.InnerChannel.SessionId);
        }
    }
}

Example 5.16. Client Output

The session channel must be opened before the session ID can be accessed.
Price 120
urn:uuid:9dd6c7de-d89a-4200-9be4-59f9e5dfc0bc
urn:uuid:9dd6c7de-d89a-4200-9be4-59f9e5dfc0bc
Price 120
urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a
Price 120
urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a
try it again ... demo for Single

Price 120
urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a

The SessionId can be accessed via the InnerChannel proxy property. To access the SessionId, the connection must be opened between the proxy and the server; this can either be done explicitly via Open or by calling up at least one method. After the proxy has been closed, the old SessionId can be accessed. When the proxy is opened again, a new SessionId is negotiated.

The following conclusion can be drawn from the server code in Listing 5-17 with a configuration shown in Listing 5-18 and the output shown in Listing 5-19.

Example 5.17. Server Side SessionId

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single )]
    public class CarRentalService: ICarRentalService, IDisposable
    {
        public CarRentalService()
        {
            Console.WriteLine("CarRentalService Constructor" );
        }

       public double CalculatePrice(
            DateTime pickupDate, DateTime returnDate,
            string pickupLocation, string returnLocation)
        {
            OperationContext ctx = OperationContext.Current;
            Console.WriteLine("CalculatePrice 	{0} ", ctx.SessionId);
            if (returnLocation.Equals("Villach"))
return 50;
            else
                return 120;
        }
        public void Dispose()
        {
            OperationContext ctx = OperationContext.Current;
            Console.WriteLine("Disposed 	{0}", ctx.SessionId);

        }
    }
}

Example 5.18. Config File

<system.serviceModel>
    <bindings>
      <wsHttpBinding >
        <binding name="mywsBinding">
          <reliableSession enabled="false"/>
          <security mode="Message"/>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="MetaDataBehavior"
          name="Wrox.CarRentalService.Implementations.
          Europe.CarRentalService">
      <endpoint address=""
            binding="wsHttpBinding"
            bindingConfiguration="mywsBinding"
            contract="Wrox.CarRentalService.Contracts.ICarRentalService"
      />

    </service>
  </services>
 </system.serviceModel>

Example 5.19. Server Output

The car rental service is up and running...
CalculatePrice  urn:uuid:9dd6c7de-d89a-4200-9be4-59f9e5dfc0bc
CalculatePrice  urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a
CalculatePrice  urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a
CalculatePrice  urn:uuid:c3606853-13a0-4fc0-aa4e-f859a2d0891a

The SessionId can be accessed via the OperationContext.Current.SessionId property. The decisive factor for a common SessionId is not the InstanceContextMode, but the binding used. The SessionId can also be accessed in the Dispose method, although this only makes sense in PerCall or PerSession mode.

PERFORMANCE

The performance of your WCF services hinges on a wide range of factors. In addition to general topics such as CPU, RAM, and network performance, WCF-specific considerations such as InstanceContextMode, ConcurrencyMode, DataContract design or the binding used also play an important role.

The InstanceContextMode is used to control the instantiation behavior of your service object — possible variants are PerCall, PerSession, or Singleton.

The binding determines which transfer protocol and which encoding is used. In addition, a wide range of WS* protocols can be used via the binding.

ConcurrencyMode describes whether multiple threads are permitted to access one and the same object at the same time. ConcurrencyMode is controlled via the [ServiceBehavior] attribute, and its default setting is ConcurrencyMode.Single. Further options are ConcurrencyMode.Multiple and ConcurrencyMode.Reentrant. ConcurrencyMode.Single means that a server object can only be accessed by a single thread. Therefore, this setting will not lead to any synchronization problems because other requests are automatically placed in a queue, and the object may only be accessed when the first thread has released the object. ConcurrencyMode.Multiple means that any number of threads may access one and the same object. If necessary, you must take care of thread synchronization manually with classic .NET tools such as Monitor or Mutex.

Note

ConcurrencyMode.Reentrant is ultimately important for callback scenarios and is not described here in more detail.

In principle, however, the ConcurrencyMode you use only plays a role if you use a multithreaded client which accesses a PerSession object or if you use a Singleton. In the case of PerCall scenarios, a new object is created for every method call in any event, avoiding the threading problem from the outset.

The use of the [ServiceBehavior] attribute is shown in Listing 5-20.

Example 5.20. [ServiceBehavior]

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,
   ConcurrencyMode=ConcurrencyMode.Single)]
public class CarRentalService: ICarRentalService, IDisposable
{

Throttling

WCF offers another possibility for increasing performance and for avoiding server overloads, thereby preventing DoS attacks, for example. You can control the following limits by using the <serviceThrottling> element in the ServiceBehavior section of your configuration file, as shown in Listing 5-21 or, of course, by programming the following:

  • maxConcurrentCalls: Maximum number of service operations which can be processed at the same time. If this number is exceeded, other method calls are placed in a queue and processed gradually.

  • maxConcurrentSessions: Maximum number of simultaneous transfer or application sessions.

  • maxConcurrentInstances: Maximum number of instances.

Example 5.21. serviceThrottling Behavior

<serviceBehaviors>
        <behavior name="throttlingBehavior">
          <serviceThrottling
              maxConcurrentCalls ="5"
              maxConcurrentSessions="2"
              maxConcurrentInstances="3" />
        </behavior>
      </serviceBehaviors>

To read out the throttling values you can use the code shown in Listing 5-22.

Example 5.22. Read Throttling Values

namespace Wrox.CarRentalService.ConsoleHost
{
    class Program
    {
        static void Main(string[] args)
        {
            System.ServiceModel.ServiceHost carRentalHost = null;
            carRentalHost = new ServiceHost(
                 typeof(Wrox.CarRentalService.Implementations.
                        Europe.CarRentalService));
            carRentalHost.Open();
            ChannelDispatcher dispatcher =
               carRentalHost.ChannelDispatchers[0]
               as ChannelDispatcher;
            Console.WriteLine("Max concurrent calls {0}",
                dispatcher.ServiceThrottle.MaxConcurrentCalls);
            Console.WriteLine("Max concurrent instances {0}",
                dispatcher.ServiceThrottle.MaxConcurrentInstances);
            Console.WriteLine("Max concurrent sessions {0}",
                dispatcher.ServiceThrottle.MaxConcurrentSessions);
            Console.WriteLine(@"The car rental service is up
                and running...");
            Console.ReadLine();
        }
    }
}

By using this ServiceBehavior, you have control over how many instances or sessions can exist concurrently, or how many calls are permitted at the same time. The defaults used can be adapted depending on your environment to achieve improved overall performance. However, the throttling values which are most suitable depend on a variety of parameters. You should ask the following questions: How much memory capacity does a session take? How many concurrent clients are expected? Are there multithreaded clients? Which InstanceContextMode is used? Which binding is used?

In all tuning scenarios it is important to have a baseline and, after making changes to your configuration, to compare this baseline with the new values to ascertain whether these changes have actually had a positive impact, or whether the overall load on your server has come down at all. WCF offers a variety of performance counters for monitoring the performance or for creating a baseline. Performance counters must be activated by configuration or by programming before they can be read.

An example of activating via configuration file is shown in Listing 5-23.

Example 5.23. Activate Performance Counters

<system.serviceModel>
. . .
  <diagnostics performanceCounters="All"/>
. . .
</system.serviceModel>

After you have activated the performance counters and started your service host, you can monitor the current performance values with the Windows Performance Monitor; WCF offers performance indicators for the service as a whole — for endpoints and for operations.

To show what effect the individual components (serviceThrottling, InstanceContextMode, ConcurrencyMode) have, you start with a simple PerCall service with basicHttpBinding and take a step-by-step look at configuration variants.

The starting point is the existing CalculatePrice operation in the CarRentalService. To make the effects of throttling more readily visible, greatly reduced throttling settings are shown in Listing 5-24.

Example 5.24. Service Throttling

<serviceThrottling
 maxConcurrentCalls ="5"
 maxConcurrentInstances="10"
 maxConcurrentSessions="15"
 />

The service implementation is shown in Listing 5-25.

Example 5.25. Service Implementation

namespace Wrox.CarRentalService.Implementations.Europe
{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,
        ConcurrencyMode=ConcurrencyMode.Reentrant)]
    public class CarRentalService: ICarRentalService, IDisposable
    {
        public double CalculatePrice(
               DateTime pickupDate, DateTime returnDate,
               string pickupLocation, string vehiclePreference)
        {
            System.Threading.Thread.Sleep(5000);
            return double.Parse(pickupLocation);
        }
        public double GetDummyNumber()
        {
            return 10;
        }

        public CarRentalService()
        {
            Console.WriteLine("CarRentalService Constructor");
        }

        public void Dispose()
        {
            Console.WriteLine("CarRentalService disposing...");
            System.Threading.Thread.Sleep(2000);
            Console.WriteLine("CarRentalService disposing...");
        }
    }
}

The client code shown in Listing 5-26 creates a single proxy outside the loop via the basicHttpBinding and then calls up the CalculatePrice method asynchronously 40 times.

Example 5.26. Client Code

namespace Wrox.CarRentalService.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            CarRentalProxy.RentalServiceClient carRentalClient = new
CarRentalProxy.RentalServiceClient("NetTcpBinding_RentalService");

            for (int i = 0; i < 40; i++)
            {
                carRentalClient.BeginCalculatePrice
                   (DateTime.Now, DateTime.Now.AddDays(5),
                   i.ToString(),
"Graz",
                  priceCalcFinished,
                  carRentalClient);
            }

            Console.ReadLine();

        }
        public static void priceCalcFinished(IAsyncResult asyncResult)
        {
            Console.WriteLine("priceCalcFinished");
            try
            {
                RentalServiceClient carRentalClient =
                    asyncResult.AsyncState as RentalServiceClient;
                double price = carRentalClient.EndCalculatePrice(asyncResult);
                Console.WriteLine("Price {0}", price);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); ;
            }
        }
    }
}

After the start of this example, you can see how the price calculations are conducted in blocks of five because a maximum of five calls is permitted at any one time. The remaining calls are buffered and are processed bit by bit. Setting the maxConcurrentInstances attribute to 5 would also lead to the same result. Changing the maxConcurrentSessions attribute makes absolutely no difference in this scenario because neither a connection-oriented transfer protocol nor a ws binding with security or reliability are used. Were the processing of the individual calls to take too long, the client would receive a timeout exception; the default for this is after one minute. The timespan between the method being called and the timeout exception can be changed on the client side by using the sendTimeout attribute in your binding.

If the binding is changed to netTCPBinding, using the maxConcurrentSessions attribute would still not work, even though TCP supports connections at a transfer protocol level. This is because only one proxy has been created on the client side. As a result, the maxConcurrentSessions attribute has no effect.

However, if you move the instantiation of the proxy into the loop, each proxy instance establishes a separate connection with the server side.

Listing 5-27 shows the changed client code.

Example 5.27. Change Client Code

for (int i = 0; i < 40; i++)
{
  CarRentalProxy.RentalServiceClient carRentalClient = new CarRentalProxy.
RentalServiceClient("NetTcpBinding_RentalService");
carRentalClient.BeginCalculatePrice
                 (
                  DateTime.Now, DateTime.Now.AddDays(5),
                  i.ToString(),
                  "Graz",
                  priceCalcFinished,
                  carRentalClient);
}

Based on the configuration in Listing 5-28, there may only ever be five instances active. If the values for the other two attributes maxConcurrentCalls or maxConcurrentInstances are lower, they would be referred to as limits, of course.

Example 5.28. Changed serviceThrottling

<serviceThrottling
maxConcurrentCalls ="100"
maxConcurrentInstances="100"
maxConcurrentSessions="5" />

In this scenario it is also extremely important for the proxy to be closed in the callback method as shown in Listing 5-29; otherwise, the session would be maintained and would only be closed after the 10-minute default.

Example 5.29. Close Proxy in the Callback Method

public static void priceCalcFinished(IAsyncResult asyncResult)
        {
            Console.WriteLine("priceCalcFinished");
            try
            {
                RentalServiceClient carRentalClient =
                   asyncResult.AsyncState as RentalServiceClient;
                double price =
                   carRentalClient.EndCalculatePrice(asyncResult);
                Console.WriteLine("Price {0}", price);
                carRentalClient.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); ;
            }
}

As a final example, here is the use of ConcurrencyMode.Multiple in conjunction with InstanceContextMode.PerSession and a multithreaded client. For this purpose, the server code was adapted in such a way that an instance variable is incremented in the CalculatePrice method as shown in Listing 5-30.

Example 5.30. ConcurrencyMode.Multiple with PerSession

namespace Wrox.CarRentalService.Implementations.Europe
{
  [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession ,
        ConcurrencyMode=ConcurrencyMode.Multiple  )]
    public class CarRentalService: ICarRentalService, IDisposable
    {
        private object myLock = new object();
        private int Counter;
        public double CalculatePrice(
          DateTime pickupDate, DateTime returnDate,
          string pickupLocation, string vehiclePreference)
        {
            lock (myLock)
            {
                int tempValue = Counter;
                tempValue++;
                System.Threading.Thread.Sleep(2000);
                Counter = tempValue;
                Console.WriteLine("Counter {0}", Counter);
            }
            return double.Parse(pickupLocation);
        }

        public CarRentalService()
        {
            Console.WriteLine("CarRentalService Constructor");
        }

        public void Dispose()
        {
            Console.WriteLine("CarRentalService disposing...");
            System.Threading.Thread.Sleep(2000);
            Console.WriteLine("CarRentalService disposing...");
        }
    }
}

If the CalculatePrice method is now called up by a multithread client, as shown in Listing 5-31, a logical error occurs in the calculation.

Example 5.31. Multithreaded Client

namespace Wrox.CarRentalService.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
           CarRentalProxy.RentalServiceClient carRentalClient = new
CarRentalProxy.RentalServiceClient("NetTcpBinding_RentalService");
for (int i = 0; i < 40; i++)
            {
                carRentalClient.BeginCalculatePrice(DateTime.Now,
DateTime.Now.AddDays(5),
                     i.ToString(),
                     "Graz",
                    priceCalcFinished,
                    carRentalClient );
            }

            Console.ReadLine();

        }
        public static void priceCalcFinished(IAsyncResult asyncResult)
        {
            Console.WriteLine("priceCalcFinished");
            try
            {
                RentalServiceClient carRentalClient =
                 asyncResult.AsyncState  as RentalServiceClient;
                double price =
                carRentalClient.EndCalculatePrice(asyncResult);
                Console.WriteLine("Price {0}", price);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message ); ;
            }
        }
    }
}

This problem is solved either by resetting the ConcurrencyMode to Single or by using manual synchronization in the CalculatePrice method.

Best Practices

As you have seen from the previous examples, the overall performance and load limits of your WCF environment are affected by many different factors, such as the InstanceContextMode, ConcurrencyMode, and throttling behavior. However, you should always initially keep to the defaults or the recommended best practices with regard to tuning. For example, it is advisable to use the DataContractSerializer and to favor a PerCall architecture with ConcurrencyMode.Single. You should only start to adapt the default settings if you notice that these defaults or best practices lead to problems. When tuning, it is always important to have reliable numerical material on hand, confirming whether or not your changes have been successful. As mentioned previously, WCF offers a series of performance counters for general monitoring.

Load Balancing

The typical scale-out scenario involves adding more servers to your environment, if necessary, and using them to take on some of the workload of the other servers. However, load balancing is only possible if the client calls can be passed on to any server for processing. That is generally possible without any problems in a PerCall scenario without a session-oriented protocol. The SOAP message is sent to any server, where a new object is created for this call. Where necessary, requisite data is loaded from another data source (e.g., an SQL Server). After the work is done, the object is deleted on the server side again. In the case of InstanceMode.PerSession, InstanceMode.Single, or a connection-oriented protocol, load balancing with standard WCF technologies is not possible. In PerSession mode in particular, WCF is unable to swap session variables to a central state or database server, unlike a classic ASP.NET session state.

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

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