[Reprint] Windows Communication Foundation entry (Part Three)

Source: Internet
Author: User
[From] http://www.cnblogs.com/wayfarer/archive/2006/04/17/377016.html
[Thank you] Zhang Yi: clear window notes

Windows Communication Foundation journey Series 3

Download the sample code: DuplexSample.rar

4. Service Contract Programming Model
In Part Two, I used "Hello World" as an example to explain how to define a Service. The core is to apply ServiceContractAttribute to interfaces or classes and OperationContractAttribute to methods. In the Service method, multiple parameters or return types can be accepted, as long as these data types can be serialized. This method is usually called a local object. It is a Remote Procedure Call (local-object, Remoting-Procedure-Call) method, which is very helpful for developers to quickly develop services.

In the Service Contract programming model, another method is based on Message Contract. A service method can have only one parameter and one return value, and their data type is A Message Type customized through Message Contract. In a custom message, you can define the detailed Header and Body for the message, making the message exchange more flexible and more conducive to message control.

An interesting topic is that when we define a Service, if a private method is applied OperationContractAttribute, this method can be called on the client. This seems to be in conflict with the significance of private for object encapsulation. But such a rule has its practical significance, because for a service, the requirements of the server and the client are often different. On the server side, even if the service object is allowed to be called remotely, local calls may vary depending on the situation. The following service definition:
[ServiceContract]
Public class BookTicket
{
[OperationContract]
Public bool Check (Ticket ticket)
{
Bool flag;
// Logic to check whether the ticket is none;
Return flag;
}
[OperationContract]
Private bool Book (Ticket ticket)
{
// Logic to book the ticket
}
}
In the BookTicket service class, both the Check and Book methods are service methods, but the latter is defined as private methods. Why? Because for customers, they will first check whether there are any movie tickets, but then reserve the movie tickets. That is to say, these two functions are customer-oriented services and will be called remotely. For the Check method, in addition to being called by remote customers, it may also be called by business logic such as querying movie tickets, booking movie tickets, and selling movie tickets. The Book method is only applicable to remote customers and can only be called remotely. To ensure the security of this method, set it to private so that the local object does not call it.

Therefore, in WCF, whether a method should be set as a service method or public or private must be determined based on the specific business logic. If there are many private service methods involved, a good method is to combine these methods by using the Fa C ade mode of the design mode. The real logic of these methods may be dispersed into their local objects. For these local objects, you can also impose certain access restrictions, as shown in the following code:
Internal class BusinessObjA
{
Internal void FooA (){}
}
Internal class BusinessObjB
{
Internal void FooB (){}
}
Internal class BusinessObjC
{
Internal void FooC (){}
}
[ServiceContract]
Internal class Fa C ade
{
Private BusinessObjA objA = new BusinessObjA ();
Private BusinessObjB objB = new BusinessObjB ();
Private BusinessObjC objC = new BusinessObjC ();
[OperationContract]
Private void SvcA ()
{
ObjA. FooA ();
}
[OperationContract]
Private void SvcB ()
{
ObjB. FooB ();
}
[OperationContract]
Private void SvcC ()
{
ObjC. FooC ();
}
}
Methods FooA, FooB, and FooC are used as the internal method and refuse to be called by local objects outside the Assembly. However, SvcA, SvcB, and SvcC methods can be called by remote objects. We can even define BusinessObjA, BusinessObjB, and other classes as Nested classes of the Fa C ade class. Using this method facilitates these special service methods and is more convenient to remote customers.

Define a Service. The most common is to explicitly define an interface as a Service. This method makes the definition of the service more flexible. I have already described this in Part Two. Of course, if this method is used, the private method described above will not become the form of a service method, because in an interface definition, all methods are public.

