In this first exercise, we will showcase how to create code that utilizes Reliable Sessions.
Create a new WinFX Service project in C:\WCFHandsOn\Chapter5\PartI\Before\.
Specify a name of ConsumerBankingService as the name of the project, and ReliableSessions as the name of the solution.
Within Visual Studio, add a new class and name it ConsumerBankingCore.cs.
Add a reference to System.ServiceModel.
Add using System.ServiceModel; to the top of the Program.cs file:
using System; using System.Collections.Generic; using System.Text; using System.ServiceModel;
Next, create the service interface and service implementation. Specify that this Service Contract will use reliable sessions by specifying (Session=true):
namespace ConsumerBankingService { [ServiceContract(Session=true)] public interface IConsumerBankingCore { [OperationContract] bool Deposit(int accountNumber, decimal amount); [OperationContract] bool Withdraw(int accountNumber, decimal amount); [OperationContract] decimal GetBalance(int accountNumber); } // This will share the information per a single session // [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)] // This will share the information in a single session, across multiple // clients // [ServiceBehavior(InstanceContextMode = InstanceContextMode.Shareable)] [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class ConsumerBankingCore : IConsumerBankingCore { private decimal _balance = default(decimal); public bool Deposit(int accountNumber, decimal amount) { _balance = _balance + amount; Console.WriteLine("Depositing {0} into Account {1}", amount, accountNumber); Console.WriteLine("Balance:" + _balance.ToString()); return true; } public bool Withdraw(int accountNumber, decimal amount) { _balance = _balance - amount; Console.WriteLine("Withdrawing {0} from Account {1}", amount accountNumber); Console.WriteLine("Balance:" +_balance.ToString()); return true; } public decimal GetBalance(int accountNumber) { return _balance; } } }
The next step is to create the host for the service.
Having created the service, you will now create a client to interact with it.
In the open solution, add a new project.
Create a new Windows Forms application named ATMClient in C:\WCFHandsOn\Chapter5\Before\PartI\.
Create the proxy for the banking service.
Right-click on the ConsumerBankingService project, click Debug, and select Start New Instance.
This will start the ConsumerBankingService, making it available for us to query it for metadata with the svcutil utility.
Enter the following at the command prompt:
[View full width]
"C:\Program Files\Microsoft SDKs\Windows\v1.0\Bin\SvcUtil.exe" http://localhost:8080 /ConsumerBankingService
Stop the Debugger.
Open a Visual Studio command prompt and navigate to the directory C:\WCFHandsOn\Chapter5\Before\PartI\.
Using Solution Explorer, add ConsumerBankingCoreProxy.cs to the ATMClient project.
Using Solution Explorer, add output.config to the ATMClient project.
Rename output.config to App.config.
Open the App.Config file.
Modify the endpoint element to include the attribute name with a value of CoreBanking:
[View full width]
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <client> <endpoint name="CoreBanking" address="http://localhost:8080/ ConsumerBankingService" bindingConfiguration="WSHttpBinding_IConsumerBankingCore" binding="customBinding" contract="IConsumerBankingCore" /> </client> <bindings> <customBinding> <binding name="Secure conversation bootstrap binding 5d58043f-7615-48c7-80e6-95668c8117f3" <security defaultAlgorithmSuite="Default" authenticationMode="SspiNegotiated" defaultProtectionLevel="EncryptAndSign" requireDerivedKeys="true" securityHeaderLayout="Strict" includeTimestamp="true" keyEntropyMode="CombinedEntropy" messageProtectionOrder="SignBeforeEncrypt" protectTokens="false" requireSecurityContextCancellation="true" securityVersion="WSSecurityXXX2005" requireSignatureConfirmation="false"> <localClientSettings cacheCookies="true" detectReplays="true" replayCacheSize="900000" maxClockSkew="00:05:00" maxCookieCachingTime="10675199.02:48:05.4775807" replayWindow="00:05:00" sessionKeyRenewalInterval="10:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" timestampValidityDuration="00:05:00" cookieRenewalThresholdPercentage="90" /> <localServiceSettings detectReplays="true" issuedCookieLifetime="10:00:00" maxStatefulNegotiations="1024" replayCacheSize="900000" MaxClockSkew="00:05:00" negotiationTimeout="00:02:00" replayWindow="00:05:00" inactivityTimeout="01:00:00" sessionKeyRenewalInterval="15:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" maxConcurrentSessions="1000" timestampValidityDuration="00:05:00" /> </security> <textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16" messageVersion="Default"writeEncoding="utf-8" /> </binding> <binding name="WSHttpBinding_IConsumerBankingCore"> <security defaultAlgorithmSuite="Default" authenticationMode="SecureConversation" bootstrapBindingConfiguration="Secure conversation bootstrap binding 5d58043f-7615-48c7-80e6-95668c8117f3" bootstrapBindingSectionName="customBinding" defaultProtectionLevel="EncryptAndSign" requireDerivedKeys="true" securityHeaderLayout="Strict" includeTimestamp="true" keyEntropyMode="CombinedEntropy" messageProtectionOrder="SignBeforeEncrypt" protectTokens="false" requireSecurityContextCancellation="true" securityVersion="WSSecurityXXX2005" requireSignatureConfirmation="false" <localClientSettings cacheCookies="true" detectReplays="true" replayCacheSize="900000"maxClockSkew="00:05:00" maxCookieCachingTime="10675199.02:48:05.4775807" replayWindow="00:05:00" sessionKeyRenewalInterval="10:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" timestampValidityDuration="00:05:00" cookieRenewalThresholdPercentage="90" /> <localServiceSettings detectReplays="true" issuedCookieLifetime="10:00:00" maxStatefulNegotiations="1024" replayCacheSize="900000" maxClockSkew="00:05:00" negotiationTimeout="00:02:00" replayWindow="00:05:00" inactivityTimeout="01:00:00" sessionKeyRenewalInterval="15:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" maxConcurrentSessions="1000" timestampValidityDuration="00:05:00"/> </security> <textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16" messageVersion="Default" writeEncoding="utf-8" /> <httpTransport manualAddressing="false" maxBufferPoolSize="524288" maxMessageSize="65536" allowCookies="false" authenticationScheme="Anonymous" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" mapAddressingHeadersToHttpHeaders="false" proxyAuthenticationScheme="Anonymous" realm="" transferMode="Buffered" unsafeConnectionNtlmAuthentication="false" useDefaultWebProxy="true" /> </binding> <binding name="Secure conversation bootstrap binding d58973a4-c474-42f2-8fb8-f2a64bc07c96"> <security defaultAlgorithmSuite="Default" authenticationMode="SspiNegotiated" defaultProtectionLevel="EncryptAndSign" requireDerivedKeys="true" securityHeaderLayout="Strict" includeTimestamp="true" keyEntropyMode="CombinedEntropy" messageProtectionOrder="SignBeforeEncrypt" protectTokens="false" requireSecurityContextCancellation="true" securityVersion="WSSecurityXXX2005" requireSignatureConfirmation="false"> <localClientSettings cacheCookies="true" detectReplays="true" replayCacheSize="900000" maxClockSkew="00:05:00" maxCookieCachingTime="10675199.02:48:05.4775807" replayWindow="00:05:00" sessionKeyRenewalInterval="10:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" timestampValidityDuration="00:05:00" cookieRenewalThresholdPercentage="90" /> <localServiceSettings detectReplays="true" issuedCookieLifetime="10:00:00" maxStatefulNegotiations="1024" replayCacheSize="900000" maxClockSkew="00 :05:00" negotiationTimeout="00:02:00" replayWindow="00:05:00" inactivityTimeout="01:00:00" sessionKeyRenewalInterval="15:00:00" sessionKeyRolloverInterval="00:05:00" reconnectTransportOnFailure="true" maxConcurrentSessions="1000" timestampValidityDuration="00:05:00" /> </security> <textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16" messageVersion="Default" writeEncoding="utf-8" /> </binding> </customBinding> </bindings> </system.serviceModel> </configuration>
Next, open the code view for Form1.cs.
Add the line ConsumerBankingCoreProxy proxy = new ConsumerBankingCoreProxy("CoreBanking"); to the Form1 class:
public partial class Form1 : Form { ConsumerBankingCoreProxy proxy = new ConsumerBankingCoreProxy("CoreBanking"); public Form1() { InitializeComponent(); }
Next, design the Windows Form that will comprise the user interface.
Double-click on Form1.cs in Solution Explorer to modify the form in the designer.
Add a label named lblBankAccountNumber.
Set the text property for this label to "Bank Account Number".
Add a label named lblAmount.
Set the text property for this label to "Amount".
Add a textbox named tbAccountNumber.
Set the text property for this textbox to "12345".
Add a textbox named tbAmount.
Set the text property for this textbox to "25.00".
Add a label named lblBankBalance.
Set the text property for this label to "Current Bank Balance".
Add a label named lblBalance.
Set the text property for this label to "0".
Add a button named "btnDeposit".
Set the text property for this button to "Deposit".
Add a button named "btnWithdraw".
Set the text property for this button to "Withdrawal".
Add a button named "btnSonWithdraw".
Set the text property for this button to "Son Withdrawal".
Arrange these controls to resemble what's shown in Figure 5.1.
Figure 5.1. The designed form.
Now add the code to execute a deposit.
In the Windows Form designer double-click on the btnDeposit control.
This will open the code view. Add the following code to the btnDeposit_Click event:
[View full width]
private void btnDeposit_Click(object sender, EventArgs e) { bool success = proxy.Deposit (Convert.ToInt32(tbAccountNumber.Text), Convert .ToDecimal(tbAmount.Text)); decimal balance = proxy.GetBalance(Convert.ToInt32(tbAccountNumber.Text)); if (success) { lblBalance.Text = balance.ToString(); } else { System.Windows.Forms.MessageBox.Show("Unable to make deposit"); } }
In the Windows Form designer, double-click on the btnWithdraw control.
This will open the code view. Add the following code to the btnWithdraw_Click event:
[View full width]
private void btnWithdraw_Click(object sender, EventArgs e) { bool success = proxy.Withdraw(Convert.ToInt32(tbAccountNumber.Text), Convert .ToDecimal(tbAmount.Text)); decimal balance = proxy.GetBalance(Convert.ToInt32(tbAccountNumber.Text)); if (success) { lblBalance.Text = balance.ToString(); } else { System.Windows.Forms.MessageBox.Show("Unable to make withdrawal"); } }
You will now add the code that uses a second proxy that uses that same session:
In the Windows Form designer, double-click on the btnSonWithdraw control.
This will open the code view.
Add the following code to the btnSonWithdraw_Click event:
[View full width]
private void btnSonWithdraw_Click(object sender, EventArgs e) { using (ConsumerBankingCoreProxy proxySon = new ConsumerBankingCoreProxy("CoreBanking")) { System.ServiceModel.EndpointAddress proxyAddress = proxy.InnerChannel .ResolveInstance(); proxySon.Endpoint.Address = proxyAddress; bool success = proxySon.Withdraw(Convert.ToInt32(tbAccountNumber.Text),25); decimal balance = proxySon.GetBalance(Convert.ToInt32(tbAccountNumber.Text)); if (success) { lblBalance.Text = balance.ToString(); } else { System.Windows.Forms.MessageBox.Show("Unable to make withdrawal"); } } }
You are now ready to test the application.
In this first test, we will run the client and service without the benefit of sessions.
You will notice that there is no information being shared between calls. Each call starts with no shared state of the prior actions. There is no session. The expected outcome of this test is shown in Figure 5.2.
In this next test, we will enable sessions. This will be done by modifying the InstanceContextMode of the Service Behavior.
Note that when you perform deposits and withdrawals now, there is obvious knowledge of prior states.
The expected outcome of this test is shown in Figure 5.3.
There will be scenarios where you wish to share the same session among multiple clients. As in the last test, this can be done by modifying the InstanceContextMode.
In this test, you will see that you can have multiple proxies that can share the same session:
The code for the click event of the Son Withdraw button resolves the address of the first proxy. It creates an EndpointAddress object and assigns that to the second proxy:
That proxy, SonProxy, can now share the same session as the main proxy. Both proxies can interact with the shared state information.