Академический Документы
Профессиональный Документы
Культура Документы
Hands-On Lab
Lab Manual
Reliable, Transacted and
Instrumented Messaging with the
Windows Communication
Foundation
This lab is designed for use with the .NET Framework 3 Beta 2.
Page i
Page ii
Information in this document is subject to change without notice. The example companies, organizations,
products, people, and events depicted herein are fictitious. No association with any real company, organization,
product, person or event is intended or should be inferred. Complying with all applicable copyright laws is the
responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced,
stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical,
photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft
Corporation.
Microsoft may have patents, patent applications, trademarked, copyrights, or other intellectual property rights
covering subject matter in this document. Except as expressly provided in any written license agreement from
Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights,
or other intellectual property.
Microsoft, MS-DOS, MS, Windows, Windows NT, MSDN, Active Directory, BizTalk, SQL Server, SharePoint,
Outlook, PowerPoint, FrontPage, Visual Basic, Visual C++, Visual J++, Visual InterDev, Visual SourceSafe, Visual
C#, Visual J#, and Visual Studio are either registered trademarks or trademarks of Microsoft Corporation in the
U.S.A. and/or other countries.
Other product and company names herein may be the trademarks of their respective owners.
Page iii
Contents
Page iv
Reliable, Transacted and Instrumented Messaging with the
Windows Communication Foundation
Lab Objective
Estimated time to complete this lab: 60 minutes
The objective of this lab is to demonstrate the Windows Communication Foundation’s facilities for
reliable, queued and transacted messaging, and for state management. The lab also shows how to
instrument Windows Communication Foundation services with custom performance counters.
In this exercise, you will build a Windows Communication Foundation service that uses a data contract
to define the format of data in the messages it exchanges. The service is for trading in derivatives.
A derivative is a financial entity whose value is derived from that of another. Here is an example. The
value of a single share of Microsoft Corporation Stock was $24.41 on October 11, 2005. Given that
value, one might offer for sale an option to buy 1,000 of those shares for $25 each on November 11,
2005. Such an option, which is known as a call, might be purchased by someone who anticipates that
the price of the shares will rise above $25 by November 11, 2005, and sold by someone who
anticipates that the price of the shares will drop. The call is a derivative, its value being derived from
the value of Microsoft Corporation stock.
Pricing a derivative is a complex task. Indeed, estimating the value of derivatives is perhaps the most
high-profile problem in modern microeconomics.
In the case of our example, clearly the quantity of the stock, and the current and past prices of the
Microsoft Corporation stock are factors to consider, but other factors might be based on analyses of the
values of quantities that are thought to affect the prices of the stock, such as the values of various stock
Page 1
market indices, or the interest rate of the U.S. Federal Reserve Bank. In fact, one can say that, in
general, the price of derivative is some function of one or more quantities, one or more market values,
and the outcome of one or more quantitative analytical functions.
Thus, the service that you will construct in this exercise is one that allows a client to define a derivative
trade, identify any number of quantitative analytical functions to be used in estimating the value of the
derivative to be traded, and, optionally, make the trade.
1. Log onto the virtual PC with the username Administrator, and the password, pass@word1. A
folder with an electronic copy of this manual is located on the desktop, which will be useful for
copying and pasting code into Visual Studio .NET. There is one particularly long passage of code
that would certainly have to be copied and pasted rather than typed.
2. Open Visual Studio 2005, and create a new blank solution called, TradingService, in c:\Windows
Communication Foundation\Labs, as shown in figure 1.1, below.
Page 2
Figure 1.1 Adding a Class Library project to the solution
3. In the properties for the project, set the default namespace to Fabrikam
Page 3
Figure 1.1 Adding references to the Windows Communication Foundation
assemblies
Task 3 – Define the Contract that the Trading Service will Expose
2. Rename the class file, Class1.cs, in the TradingService project to ITradingService.cs, and modify
the content thereof to read as follows:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceContract]
public interface ITradingService
{
[OperationContract]
string BeginDeal();
[OperationContract]
void AddTrade(Trade trade);
[OperationContract]
void AddFunction(string function);
[OperationContract]
decimal Calculate();
[OperationContract]
void Purchase();
Page 4
[OperationContract]
void EndDeal();
}
}
3. Thus, you have defined an interface, ITradingService, with a method, AddTrade(), that accepts an
instance of a class named Trade as a parameter. Furthermore, the AddTrade() method has the
[OperationContract] attribute, which implies that parameters may be passed to it from a remote
client. Therefore, not only must a Trade class be defined, but a data contract must also be
defined for that class to specify the format in which instances of the class will be transmitted to
the AddTrade() method from remote clients. So, add a class named, Trade.cs, to the project, and
modify its contents to look like this:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace Fabrikam
{
[DataContract(Namespace="Fabrikam",Name="Trade")]
public class Trade
{
[DataMember]
public string Symbol;
[DataMember]
public long? Count;
[DataMember]
public DateTime? Date;
}
}
}
4. Add a class named, TradingSystem.cs, to the project, and modify its contents in this way:
using System;
using System.Collections.Generic;
using System.Text;
namespace Fabrikam
Page 5
{
public class TradingSystem: ITradingService
{
#region ITradingService Members
string ITradingService.BeginDeal()
{
string dealIdentifier = Guid.NewGuid().ToString();
return dealIdentifier;
}
decimal ITradingService.Calculate()
{
Decimal value = DateTime.Now.Millisecond/10;
Console.WriteLine("Calculated value as {0}",value);
return value;
}
void ITradingService.Purchase()
{
Console.WriteLine("Purchased!");
}
void ITradingService.EndDeal()
{
Console.WriteLine("Completed deal.");
}
#endregion
}
}
5. Compile the TradingService project to confirm that there are no syntax errors.
Page 6
Figure 1.4 Adding a reference to the TradingService project
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
public class Program
{
public static void Main(string[] args)
{
Uri[] baseAddresses =
new Uri[]{new Uri("net.tcp://localhost:8001/")};
using(ServiceHost host =
new ServiceHost(typeof(TradingSystem),baseAddresses))
{
host.Open();
host.Close();
}
Page 7
}
}
}
10.Compile the TradingServiceHost project to ensure that there are no syntax errors.
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Client
{
Page 8
[ServiceContract]
public interface ITradingService
{
[OperationContract]
string BeginDeal();
[OperationContract]
void AddTrade(Trade trade);
[OperationContract]
void AddFunction(string function);
[OperationContract]
Decimal Calculate();
[OperationContract]
void Purchase();
[OperationContract]
void EndDeal();
}
}
19.Add another class module to the client project, name it Trade.cs, and alter its contents in this way:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace Client
{
[DataContract(Namespace="Fabrikam",Name="Trade")]
public class Trade
{
[DataMember]
public string Symbol;
[DataMember]
public long? Count;
[DataMember]
public DateTime? Date;
}
}
By so doing, you have defined a data contract in the client application that is compatible with the
data contract defined in the derivatives trading service, although the class used to define the data
contract in the client application is different from the class used to define the data contract in the
service.
20.Add an application configuration file named, app.config, to the Client project and change its
contents to look like this:
Page 9
contract="Client.ITradingService"/>
</client>
</system.serviceModel>
</configuration>
21. Modify the code in the Program.cs module of the Client project thusly:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
namespace Client
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Press any key when the service is ready.");
Console.ReadKey();
dealProxy.BeginDeal();
dealProxy.AddFunction("InterestRateEstimation");
dealProxy.AddFunction("TechnologyStockEstimation");
dealProxy.Purchase();
dealProxy.EndDeal();
((IChannel)dealProxy).Close();
Console.WriteLine();
Console.WriteLine("Done.");
Console.ReadKey();
}
}
}
22.Build the Client project to confirm the absence of any syntax errors.
Page 10
Task 9 – Use the Derivatives Trading Service
23.Modify the Startup Project properties of the TradingService solution as shown in figure 1.5, below.
24.Choose Debug and then Start Debugging from the Visual Studio menus.
25.When you see a message in the console application window of the TradingServiceHost
executable confirming that the derivatives trading service is available, enter a keystroke into the
console application window of the Client executable. The output in the console application
window of the TradingServiceHost should be similar to the output shown in figure 1.6, below,
while the output in the console application window of the Client should be similar to the output in
figure 1.7. The numbers shown in your console application windows may vary slightly from the
numbers shown in the figures, due to variations in the prevailing market conditions over time.
Page 11
Figure 1.6 Output from the Trading Service
26.Entera keystroke into the console application window of the Client executable to terminate it, and
enter a keystroke into the console application window of the TradingServiceHost executable to
terminate the trading service.
Page 12
Exercise 2 – Manage state within a service
In this exercise, you will use the Windows Communication Foundation’s facilities for managing state
within a service.
27. Examine
the code of the TradingSystem class in the TradingSystem.cs module of the
TradingService project of the TradingService solution constructed in the previous exercise:
using System;
using System.Collections.Generic;
using System.Text;
namespace Fabrikam
{
public class TradingSystem: ITradingService
{
#region ITradingService Members
string ITradingService.BeginDeal()
{
string dealIdentifier = Guid.NewGuid().ToString();
return dealIdentifier;
}
decimal ITradingService.Calculate()
{
Decimal value = DateTime.Now.Millisecond/10;
Console.WriteLine(string.Format("Calculated value as {0}",value));
return value;
}
void ITradingService.Purchase()
{
Console.WriteLine("Purchased!");
}
void ITradingService.EndDeal()
{
Console.WriteLine("Completed deal.");
Page 13
}
#endregion
}
}
By default, a new instance of the TradingSystem class will be created every time any of the class’
methods are invoked via the Windows Communication Foundation. Therefore, by default, if the
AddTrade() method is invoked one or more times, and then the Purchase() method, none of the
data received from the invocations of the AddTrade() method will be available in the execution of
the Purchase() method.
Now, suppose that problem is overcome, and when the Purchase() method is invoked, all of the
data received from preceding invocations of the AddTrade() method are available. An additional
problem is that the client on behalf of which the Purchase() method is being invoked may not be the
same client on behalf of whom all of the calls to the AddTrade() method had been made, so one
client’s activities might inadvertently result in the execution of trades defined by another client.
Furthermore, nothing in the code of the TradingSystem class ensures that the methods of the class
are invoked in a valid sequence. Thus, not only is it possible for the Purchase() method to be
invoked before the AddTrade() method has ever been invoked, but it is also possible for the
EndDeal() method to be invoked before the BeginDeal() method has ever been invoked.
You will solve all of these problems quickly in the next few tasks. They will be tackled in reverse
order.
Task 2 – Control the Sequence in which the Operations of the Trading Service are Invoked
28.Alter the code in the ITradingService.cs module of the TradingService project as shown here:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceContract(Session=true)]
public interface ITradingService
{
[OperationContract(IsInitiating=true,IsTerminating=false)]
string BeginDeal();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true
)]
void AddTrade(Trade trade);
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true
)]
void AddFunction(string function);
[OperationContract(IsInitiating=false,IsTerminating=false)]
decimal Calculate();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=fals
e)]
Page 14
void Purchase();
[OperationContract(IsInitiating=false,IsTerminating=true,IsOneWay=true)
]
void EndDeal();
}
}
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
30.Alter the code in the TradingSystem.cs module of the TradingService project in this way:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class TradingSystem: ITradingService
Task 4 – Keep Track of Data between the Invocations of Operations on behalf of a Single Client
31. Add
this DealData class to the Fabrikam namespace in the TradingSystem.cs module of the
TradingService project:
Page 15
get
{
return this.trades.ToArray();
}
}
#endregion
}
The DealData class defines the structure of the state information that will be stored between the
invocations of the operations of the Derivatives Trading Service.
32.Alterthe BeginDeal() method of the TradingService class in this way, to keep track of the
identifier for a deal, by storing it in a DealData object within the extensions to the Windows
Communication Foundation’s InstanceContext:
string ITradingService.BeginDeal()
{
string dealIdentifier = Guid.NewGuid().ToString();
OperationContext.Current.InstanceContext.Extensions.Add(new
DealData(dealIdentifier));
33.Modify the AddTrade() method of the TradingService class to keep track of trades added to deals:
34.Changethe Purchase() method of the TradingService class so that it operates on the trades that
have been added to a deal:
Page 16
void ITradingService.Purchase()
{
DealData dealData =
OperationContext.Current.InstanceContext.Extensions.Find<DealData>();
foreach(Trade trade in dealData.Trades)
{
Console.WriteLine("Purchased {0}",trade);
}
}
35.Alter the EndDeal() method to use the deal identifier that was stored by the BeginDeal() method.
void ITradingService.EndDeal()
{
DealData dealData =
OperationContext.Current.InstanceContext.Extensions.Find<DealData>();
Console.WriteLine("Completed deal: {0}",dealData.DealIdentifier);
}
Task 5 – Make the Client Compatible with the Modified Derivatives Trading Service
36.Alter
the interface, ITradingService in the ITradingService.cs module of the Client project in the
TradingService solution, in this way:
[ServiceContract(Session=true)]
public interface ITradingService
{
[OperationContract(IsInitiating=true,IsTerminating=false)]
string BeginDeal();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true)]
void AddTrade(Trade trade);
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true)]
void AddFunction(string function);
[OperationContract(IsInitiating=false,IsTerminating=false)]
decimal Calculate();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=false)
]
void Purchase();
[OperationContract(IsInitiating=false,IsTerminating=true,IsOneWay=true)]
void EndDeal();
}
Page 17
The numbers shown in your console application windows may vary slightly from the numbers
shown in the figures, due to variations in the prevailing market conditions over time.
39.Enter a keystroke into the console application window of the Client executable to terminate it, and
enter a keystroke into the console application window of the TradingServiceHost executable to
terminate the trading service.
In this exercise, you will witness the Windows Communication Foundation’s reliable session
capabilities.
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceContract(Session=true)]
public interface ITradingService
{
[OperationContract(IsInitiating=true,IsTerminating=false)]
Page 18
string BeginDeal();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true
)]
void AddTrade(Trade trade);
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=true
)]
void AddFunction(string function);
[OperationContract(IsInitiating=false,IsTerminating=false)]
decimal Calculate();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=fals
e)]
void Purchase();
[OperationContract(IsInitiating=false,IsTerminating=true,IsOneWay=true)
]
void EndDeal();
}
}
The IsInitiating and IsTerminating parameters that we have provided for the [OperationContract]
attribute ensure that any sequence of invocations of the operations defined by the interface begin
with an invocation of the BeginDeal() method, and end with the invocation of the EndDeal() method.
However, if a client invokes the BeginDeal() method, and then the AddTrade() method twice,
followed by the Purchase() method and then the EndDeal() method, but one of the invocations of
the AddTrade() method never reaches the service, the operations of the service will still be invoked
in a valid sequence, and the missing AddTrade() invocation will go unnoticed. Similarly, if the
invocations of the AddTrade() method both arrive, but one is delayed and arrives after the
invocation of the Purchase() method, then, once again, the operations of the service will still be
invoked in a valid sequence, but not the sequence that the client intended. Fortunately, with the
Windows Communication Foundation, guarding against these problems is very easy. In the next
task, you will configure reliable sessions to ensure that no messages are lost.
[ServiceContract(Session=true)]
[DeliveryRequirements(RequireOrderedDelivery=true)]
public interface ITradingService
42.Adjust the definition of the ITradingService interface in the Client solution in the same way:
[ServiceContract(Session=true)]
[DeliveryRequirements(RequireOrderedDelivery=true)]
public interface ITradingService
Page 19
<services>
<service name="Fabrikam.TradingSystem">
<endpoint
address=" TradingService"
binding="netTcpBinding"
bindingConfiguration="ReliableBinding"
contract="Fabrikam.ITradingService"/>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="ReliableBinding" >
<reliableSession enabled="true" ordered="true"/>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
Page 20
47. Enter
a keystroke into the console application window of the Client executable to terminate it, and
enter a keystroke into the console application window of the TradingServiceHost executable to
terminate the trading service.
By completing the previous task, you will have seen that the Derivatives Trading Service and its
client can communicate with one another without error via a reliable messaging protocol. However,
because you were not able to see transmissions between the client and the service going astray,
you were also not able to see for yourself how the reliable messaging protocol would compensate
for such mishaps. Now you will add some logic to intercept transmissions from the client and
randomly and visibly discard some of them.
48.Adda new class module called InterceptorBinding.cs to the Client project, and replace the
default contents of that class with this code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.Text;
using System.Threading;
using System.Xml;
namespace Client
{
public MessageInterceptor()
{
this.randomizer = new Random(DateTime.Now.Millisecond);
Page 21
Console.WriteLine("Dropping message.");
message = null;
return;
}
Console.WriteLine("Forwarding message.");
}
}
Page 22
public override IChannelListener<TChannel>
BuildChannelListener<TChannel>(BindingContext context)
{
InterceptorChannelListener<TChannel> result = new
InterceptorChannelListener<TChannel>(this.interceptor, context.Binding);
result.InnerChannelListener =
context.BuildInnerChannelListener<TChannel>();
return result;
}
Page 23
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback
callback, object state)
{
return this.innerChannel.BeginOpen(timeout, callback, state);
}
Page 24
this.interceptor = interceptor;
}
}
Page 25
protected override TChannel OnCreateChannel(EndpointAddress remoteAddress,
Uri via)
{
TChannel innerChannel =
this.InnerChannelFactory.CreateChannel(remoteAddress, via);
if (typeof(TChannel) == typeof(IOutputChannel))
{
return (TChannel)(object)new InterceptorOutputChannel(this,
this.interceptor, (IOutputChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IRequestChannel))
{
return (TChannel)(object)new InterceptorRequestChannel(this,
this.interceptor, (IRequestChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IDuplexChannel))
{
return (TChannel)(object)new InterceptorDuplexChannel(this,
(IDuplexChannel)innerChannel, this.interceptor);
}
else if (typeof(TChannel) == typeof(IOutputSessionChannel))
{
return (TChannel)(object)new InterceptorOutputSessionChannel(this,
this.interceptor, (IOutputSessionChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IRequestSessionChannel))
{
return (TChannel)(object)new InterceptorRequestSessionChannel(this,
this.interceptor,
(IRequestSessionChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IDuplexSessionChannel))
{
return (TChannel)(object)new InterceptorDuplexSessionChannel(this,
(IDuplexSessionChannel)innerChannel, this.interceptor);
}
throw new ArgumentException("Channel type is not supported.",
"TChannel");
}
Page 26
{
get { return this.InnerChannel.Via; }
}
Page 27
{
public InterceptorRequestChannel(ChannelFactoryBase factory,
IMessageInterceptor interceptor, IRequestChannel innerChannel)
: base(factory, innerChannel, interceptor)
{
}
if (reply == null)
{
Page 28
OnDropMessage();
return
Message.CreateMessage(MessageVersion.Soap12WSAddressing10, new
DroppedMessageFault("Reply Message dropped by the interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
}
else
{
return reply;
}
}
}
if (reply == null)
{
OnDropMessage();
return Message.CreateMessage(message.Version, new
DroppedMessageFault("Reply Message dropped by the interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
}
else
{
return reply;
}
}
}
}
Page 29
public IOutputSession Session
{
get { return innerSessionChannel.Session; }
}
Page 30
}
return base.CanConvertTo(context, destinationType);
}
Page 31
public Uri Via
{
get { return this.InnerChannel.Via; }
}
Page 32
Interceptor.ProcessReceive(ref message);
if (message == null)
{
OnDropMessage();
}
}
}
else
{
return false;
}
} while (message == null);
return true;
}
Page 33
return message;
}
else
{
throw new TimeoutException("Receive timed out.");
}
}
Page 34
IMessageInterceptor interceptor;
Page 35
}
void ThrowIfInnerListenerNotSet()
{
if (this.InnerChannelListener == null)
{
throw new InvalidOperationException("Inner listener is not set.");
}
}
IChannelListener GetInnerListenerSnapshot()
{
IChannelListener innerChannelListener = this.InnerChannelListener;
if (innerChannelListener == null)
{
throw new InvalidOperationException("Inner listener is not set.");
}
return innerChannelListener;
}
Page 36
return OnAcceptChannel(innerChannel);
}
if (typeof(TChannel) == typeof(IInputChannel))
{
return (TChannel)(object)new InterceptorInputChannel(this,
(IInputChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IReplyChannel))
{
return (TChannel)(object)new InterceptorReplyChannel(this,
(IReplyChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IDuplexChannel))
{
return (TChannel)(object)new InterceptorDuplexChannel(this,
(IDuplexChannel)innerChannel, this.interceptor);
}
else if (typeof(TChannel) == typeof(IInputSessionChannel))
{
return (TChannel)(object)new InterceptorInputSessionChannel(this,
(IInputSessionChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IReplySessionChannel))
{
return (TChannel)(object)new InterceptorReplySessionChannel(this,
(IReplySessionChannel)innerChannel);
}
else if (typeof(TChannel) == typeof(IDuplexSessionChannel))
{
return (TChannel)(object)new InterceptorDuplexSessionChannel(this,
(IDuplexSessionChannel)innerChannel, this.interceptor);
Page 37
}
public InterceptorInputChannel(InterceptorChannelListener<TChannel>
listener, IInputChannel innerChannel)
: base(listener, innerChannel, listener.interceptor)
{
this.listener = listener;
}
Page 38
if (message == null)
{
OnDropMessage();
}
}
}
else
{
return false;
}
} while (message == null);
return true;
}
Page 39
return message;
}
else
{
throw new TimeoutException("Receive timed out.");
}
}
}
public InterceptorReplyChannel(InterceptorChannelListener<TChannel>
listener, IReplyChannel innerChannel)
: base(listener, innerChannel, listener.interceptor)
{
this.listener = listener;
}
requestContext = null;
do
Page 40
{
if (this.InnerChannel.TryReceiveRequest(timeout, out
innerRequestContext))
{
if (innerRequestContext == null)
{
return true;
}
else
{
resultMessage = innerRequestContext.RequestMessage;
Interceptor.ProcessReceive(ref resultMessage);
if (resultMessage == null)
{
OnDropMessage();
}
else
{
requestContext = new
InterceptorRequestContext(resultMessage, this, innerRequestContext, timeout);
}
}
}
else
{
return false;
}
} while (resultMessage == null);
return true;
}
Page 41
}
Page 42
get { return this.message; }
}
Page 43
this.innerContext.Reply(message, timeout);
}
}
}
}
public
InterceptorInputSessionChannel(InterceptorChannelListener<TChannel> listener,
IInputSessionChannel innerChannel)
: base(listener, innerChannel)
{
this.innerSessionChannel = innerChannel;
}
public
InterceptorReplySessionChannel(InterceptorChannelListener<TChannel> listener,
IReplySessionChannel innerChannel)
: base(listener, innerChannel)
{
this.innerSessionChannel = innerChannel;
}
Page 44
class InterceptorSection : BindingElementExtensionSection
{
const string ClassType = "type";
[ConfigurationProperty(ClassType)]
[TypeConverter(typeof(InterceptorConverter))]
public IMessageInterceptor Interceptor
{
get { return (IMessageInterceptor)base[ClassType]; }
set { base[ClassType] = value; }
}
channel.Interceptor.ProcessSend(ref this.message);
if (this.message == null)
{
channel.OnDropMessage();
this.message = Message.CreateMessage(message.Version, new
DroppedMessageFault("Reply Message dropped by interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
Complete(true);
}
else
{
Page 45
innerContext.BeginReply(this.message, new
AsyncCallback(HandleCallback), null);
}
}
Page 46
public RequestAsyncResult(InterceptorChannelBase<IRequestChannel> channel,
Message message, AsyncCallback callback, object state)
: base(callback, state)
{
this.message = message;
this.channel = channel;
channel.Interceptor.ProcessSend(ref this.message);
if (this.message == null)
{
channel.OnDropMessage();
this.message =
Message.CreateMessage(MessageVersion.Soap12WSAddressing10, new
DroppedMessageFault("Request Message dropped by interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
Complete(true);
}
else
{
channel.InnerChannel.BeginRequest(this.message, new
AsyncCallback(HandleCallback), null);
}
}
channel.Interceptor.ProcessSend(ref this.message);
if (this.message == null)
{
channel.OnDropMessage();
this.message = Message.CreateMessage(message.Version, new
DroppedMessageFault("Request Message dropped by interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
Complete(true);
}
else
{
channel.InnerChannel.BeginRequest(this.message, timeout, new
AsyncCallback(HandleCallback), null);
}
}
Page 47
channel.OnDropMessage();
this.message =
Message.CreateMessage(MessageVersion.Soap12WSAddressing10, new
DroppedMessageFault("Reply Message dropped by interceptor."),
"http://www.w3.org/2005/08/addressing/fault");
}
else
{
this.message = reply;
}
Complete(false);
}
catch (Exception e)
{
Complete(false, e);
}
}
return requestResult.message;
}
}
Page 48
public SendAsyncResult(InterceptorChannelBase<TChannel> channel, Message
message, AsyncCallback callback, object state)
: base(callback, state)
{
this.message = message;
this.channel = channel;
channel.Interceptor.ProcessSend(ref this.message);
if (this.message == null)
{
channel.OnDropMessage();
Complete(true);
}
else
{
channel.InnerChannel.BeginSend(this.message, new
AsyncCallback(HandleCallback), null);
}
}
channel.Interceptor.ProcessSend(ref this.message);
if (this.message == null)
{
channel.OnDropMessage();
Complete(true);
}
else
{
channel.InnerChannel.BeginSend(this.message, timeout, new
AsyncCallback(HandleCallback), null);
}
}
Page 49
{
get { return base.AsyncWaitHandle; }
}
Page 50
this.message = null;
Complete(false);
}
else
{
channel.Interceptor.ProcessReceive(ref message);
if (message == null)
{
channel.OnDropMessage();
asyncResult =
channel.InnerChannel.BeginTryReceive(timeout, new AsyncCallback(HandleCallback),
null);
}
else
{
this.message = message;
Complete(false);
}
}
}
else
{
Complete(false, new TimeoutException("Receive request timed
out."));
}
}
catch (Exception e)
{
Complete(false, e);
}
}
return inputResult.message;
}
}
Page 51
IRequestContext context;
TimeSpan timeout;
InterceptorChannelBase<IReplyChannel> channel;
public TryReceiveRequestAsyncResult(InterceptorChannelBase<IReplyChannel>
channel, TimeSpan timeout, AsyncCallback callback, object state)
: base(callback, state)
{
this.timeout = timeout;
this.channel = channel;
channel.InnerChannel.BeginTryReceiveRequest(timeout, new
AsyncCallback(HandleCallback), null);
}
Complete(false);
}
else
{
Message result = context.RequestMessage;
channel.Interceptor.ProcessReceive(ref result);
if (result == null)
{
channel.OnDropMessage();
asyncResult =
channel.InnerChannel.BeginTryReceiveRequest(timeout, new
AsyncCallback(HandleCallback), null);
}
else
{
this.message = result;
this.context = context;
Complete(false);
}
}
}
else
{
Complete(false, new TimeoutException("Receive request timed
out."));
}
}
Page 52
catch (Exception e)
{
Complete(false, e);
}
}
context = replyResult.context;
return replyResult.message;
}
}
Page 53
throw new NotImplementedException("The method or operation is not
implemented.");
}
}
lock (ThisLock)
{
if (manualResetEvent == null)
{
manualResetEvent = new ManualResetEvent(isCompleted);
}
}
return manualResetEvent;
}
}
Page 54
object ThisLock
{
get { return this; }
}
if (completedSynchronously)
{
// If we completedSynchronously, then there's no chance that the
manualResetEvent was created so
// we don't need to worry about a race
this.isCompleted = true;
if (this.manualResetEvent != null)
{
throw new InvalidOperationException("No ManualResetEvent should
be created for a synchronous AsyncResult.");
}
}
else
{
lock (ThisLock)
{
this.isCompleted = true;
manualResetEvent = this.manualResetEvent;
}
}
try
{
if (callback != null)
{
callback(this);
}
if (manualResetEvent != null)
{
manualResetEvent.Set();
}
}
catch (Exception unhandledException)
{
// The callback raising an exception is equivalent to Main raising
an exception w/out a catch.
// Queue it onto another thread and throw it there to ensure that
there's no other handler in
// place and the default unhandled exception behavior occurs.
Page 55
// Because the stack trace gets lost on a rethrow, we're wrapping
it in a generic exception
// so the stack trace is preserved.
unhandledException = new Exception("AsyncCallbackException",
unhandledException);
ThreadPool.UnsafeQueueUserWorkItem(new
WaitCallback(RaiseUnhandledException), unhandledException);
}
}
if (asyncResult.endCalled)
{
throw new InvalidOperationException("Async object already ended.");
}
asyncResult.endCalled = true;
if (!asyncResult.isCompleted)
{
using (WaitHandle waitHandle = asyncResult.AsyncWaitHandle)
{
waitHandle.WaitOne();
}
}
if (asyncResult.exception != null)
{
throw asyncResult.exception;
}
}
void RaiseUnhandledException(object o)
{
Exception exception = (Exception)o;
throw exception;
}
}
}
Page 56
49.Examine the ProcessSend() method of the MessageInterceptor class that you have added to the
InterceptorBindingElement module.
This method receives messages and randomly annihilates about thirty percent of them. You will
now have the Windows Communication Foundation send all messages outbound from the client
to the Derivatives Trading Service through this method directly after they have been processed by
the reliable messaging system within the client. By doing so, you will simulate the effect of
messages getting lost in transmission between the client and the server, and you will see the
Windows Communication Foundation’s reliable session facilities compensate for the loss.
50.Add a reference to the System.Configuration assembly to the Client project of the Trading Service
solution.
51. Add a new class module called ReliableBinding.cs to the Client project, and alter the code therein
in this way to define a custom binding that incorporates your message interceptor:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
namespace Client
{
public class ReliableBinding: CustomBinding, IBindingDeliveryCapabilities
{
public ReliableBinding()
{
this.Elements.Add(new ReliableSessionBindingElement(true));
this.Elements.Add(new InterceptorBindingElement());
this.Elements.Add(new TcpTransportBindingElement());
}
bool IBindingDeliveryCapabilities.AssuresOrderedDelivery
{
Page 57
get
{
return true;
}
}
bool IBindingDeliveryCapabilities.QueuedDelivery
{
get
{
return false;
}
}
#endregion
}
}
52.Save all of the changes, and then copy the ReliableBinding.cs class in the Client project, and
paste into the TradingServiceHost project, so that there is a copy of that class module in both of
those two projects.
53.Modify the copy in the TradingServiceHost project to use the Fabrikam namespace and to omit
the message interceptor by commenting out the line of code which adds it to the binding
elements. It will suffice for messages to be lost on the way from the client:
namespace Fabrikam
{
public ReliableBinding()
{
this.Elements.Add(new ReliableSessionBindingElement(true));
//this.Elements.Add(new InterceptorBindingElement());
this.Elements.Add(new TcpTransportBindingElement());
}
54.Modifythe code in the Program.cs module of the Client project to use the new custom binding
when transmitting to the Derivatives Trading Service:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
namespace Client
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press any key when the service is ready.");
Console.ReadKey();
do
{
Page 58
ITradingService dealProxy = new ChannelFactory<ITradingService>(new
ReliableBinding(),new
EndpointAddress("net.tcp://localhost:8001/TradingServiceExperiment")).CreateCha
nnel();
dealProxy.BeginDeal();
dealProxy.AddFunction("InterestRateEstimation");
dealProxy.AddFunction("TechnologyStockEstimation");
dealProxy.Purchase();
dealProxy.EndDeal();
((IChannel)dealProxy).Close();
Console.WriteLine();
Console.WriteLine("Done.");
Console.ReadKey();
}
}
}
55.Change the code in the Program.cs module of the TradingServiceHost project to add an endpoint
to the Derivatives Trading Service that uses the new custom binding as well:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
public class Program
{
public static void Main(string[] args)
{
Uri[] baseAddresses =
new Uri[]{new Uri("net.tcp://localhost:8001/")};
using(ServiceHost host =
new ServiceHost(typeof(TradingSystem),baseAddresses))
Page 59
{
host.AddServiceEndpoint(typeof(ITradingService),new
ReliableBinding(),"TradingServiceExperiment");
host.Open();
host.Close();
}
}
}
}
Task 5 – See the Windows Communication Foundation’s Reliable Message Facility Compensate
for Lost Messages
56.Choose Debug and then Start Debugging from the Visual Studio menus.
57. Whenyou see a message in the console application window of the TradingServiceHost
executable confirming that the derivatives trading service is available, enter a keystroke into the
console application window of the Client executable. The output in the console application
window of the TradingServiceHost should be similar to the output shown in figure 2.1, above,
almost unchanged from how it has been hitherto. However, the output in the console application
window of the Client executable should be similar to the output in figure 3.1, below, where one
can see that a message has been destroyed at random and automatically retried.
Page 60
58.Choose Debug and Stop Debugging from the Visual Studio menus.
59.Youwill not need the service endpoint that you configured to use the custom binding again, so
remove it from the code in the Program.cs module of the TradingServiceHost project. You will be
adding an endpoint in the next task. Note that your application will not run again until you have
completed Exercise 4.
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
public class Program
{
public static void Main(string[] args)
{
Uri[] baseAddresses =
new Uri[]{new Uri("net.tcp://localhost:8001/")};
using(ServiceHost host =
new ServiceHost(typeof(TradingSystem),baseAddresses))
{
//host.AddServiceEndpoint(typeof(ITradingService),new
ReliableBinding(),"net.tcp://localhost:8001/TradingService");
host.Open();
host.Close();
}
}
}
}
In this exercise, you will build a Windows Communication Foundation service that receives messages
via an MSMQ message queue. The service will be used for recording trades of derivatives.
Page 61
Figure 4.1 The Windows Components Wizard
63.IfMessage Queuing is not checked in the Application Server window, as shown in figure 4.2,
below, then check it, and click on the button labeled, OK, and then on the button labeled, Next, in
the Windows Components Wizard, and follow the instructions on the subsequent screens to
install MSMQ. Otherwise, click on the button labeled, Cancel, in the Application Server window
and the Windows Components Wizard. Close the Add or Remove Programs window.
Page 62
Figure 4.2 The Application Server Window of the Windows Components
Wizard
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceContract]
[DeliveryRequirements(QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Required)]
public interface ITradeRecorder
{
[OperationContract(IsOneWay=true)]
void RecordTrades(Trade[] trades);
}
}
Page 63
67. Copy the class module, Trade.cs, from the TradingService project and paste into to the
TradeRecordingService project so that both projects have a copy of the module.
68.Add a class module called TradeRecorder.cs to the TradeRecordingService project, and modify
its code to look like this:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class TradeRecorder: ITradeRecorder
{
public TradeRecorder()
{
}
#endregion
}
}
69.Compile the TradeRecordingService project to ensure that there are no syntax errors.
using System;
using System.Collections.Generic;
using System.Messaging;
using System.ServiceModel;
Page 64
using System.Text;
namespace Fabrikam
{
public class Program
{
private const string queueName = @".\private$\traderecording";
host.Close();
}
}
}
}
Page 65
</netMsmqBinding>
</bindings>
</system.serviceModel>
</configuration>
78.Copy the module, ITradeRecorder.cs, from the TradingRecordingService project and paste into to
the TradingService project so that both projects have a copy of the module.
79.Modify code of the Purchase() method of the TradingSystem class in the TradingService project
so that the Trading Service uses the Trade Recording Service to record derivatives trades:
void ITradingService.Purchase()
{
DealData dealData =
OperationContext.Current.InstanceContext.Extensions.Find<DealData>();
ITradeRecorder proxy = new
ChannelFactory<ITradeRecorder>("TradeRecordingService").CreateChannel();
proxy.RecordTrades(dealData.Trades);
foreach(Trade trade in dealData.Trades)
{
Console.WriteLine(string.Format("Purchased {0}",trade));
}
}
80.Modify
the app.config file of the TradingServiceHost project to specify how the Trading Service is
to communicate with the Trade Recording Service:
address="net.msmq://localhost/private/TradeRecording"
Page 66
binding="netMsmqBinding"
bindingConfiguration="QueuedBinding"
contract="Fabrikam.ITradeRecorder"/>
</client>
<bindings>
<netMsmqBinding>
<binding name="QueuedBinding" >
<security mode="None"/>
</binding>
</netMsmqBinding>
<netTcpBinding>
<binding name="ReliableBinding" >
<reliableSession enabled="true" ordered ="true"/>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
81. Modifythe code in the Program.cs module of the Client project of the TradingService solution so
as to circumvent the mechanism for discarding messages that you used in the last exercise:
do
{
ITradingService dealProxy = new
ChannelFactory<ITradingService>("TradingServiceConfiguration").CreateChannel();
dealProxy.BeginDeal();
dealProxy.AddFunction("InterestRateEstimation");
dealProxy.AddFunction("TechnologyStockEstimation");
dealProxy.Purchase();
dealProxy.EndDeal();
((IChannel)dealProxy).Close();
82.Set the Startup Project properties of the TradingService solution as shown in figure 4.3, below.
Page 67
Figure 4.3 The Startup Project Properties of the TradingService
Solution
83.Choose Debug and then Start Debugging from the Visual Studio menus.
84.When you see a message in the console application window of the TradeRecordingServiceHost
confirming that the Trade Recording Service is available, and a message in the console
application window of the TradingServiceHost executable confirming that the derivatives trading
service is available, enter a keystroke into the console application window of the Client
executable. The output in the console application window of the TradingServiceHost should be
similar to the output shown in figure 2.1, above. The output in the console application window of
the Client should be similar to the output in figure 1.7, above. The output in the console
application window of the TradeRecordingServiceHost should be similar to the output shown in
figure 4.4, below.
Page 68
Figure 4.4 Output from the Trade Recording Service
85.Choose Debug and Stop Debugging from the Visual Studio menus.
86.Choose Debug and then Start Debugging from the Visual Studio menus.
87. Choose Debug and then Start Debugging from the Visual Studio menus.
88.Close the console application window of the TradeRecordingServiceHost.
89.When you see a message in the console application window of the TradingServiceHost
executable confirming that the derivatives trading service is available, enter a keystroke into the
console application window of the Client executable. The output in the console application
window of the TradingServiceHost should be similar to the output shown in figure 2.1, above.
The output in the console application window of the Client should be similar to the output in figure
1.7, above.
90.Choose Debug and Stop Debugging from the Visual Studio menus.
91. Start a new instance of the TradeRecordingServiceHost.
92.The output in the console application window of the TradeRecordingServiceHost should be
similar to the output shown in figure 4.4, above, thereby proving that the Trade Recording Service
can process messages that were sent to it when it was not available.
93.Choose Debug and Stop Debugging from the Visual Studio menus.
Page 69
Exercise 5 – Wrap a set of messages in a transaction
In this exercise, you will witness the Windows Communication Foundation’s ability to compose multiple
distributed operations into a single unit of work, or transaction. We will assume that a client using the
Derivatives Trading Service that we have been constructing will want to estimate the price of two deals:
a primary deal, and another one that is intended as a hedge against the possibility of losses in the first.
The client will either commit to both deals together, or to neither one. The client will commit to both
deals together if the difference in the estimated value of the two deals differs within a certain range.
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Transactions;
namespace Client
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press any key when the service is ready.");
Console.ReadKey();
do
{
Console.WriteLine();
dealProxy.BeginDeal();
Page 70
trade.Symbol = "MSFT";
trade.Date = DateTime.Now.AddMonths(2);
dealProxy.AddTrade(trade);
dealProxy.AddFunction("InterestRateEstimation");
dealProxy.AddFunction("TechnologyStockEstimation");
dealValue = dealProxy.Calculate();
dealProxy.Purchase();
dealProxy.EndDeal();
hedgeProxy = new
ChannelFactory<ITradingService>("TradingServiceConfiguration").CreateChannel();
hedgeProxy.BeginDeal();
hedgeProxy.AddFunction("InterestRateEstimation");
hedgeProxy.AddFunction("TechnologyStockEstimation");
hedgeValue = hedgeProxy.Calculate();
hedgeProxy.Purchase();
hedgeProxy.EndDeal();
}
}
}
catch(Exception exception)
{
Console.WriteLine(exception.Message);
Page 71
}
finally
{
((IChannel)dealProxy).Close();
((IChannel)hedgeProxy).Close();
}
}
Console.WriteLine();
Console.WriteLine("Done.");
Console.ReadKey();
}
}
}
96.Now enhance the Derivatives Trading Service so that it will only record any single trade if the
client commits to all of the trades that it is considering together. To do so, add an attribute to the
Purchase() method of the TradingSystem class in the TradingSystem.cs module of the
TradingService project.
[OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
void ITradingService.Purchase()
{
DealData dealData =
OperationContext.Current.InstanceContext.Extensions.Find<DealData>();
ITradeRecorder proxy = new
ChannelFactory<ITradeRecorder>("TradeRecordingService").CreateChannel();
proxy.RecordTrades(dealData.Trades);
Console.WriteLine("Purchased!");
}
97. Addan attribute indicating that the Purchase() operation can be composed into a transaction in
the declaration of the operation in the ITradingService.cs module of the TradingService project:
Page 72
void AddFunction(string function);
[OperationContract(IsInitiating=false,IsTerminating=false)]
decimal Calculate();
[OperationContract(IsInitiating=false,IsTerminating=false,IsOneWay=false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Purchase();
[OperationContract(IsInitiating=false,IsTerminating=true,IsOneWay=true)]
void EndDeal();
}
98.Modifythe app.config file of the TradingServiceHost project to specify that information about any
transaction in progress is to be passed to the Derivatives Trading Service:
<netTcpBinding>
<binding name="ReliableBinding"
transactionFlow="true" >
<reliableSession enabled="true" ordered ="true"/>
</binding>
</netTcpBinding>
99.Inthe Client project of the TradingService solution, alter the definition of the ITradingService
interface to conform to the new definition in the TradingService project:
100.Change the configuration of the binding in the app.config file of the Client to match the definition
of the binding in the configuration of the service:
<netTcpBinding>
<binding name="ReliableBinding"
transactionFlow="true" >
<reliableSession enabled="true" ordered ="true"/>
</binding>
</netTcpBinding>
Page 73
Task 4 – See the Composition of Multiple Distributed Operations into a Single Unit of Work
101.Inthe TradingService solution in Visual Studio, choose Debug and then Start Debugging from
the menus.
102.When you see a message in the console application window of the TradeRecordingServiceHost
confirming that the Trade Recording Service is available, and a message in the
TradingServiceHost executable confirming that the derivatives trading service is available, enter a
keystroke into the console application window of the Client executable. The output in the console
application windows of the Client executable, the TradingServiceHost executable, and the Trade
RecordingHost executable should be similar to the output in figures 5.1, 5.2, and 5.3, below.
There we see that the client has sent four purchase methods to the service, but only committed to
the first pair, and, consequently, only two trades show up in the output of the Trade Recording
Service.
Page 74
Figure 5.2 Output from the Trading Service
Page 75
103.Choose Debug and Stop Debugging from the Visual Studio menus.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class TradeRecorder: ITradeRecorder
{
private const string CounterCategoryName = "TradeRecording";
private const string VolumeCounterName = "Trade Volume";
public TradeRecorder()
{
if
(PerformanceCounterCategory.Exists(TradeRecorder.CounterCategoryName))
{
PerformanceCounterCategory.Delete(TradeRecorder.CounterCategoryName);
}
this.counterCategory =
PerformanceCounterCategory.Create(TradeRecorder.CounterCategoryName,"Trade
Recording
Data",PerformanceCounterCategoryType.MultiInstance,counterCollection);
}
Page 76
#region ITradeRecorder Members
void ITradeRecorder.RecordTrades(Trade[] trades)
{
Console.WriteLine("Recording trade ...");
#endregion
}
}
Task 2 – Update the Value of the Custom Trading Volume Performance Counter
105.For each instance of the Trade Recording Service to be able to update the value of the newly-
defined trading volume performance counter for trades communicated across its own receiving
queue, it must access its own instance of the performance counter. That can be accomplished
using the facilities of Windows Management Instrumentation, so add a reference to the
System.Management assembly to the TradeRecordingService project in the TradingService
solution.
106.Include the System.Management, System.Management.Instrumentation,
System.ServiceModel.Description, and System.Runtime.InteropServices namespaces in the
TradeRecorder.cs module of the TradeRecordingService project:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;
using System.Management.Instrumentation;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
107.To the TradeRecorder class in the TradeRecorder.cs module, add the InitializeCounters() method
for retrieving the instance of the trading volume performance counter associated with the current
instance of the Trade Recording Service:
Page 77
}
while (true)
{
try
{
foreach (string name in names)
{
string condition = string.Format("SELECT * FROM Service
WHERE Name=\"{0}\"", name);
SelectQuery query = new SelectQuery(condition);
ManagementScope managementScope = new
ManagementScope(@"\\.\root\ServiceModel", new ConnectionOptions());
ManagementObjectSearcher searcher = new
ManagementObjectSearcher(managementScope, query);
ManagementObjectCollection instances = searcher.Get();
foreach (ManagementBaseObject instance in instances)
{
PropertyData data =
instance.Properties["CounterInstanceName"];
this.volumeCounter = new
PerformanceCounter(TradeRecorder.CounterCategoryName,
TradeRecorder.VolumeCounterName, data.Value.ToString());
this.volumeCounter.ReadOnly = false;
this.volumeCounter.RawValue = 0;
break;
}
}
break;
}
catch(COMException)
{
if(this.volumeCounter != null)
{
Console.WriteLine("Volume counter initialized.");
}
Console.WriteLine("Counters initialized.");
108.Include
the System.Threading namespaces in the TradeRecorder.cs module of the
TradeRecordingService project:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;
Page 78
using System.Management.Instrumentation;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.Text;
using System.Threading;
109.Modify
the RecordTrades() method of the TradeRecorder class in the TradeRecordingService.cs
module to update the value of the trading volume performance counter:
this.volumeCounter.RawValue = this.tradeCount;
110.Modify
the app.config file of the TradeRecordingServiceHost project to expose data via
performance counters and via Windows Management Instrumentation :
Page 79
</binding>
</netMsmqBinding>
</bindings>
</system.serviceModel>
</configuration>
111.Alter the code in the Program.cs module of the TradeRecordingServiceHost project to call the
InitializeCounter() method that you added to the TradeRecordingService:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace Fabrikam
{
class Program
{
string queueName = “.\\private$\\TradeRecording”;
host.Close();
}
}
}
}
112.Modify the code in the Program.cs file of the Client project so as to generate a large number of
trades:
using System;
using System.Collections.Generic;
using System.ServiceModel;
Page 80
using System.Text;
using System.Transactions;
namespace Client
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press any key when the service is ready.");
Console.ReadKey();
//do
for(int i = 0;i < 500;i++)
{
Console.WriteLine();
dealProxy.BeginDeal();
dealProxy.AddFunction("InterestRateEstimation");
dealProxy.AddFunction("TechnologyStockEstimation");
dealValue = dealProxy.Calculate();
dealProxy.Purchase();
dealProxy.EndDeal();
hedgeProxy = new
ChannelFactory<ITradingService>("TradingServiceConfiguration").CreateChannel();
hedgeProxy.BeginDeal();
Page 81
trade.Symbol = "MSFT";
trade.Date = DateTime.Now.AddMonths(2);
hedgeProxy.AddTrade(trade);
hedgeProxy.AddFunction("InterestRateEstimation");
hedgeProxy.AddFunction("TechnologyStockEstimation");
hedgeValue = hedgeProxy.Calculate();
hedgeProxy.Purchase();
hedgeProxy.EndDeal();
}
}
Console.WriteLine();
Console.WriteLine("Done.");
Console.ReadKey();
}
}
}
113.Choose Debug and then Start Debugging from the Visual Studio menus.
114.When you see a message in the console application window of the TradeRecordingServiceHost
confirming that the Trade Recording Service is available, and a message in the
TradingServiceHost executable confirming that the derivatives trading service is available,
choose Run from the Windows Start menu, enter,
PerfMon
Page 82
in the Run dialog box and click on the OK button.
115.Inthe Performance window, click the Add button, and then scroll through the list of performance
counter categories in the list labeled Performance Object, as shown in figure 6.1, below.
Examine the counters in the built-in categories of Windows Communication Foundation
performance counters. Those categories are called, ServiceModelService,
ServiceModelEndpoint, and ServiceModelOperation, and provide counters for monitoring
Windows Communication Foundation applications at the level of the service, the endpoints of a
service, and the individual operations of a service endpoint.
116.Select the custom TradeRecording category, and click on the Add button, and then close the
dialog.
117.In the console application window of the Client executable, enter a keystroke.
118.Observe how, as soon as the client starts committing to trades of derivatives, the graph of the
value of the custom Trade Volume counter begins to show activity in the Performance window, as
depicted in figure 6.2, below.
119.The value of the Trade Volume counter will quickly exceed the default scale of the graph, so
right-click on the entry for the Trade Volume counter in the list in the bottom right-hand corner of
the Performance window, as show in figure 6.3, below, and alter the scale to 0.01, as shown in
figure 6.4, below.
Page 83
Figure 6.2 Monitoring the Custom Trade Volume Performance Counter
Page 84
Figure 6.3 Accessing the Performance Counter Display Properties
Page 85
Figure 6.3 Adjusting the Scale of the Display of a Performance Counter
Page 86
Lab Summary
In this lab you performed the following exercises.
Thus, you have seen the capability of the Windows Communication Foundation to readily support the
requirements of demanding distributed programming solutions.
Page 87