Another topic is "inheritance of service interfaces ". An interface marked with [ServiceContract] allows multiple interfaces marked with the same [ServiceContract] in its inheritance chain. The OperationContract method defined in the interface is based on the principle of "aggregation", as shown in the following code:
[ServiceContract]
Public interface IOne
{
[OperationContract (IsOneWay = true)]
Void ();
}
[ServiceContract]
Public interface ITwo
{
[OperationContract]
Void B ();
}
[ServiceContract]
Public interface IOneTwo: IOne, ITwo
{
[OperationContract]
Void C ();
}

In this example, the interface IOneTwo inherits the interfaces IOne and ITwo. In this case, the service method exposed by the Service IOneTwo should be method A, Method B, and method C.

However, when we adopt the Duplex message exchange mode (Duplex will be described in detail later in this article), the callback interface of the service interface has certain limitations on interface inheritance. When WCF requires that the Service Interface IB inherit from another service interface IA, the IB callback interface IBCallBack must inherit IACallBack at the same time; otherwise, an InvalidContractException exception is thrown. The correct definition is as follows:
[ServiceContract (CallbackContract = IACallback)]
Interface IA {}
Interface IACallback {}

[ServiceContract (CallbackContract = IBCallback)]
Interface IB: IA {}
Interface IBCallback: IACallback {}

5. Message Exchange Patterns (MEPS)
In WCF, there are three message exchange modes between the server and the client: Request/Reply, One-Way, and Duplex.

1. Request/Reply
This is the default message exchange mode. The client calls a service method to send a Request. After receiving the Request, the server performs the corresponding operation and returns a result value (Reply ).

If there are no other special settings, if a method is marked with OperationContract, the message exchange mode of this method is the Request/Reply method, even if its return value is void. Of course, we can also set IsOneWay to false, which is also the default setting. The following code is used:
[ServiceContract]
Public interface ICalculator
{
[OperationContract]
Int Add (int a, int B );

[OperationContract]
Int Subtract (int a, int B );
}

2. One-Way
If the message exchange mode is One-Way, it indicates that there is only a request between the client and the server and there is no response. Even if the response is sent, the response is ignored. This method is similar to the notification or broadcast of a message. When a service method is set to One-Way, if the method returns a value, an InvalidOperationException is thrown.

To set the service method to One-Way, you only need to set the IsOneWay attribute of OperationContractAttribute to true, as shown in the following code:
Public class Radio
{
[OperationContract (IsOneWay = true)]
Private void BroadCast ();
}

3. Duplex
The Duplex message exchange mode provides two-way communication between the client and the server. Its implementation also enables asynchronous callback for message exchange.

The Duplex for message exchange is relatively complicated. It needs to define two interfaces. The service interface is used to send messages to the server, while the callback interface is used to return messages to the client from the server, which is completed through callback. The interface is defined as follows:
Service Interface:
[ServiceContract (Namespace = "http://microsoft.servicemodel.samples /",
Session = true, CallbackContract = typeof (ICalculatorDuplexCallback)]
Public interface ICalculatorDuplex
{
[OperationContract (IsOneWay = true)]
Void Clear ();

[OperationContract (IsOneWay = true)]
Void AddTo (double n );

[OperationContract (IsOneWay = true)]
Void SubtractFrom (double n );

[OperationContract (IsOneWay = true)]
Void MultiplyBy (double n );

[OperationContract (IsOneWay = true)]
Void DivideBy (double n );
}
Callback interface:
Public interface ICalculatorDuplexCallback
{
[OperationContract (IsOneWay = true)]
Void Equals (double result );

[OperationContract (IsOneWay = true)]
Void Equation (string equation );
}
Note that in the interface definition, the message conversion mode of each service method is set to One-Way. In addition, the callback interface is called locally, so [ServiceContract] is not required. In the service interface, you must set the CallbackContract attribute of ServiceContractAttribute to point to the type of the callback interface.

For Classes implementing services, whether the InstanceContextMode adopts the PerSession mode or the PerCall mode depends on whether the service object needs to be saved. If it is a PerSession, the life cycle of the service object is to survive in a session. In PerCall mode, a service object is created when a method is called and destroyed after the method is called. However, in Duplex mode, the Single method cannot be used; otherwise, an exception is thrown. The implementation in this example is as follows:
[ServiceBehavior (InstanceContextMode = InstanceContextMode. PerSession)]
Public class CalculatorService: ICalculatorDuplex
{
Double result;
String equation;
ICalculatorDuplexCallback callback = null;
Public CalculatorService ()
{
Result = 0.0D;
Equation = result. ToString ();
Callback = OperationContext. Current.
GetCallbackChannel ();
}
Public void AddTo (double n)
{
Result + = n;
Equation + = "+" + n. ToString ();
Callback. Equals (result );
}
// Other code not shown.
}

In CalculatorService, the callback interface object callback is obtained through OperationContext. Current. GetCallbackChannel <>. In the service method such as AddTo (), call the method of the callback object to return messages to the client.

When Duplex is used, the Binding used by Contract should be the WSDualHttpBinding provided by the system. If BasicHttpBinding is used, an error will occur. Therefore, the Host program should be as follows:
Public static void Main (string [] args)
{
Uri uri = new Uri ("http: // localhost: 8080/servicemodelsamples ");
Using (ServiceHost host = new ServiceHost (typeof (CalculatorService), uri ))
{
Host. AddServiceEndpoint (typeof (ICalculatorDuplex), new WSDualHttpBinding (), "service. svc ");
Host. Open ();
Console. WriteLine ("Press any key to quit service .");
Console. ReadKey ();
}
}
If the configuration file is used, the corresponding modifications should also be made, such:
<System. serviceModel>
<Client>
<Endpoint name = ""
Address = "http: // localhost: 8080/servicemodelsamples/service. svc"
Binding = "wsDualHttpBinding"
BindingConfiguration = "DuplexBinding"
Contract = "ICalculatorDuplex"/>
</Client>
<Bindings>
<! -- Configure a binding that support duplex communication -->
<WsDualHttpBinding>
<Binding name = "DuplexBinding"
ClientBaseAddress = "http: // localhost: 8000/myClient/">
</Binding>
</WsDualHttpBinding>
</Bindings>
</System. serviceModel>

After the Server delivers the information back to the client, the message is processed by the callback object. Therefore, the implementation of the callback object should be completed on the client, the code shown below should be in the client:
Public class CallbackHandler: ICalculatorDuplexCallback
{
Public void Equals (double result)
{
Console. WriteLine ("Equals ({0})", result );
}
Public void Equation (string equation)
{
Console. WriteLine ("Equation ({0})", equation );
}
}

The client calls the service object as follows:
Class Client
{
Static void Main ()
{
// Construct InstanceContext to handle messages on
// Callback interface.
InstanceContext site = new InstanceContext (new CallbackHandler ());

// Create a proxy with given client endpoint configuration.
Using (CalculatorDuplexProxy proxy =
New CalculatorDuplexProxy (site, "default "))
{
Double value = 100.00D;
Proxy. AddTo (value );
Value = 50.00D;
Proxy. SubtractFrom (value );
// Other code not shown.

// Wait for callback messages to complete before
// Closing.
System. Threading. Thread. Sleep (500 );
// Close the proxy.
Proxy. Close ();
}
}
}

Note that in Duplex, the session creation time is not the time when the client creates a Proxy instance, but the session is created only after the method of the service object is called for the first time, at this time, the service object will be instantiated before the method call until the session ends. The service object exists.

The above code example can be found in the WinFX SDK Sample. However, this example does not directly reflect the Duplex function. Based on the previous introduction, we know that Duplex has two-way communication between the client and the server, and its implementation can also enable asynchronous callback for message exchange. Therefore, I implemented two instances to demonstrate the role of Duplex in two aspects.

(1) two-way communication between the client and the server -- ChatDuplexWin
Example Description: A small program similar to a chat room. Duplex enables the client to communicate with the server, enabling the client to chat with the server.

The service interface and callback interface are defined as follows:
[ServiceContract (Namespace = "http://www.brucezhang.com/WCF/Samples/ChatDuplex", Session = true, CallbackContract = typeof (IChatDuplexCallback)]
Public interface IChatDuplex
{
[OperationContract (IsOneWay = true)]
Void Request (string cltMsg );
[OperationContract (IsOneWay = true)]
Void Start ();
}
Public interface IChatDuplexCallback
{
[OperationContract (IsOneWay = true)]
Void Reply (string srvMsg );
}
Obviously, the Request method is used to send messages to the server, while the Reply method enables the server to send messages back to the client. The Start () method in IChatDuplex of the service interface is used to create a session for display, because in this method, I need to directly obtain the callback object so that the server does not have to wait for the client to send a message first, instead, you can use the callback object to actively send messages to the client to implement the chat function.

The implementation class code is as follows:
[ServiceBehavior (InstanceContextMode = InstanceContextMode. PerSession)]
Public class ChatDuplex: IChatDuplex
{
Public ChatDuplex ()
{
M_callback = OperationContext. Current. GetCallbackChannel ();
}
Private IChatDuplexCallback m_callback = null;
Public void Request (string cltMsg)
{
ChatRoomUtil. MainForm. FillListBox (string. Format ("Client: {0}", cltMsg ));
}
Public void Start ()
{
ChatRoomUtil. MainForm. SetIIMDuplexCallback (m_callback );
}
}

Because I want to display the messages sent from the client in the main form interface on the server interface. Therefore, the global variable MainForm is used to save the main form object:
Public static class ChatRoomUtil
{
Public static ServerForm MainForm = new ServerForm ();
}
When the server is running, the main window of the Application is also the global variable:
Application. Run (ChatRoomUtil. MainForm );

To implement the chat function, the biggest obstacle is that when the server receives a client message, it cannot Reply the message immediately. Instead, it should wait for the server user to enter the content of the message to be sent back. That is to say, when the client calls the Request Method of the service object, it cannot directly call the callback object. Therefore, I use the Start () method to pass the callback object obtained from the service object to the main form object. In this way, the callback object can be called when the server sends a message:
Public partial class ServerForm: Form
{
Private IChatDuplexCallback m_callback;
Private void btnSend_Click (object sender, EventArgs e)
{
If (txtMessage. Text! = String. Empty)
{
LbMessage. Items. Add (string. Format ("Server: {0}", txtMessage. Text ));
If (m_callback! = Null)
{
M_callback.Reply (txtMessage. Text );
}
TxtMessage. Text = string. Empty;
}
}
Public void FillListBox (string message)
{
LbMessage. Items. Add (message );
}
Public void SetIIMDuplexCallback (IChatDuplexCallback callback)
{
M_callback = callback;
}
// Other code not shown;
}

The implementation of the client is relatively simple. Note the implementation of the callback interface:
Public class ChatDuplexCallbackHandler: IChatDuplexCallback
{
Public ChatDuplexCallbackHandler (ListBox listBox)
{
M_listBox = listBox;
}
Private ListBox m_listBox;

Public void Reply (string srvMsg)
{
M_listBox.Items.Add (string. Format ("Server: {0}", srvMsg ));
}
}
Since I have customized the constructor of this object, there will be a slight difference in the practical proxy:
InstanceContext site = new InstanceContext (new ChatDuplexCallbackHandler (this. lbMessage ));
Proxy = new ChatDuplexProxy (site );
Proxy. Start ();

Through the Start () method of the proxy object, we create a session when the proxy object is created, so that the service object is instantiated and the following line of code can be run:
M_callback = OperationContext. Current. GetCallbackChannel ();

That is to say, after the proxy object is created, the server has obtained the callback object, which ensures that the server can send messages to the client first instead of making errors because the callback is null.

(2) asynchronous callback of message exchange-AsyncDuplexWin
Instance Description: This instance is simple to verify whether the client can be run asynchronously when the callback object is called. When a service object is called, the server performs an accumulation operation. Before the calculation is complete, the client executes the task of displaying accumulated numbers. After the calculation on the server end, the callback object will be called as long as the thread of the client program is in Sleep state, then, you can choose whether to continue running the remaining tasks. In this example, the server is a console application, and the client is a Windows application.

The interface definition in this example is very simple and will not be repeated. The code for implementing the class is as follows:
Public class SumDuplex: ISumDuplex
{
Public SumDuplex ()
{
Callback = OperationContext. Current. GetCallbackChannel ();
}
Private ISumDuplexCallback callback = null;

# Region ISumDuplex Members
Public void Sum (int seed)
{
Int result = 0;
For (int I = 1; I <= seed; I ++)
{
Thread. Sleep (10 );
Console. WriteLine ("now at {0}", I );
Result + = I;
}
Callback. Equals (result );
}
# Endregion
}
Obviously, when the client calls this service object, the iteration value is printed on the console of the server.

Because the client needs to stop running the current task when calling callback, The multithreading mechanism is required:
Public delegate void DoWorkDelegate ();
Public partial class ClientForm: Form
{
Public ClientForm ()
{
InitializeComponent ();
InstanceContext site = new InstanceContext (new SumDuplexCallbackHandler (this. lbMessage ));
Proxy = new SumDuplexProxy (site );
}
Private SumDuplexProxy proxy;
Private Thread thread = null;
Private DoWorkDelegate del = null;
Private int counter = 0;

Private void btnStart_Click (object sender, EventArgs e)
{
Proxy. Sum (1, 100 );
Thread = new Thread (new ThreadStart (delegate ()
{
While (true)
{
If (ClientUtil. IsCompleted)
{
If (MessageBox. Show ("Game over, Exit? "," Policy ", MessageBoxButtons. YesNo,
MessageBoxIcon. Question) = DialogResult. Yes)
{
Break;
}
}
If (counter> 10000)
{
Break;
}
If (del! = Null)
{
Del ();
}
Thread. Sleep (50 );
}
}
));
Del + = new DoWorkDelegate (DoWork );
Thread. Start ();
}

Private void DoWork ()
{
If (lbMessage. InvokeRequired)
{
This. Invoke (new DoWorkDelegate (DoWork ));
}
Else
{
LbMessage. Items. Add (counter );
LbMessage. Refresh ();
Counter ++;
}
}
Private void ClientForm_FormClosing (object sender, FormClosingEventArgs e)
{
If (thread! = Null)
{
Thread. Abort ();
}
}
}

Because the items of The ListBox control needs to be modified in multiple threads, because the control is not thread-safe, the InvokeRequired attribute of the control should be used. In addition, the while (true) method is used to control the running of the current thread and the global variable ClientUtil. isCompleted checks whether the callback object is called. If it is called, a dialog box is displayed, selecting whether to exit the current task. The so-called current task is actually to call the DoWork method and add the accumulated counter value to the items of The ListBox control. Note that the client callback object is implemented as follows:
Class SumDuplexCallbackHandler: ISumDuplexCallback
{
Public SumDuplexCallbackHandler (ListBox listBox)
{
M_listBox = listBox;
}
Private ListBox m_listBox;
# Region ISumDuplexCallback Members
Public void Equals (int result)
{
ClientUtil. IsCompleted = true;
M_listBox.Items.Add (string. Format ("The result is: {0}", result ));
M_listBox.Refresh ();
}
# Endregion
}
When the client clicks the Start button and calls the Sum method of the service object, the iteration value is displayed on the server, and the client starts to execute its own tasks and add the accumulate number to The ListBox control. Once the calculation on the server is completed, the calculation result is transmitted to the client through the callback object. The global variable ClientUtil. IsCompleted is set to true. If the thread with the accumulated value is in sleep state, the system will add the result value to The ListBox control. A dialog box will pop up to determine whether to continue the remaining tasks.

Note: The Code and instance in this example are both run in Feb 2006 CTP.

<To be continued>

Refer:
1. David Chappell, Introducing Windows Communication Foundation
2. Microsoft Corporation, WinFX SDK

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.