Home > Articles > Programming > Windows Programming

This chapter is from the book

This chapter is from the book

Microsoft included an example of using events in .NET Remoting in the help documentation at

ms-help://MS.VSCC/MS.MSDNVS/
cpguide/html/cpconremotingexampledelegatesevents.htm
.

(You can open this help topic by browsing to the referenced link in Internet Explorer or the URL control of the Web toolbar in the VS .NET IDE.) The example is a simple console-based, chat example that permits clients to communicate through a Singleton object. Rather than repeat that code here (and because I thought the example was fun), I include a WinForms-based version that is slightly more advanced and a lot of fun to play with. Here is the basic idea.

Recall that we talked about Singleton remoted objects. When we create a Singleton MarshalByRefObject, every client will get a transparent proxy—think “super pointer”—to the exact same object on the server. By exposing an event each client can add an event handler to the event. Now mix in delegates. Delegates are multicast in .NET. This means that the event is sent to all handlers. Thus, if one client raises an event, every client that has a handler in that object's invocation list will receive an event message. Combined with a Singleton Remote object reference, each client will be adding a handler to one object's multicast delegate invocation list. Voilà! A simplified chat application.

Understanding Remote Event Behavior

We have thought of remoting so far as clients having transparent proxy reference to an object on the server. However, when we need to raise an event on the server, the server is actually calling back to the client; the client handler becomes a server, and the server becomes the client. Consequently the client has a reference to the server, and when the roles are reversed, the server needs a reference to the client. We can solve this predicament by sharing code between client and server.

Invoking Remote Events

The example is comprised of three projects all contained in the \Chapter 8\Events\Chat\Chat.sln file. The solution includes the Chat.vbproj client, ChatServer.vbproj, and the shared class library General. The ChatServer.exe is a console application that has a reference to General.dll and configures ChatServer.ChatMessage as a well-known Singleton object using an application configuration file. The Chat.exe server is a Windows Forms application (see Figure 8.3) that has a reference to General.dll. Each instance of Chat.exe requests a reference to the ChatMessage object created on the remote server. The server returns the same instance to every client that requests a ChatMessage object on the same channel from the same server. After the client gets the ChatMessage wrapper back, it assigns one of its event handlers to an event defined by the wrapper class. When any particular client sends a message to the server, a ChatMessage object raises an event and all clients get the event. As a result we can selectively echo the original message (or not) to the sender and notify each client of a message.

08fig03.gifFigure 8.3. The simple instant messaging example.

The server class simply uses a configuration file to register a Singleton instance of a ChatMessage wrapper object. You can see the code for the server in \Chapter 8\Events\Server\Server.vb. The shared General.dll assembly (which contains the wrapper) and the client that sends and handles events provide the most interesting functionality. We will go over most of that code next.

Implementing the Shared Event Wrapper

The code containing the shared event wrapper class is defined in \Chapter 8\Events\General\Class1.vb. Class1.vb defines three classes and a delegate. Listing 8.12 contains all the code for Class1.vb; a synopsis of the code follows the listing.

Listing 8.12 The Shared Classes That Manage Events between Client and Server

1:   Option Strict On
2:   Option Explicit On
3:
4:   Imports System
5:   Imports System.Runtime.Remoting
6:   Imports System.Runtime.Remoting.Channels
7:   Imports System.Runtime.Remoting.Channels.Http
8:   Imports System.Runtime.Remoting.Messaging
9:
10:  Imports System.Collections
11:
12:  <Serializable()>_
13:  Public Class ChatEventArgs
14:    Inherits System.EventArgs
15:
16:    Private FSender As String
17:    Private FMessage As String
18:
19:    Public Sub New()
20:      MyBase.New()
21:    End Sub
22:
23:    Public Sub New(ByVal sender As String, _
24:      ByVal message As String)
25:      MyClass.New()
26:      FSender = sender
27:      FMessage = message
28:    End Sub
29:
30:    Public ReadOnly Property Sender() As String
31:    Get
32:      Return FSender
33:    End Get
34:    End Property
35:
36:    Public ReadOnly Property Message() As String
37:    Get
38:      Return FMessage
39:    End Get
40:    End Property
41:  End Class
42:
43:  Public Delegate Sub MessageEventHandler(ByVal Sender As Object, _
44:    ByVal e As ChatEventArgs)
45:
46:  Public Class ChatMessage
47:    Inherits MarshalByRefObject
48:
49:    Public Event MessageEvent As MessageEventHandler
50:
51:    Public Overrides Function InitializeLifetimeService() As Object
52:      Return Nothing
53:    End Function
54:
55:    <OneWay()> _
56:    Public Sub Send(ByVal sender As String, _
57:      ByVal message As String)
58:
59:      Console.WriteLine(New String("-"c, 80))
60:      Console.WriteLine("{0} said: {1}", sender, message)
61:      Console.WriteLine(New String("-"c, 80))
62:
63:      RaiseEvent MessageEvent(Me, _
64:        New ChatEventArgs(sender, message))
65:    End Sub
66:
67:  End Class
68:
69:
70:  Public Class Client
71:    Inherits MarshalByRefObject
72:
73:    Private FChat As ChatMessage = Nothing
74:
75:    Public Overrides Function InitializeLifetimeService() As Object
76:      Return Nothing
77:    End Function
78:
79:    Public Sub New()
80:      RemotingConfiguration.Configure("Chat.exe.config")
81:
82:      FChat = New ChatMessage()
83:
84:      AddHandler FChat.MessageEvent, _
85:        AddressOf Handler
86:    End Sub
87:
88:    Public Event MessageEvent As MessageEventHandler
89:
90:    Public Sub Handler(ByVal sender As Object, _
91:      ByVal e As ChatEventArgs)
92:      RaiseEvent MessageEvent(sender, e)
93:    End Sub
94:
95:    Public Sub Send(ByVal Sender As String, _
96:      ByVal Message As String)
97:      FChat.Send(Sender, Message)
98:    End Sub
99:
100:   Public ReadOnly Property Chat() As ChatMessage
101:   Get
102:     Return FChat
103:   End Get
104:   End Property
105:
106: End Class

Lines 12 through 41 define a new type of event argument, ChatEventArgs. ChatEventArgs inherits from System.EventArgs and introduces two new members: Message and Sender. Message is the content of the message sent by a client, and Sender is a user name. ChatEventArgs is an example of an object that the client needs for information purposes only; hence it was designated as a by-value object.

Lines 43 and 44 define a new delegate named MessageEventHandler. Its signature accepts the new event argument ChatEventArgs.

Lines 46 through 67 define the by-reference object ChatMessage that is the Singleton object shared by all clients. Every client on the same channel and originating from the same server will be referring to the same instance of this class. The class itself is easy enough, but it demonstrates some old concepts and introduces some new ones. Line 47 indicates that ChatMessage is a by-reference type. Line 49 exposes a public event; this is how all clients attach their event handlers to the ChatMessage Singleton. Lines 51 through 53 override the MarshalByRefObject.InitializeLifetimeService method. InitializeLifetimeService can be overridden to change the lifetime of a Remote object. Return Nothing sets the lifetime to infinity. (Refer to the Managing a Remoted Object's Lifetime subsection later in this chapter for more information.) Lines 55 through 65 define the Send message. Clients use Send to broadcast messages. All Send does is raise MessageEvent. Note that Send is adorned with the OneWayAttribute, which causes the server to treat Send as a “fire and forget” method. Send doesn't care whether the recipients receive the message or not. This handles the case of a client dropping off without disconnecting its handler. (Send also displays trace information on the server application; see Figure 8.4.) That's all the ChatMessage class is: a class shared between client and server that wraps the message invocation.

08fig04.gifFigure 8.4. Trace information being written to the server console.

Finally, we come to the Client class in lines 70 through 106. The Client class plays the role of the executable code that is remotable and shared between client and server. If you examine it closely you will see that it mirrors the ChatMessage class except that Client is responsible for allowing the server to call back into the client application. The Client class in General.dll plays the role of client-application-on-the-server when the roles between client and server are reversed. If we didn't have a remotable class shared between client and server, we would need to copy the client application into the directory of the server application. Remember that for clients to run code defined on a server, we need an interface or shared code in order to have something to assign the shared object to. When the roles between client and server are reversed—client becomes server during the callback—the server would need an interface or shared code to the client to talk back to it. Thus for the same reason that we share code between client and server, we also share code between server and client.

TIP

For a comprehensive discussion of event sinks and .NET Remoting, Ingo Rammer [2002] has written a whole book, Advanced .NET Remoting.

Listing 8.13 contains the Chat.exe.config file that describes the configuration information to the well-known object registered on the server and the back channel to the client used when the client calls the server back.

Listing 8.13 The Configuration File for the Client Application

1:  <?xml version="1.0" encoding="utf-8" ?>
2:  <configuration>
3:    <system.runtime.remoting>
4:      <application>
5:        <channels>
6:          <channel
7:            ref="http"
8:            port="0"
9:          />
10:       </channels>
11:       <client>
12:         <wellknown
13:           type="ChatServer.ChatMessage, General"
14:           url="http://localhost:6007/ChatMessage.soap"
15:         />
16:       </client>
17:     </application>
18:   </system.runtime.remoting>
19:
20:   <appSettings>
21:     <add key="user" value="Your Name Here!" />
22:     <add key="echo" value="true" />
23:   </appSettings>
24: </configuration>

The <channels> element describes the back channel used by server to client. By initializing the port attribute with 0 we allow the port to be dynamically selected. The <client> element registers the reference to the well-known ChatMessage class on the client. This allows us to create an instance of the ChatMessage class on the client using the New operator, getting a transparent proxy instance rather than the literal ChatMessage class also defined in the client. Without the <client> element we would need to use the Activator or we'd end up with a local instance of ChatMessage rather than the remote instance.

Finally, the <appSettings> element is used by the ConfigurationSettings.AppSettings shared property to externalize general, nonremoting configuration information.

Implementing the Client Application

The client application creates an instance of the Client class. Client represents the assembly shared by both client and server, allowing server to talk back to client. The client application (shown in Figure 8.3) actually registers its events with the Client class. Listing 8.14 provides the relevant code for the client application that responds to events raised by the remote ChatMessage object. (The Client.vb source contains about 400 lines of Windows Forms code not specifically related to remoting. Listing 8.14 contains only that code related to interaction with the remote object. For the complete listing, download \Chapter 8\Events\Client\Client.vb.)

Listing 8.14 An Excerpt from the Client Application Related to Remoting

1:  Option Strict On
2:  Option Explicit On
3:
4:  Imports System
5:  Imports System.Runtime.Remoting
6:  Imports System.Runtime.Remoting.Channels
7:  Imports System.Runtime.Remoting.Channels.Http
8:  Imports Microsoft.VisualBasic
9:  Imports System.Configuration
10:
11: Public Class Form1
12:     Inherits System.Windows.Forms.Form
13:     . . .
284:
285:   Public Sub Handler(ByVal sender As Object, _
286:     ByVal e As ChatEventArgs)
287:
288:     If (e.Sender <> User) Then
289:       Received = GetSenderMessage(e.Sender, e.Message) + Received
290:     ElseIf (Echo) Then
291:       Received = GetSendeeMessage(e.Message) + Received
292:     End If
293:
294:   End Sub
295:
296:   Private ChatClient As Client
297:
298:   Private Sub Form1_Load(ByVal sender As Object, _
299:     ByVal e As System.EventArgs) Handles MyBase.Load
300:
301:     Init()
302:
303:     ChatClient = New Client()
304:
305:     AddHandler ChatClient.MessageEvent, _
306:       AddressOf Handler
307:   End Sub
308:
309:   Private Sub Send()
310:     If (ChatClient Is Nothing = False) Then
311:       ChatClient.Send(User, Sent)
312:       Sent = ""
313:     End If
314:   End Sub
315:
316:   Private Sub Button1_Click(ByVal sender As System.Object, _
317:     ByVal e As System.EventArgs) Handles ButtonSend.Click, _
318:     MenuItemSend.Click
319:
320:     Send()
321:
322:   End Sub
323:
324:   Private Sub Form1_Closed(ByVal sender As Object, _
325:     ByVal e As System.EventArgs) Handles MyBase.Closed
326:
327:     If (ChatClient Is Nothing) Then Return
328:     RemoveHandler ChatClient.MessageEvent, _
329:       AddressOf Handler
330:   End Sub
331:  . . .
344:
345:   Private Sub Init()
346:     User = ConfigurationSettings.AppSettings("user")
347:     Echo = (ConfigurationSettings.AppSettings("echo") = "true")
348:   End Sub
349:  . . .
396: End Class

Listing 8.14 contains snippets from Client.vb. Parts that are basic to Windows Forms or programming in general were removed to shorten the listing. Lines 285 through 294 define an event handler named Handler. As you can see from the listing, this handler looks like any other event handler. Note that there are no special considerations made for remoting (although there should be; more on this in a moment).

Line 296 declares the shared instance of the Client object. Client is the remotable object that the server treats like a server when it needs to communicate back with us.

Lines 298 through 307 define the form's Load event handler. Load initializes the application settings (line 301), creates a new instance of the Client class, and associates the form's event handler with the Client class's event handler. Client is the actual object called back by ChatMessage.

Button1_Click in lines 316 through 322 calls a local Send method that invokes Client.Send. Form1_Closed (lines 324 through 330) removes the event handler. If for some reason this code isn't called, the server will try to call this instance of the client application for as long as the server is running. If we hadn't used the OneWayAttribute, removing the client application without removing the event would cause exceptions. Using the OneWayAttribute avoids the exceptions but could potentially send out tons of calls to dead clients. (An alternative is to skip using the OneWayAttribute on the server and remove delegates that cause an exception on the server.) The Init method (lines 345 through 348) demonstrates how to read configuration settings from an application .config file.

Remoting and Threads

.NET Remoting is easier than DCOM and other technologies, but writing distributed applications is still not a trivial exercise. Recall that reference to something missing from the event handler in Listing 8.14 in lines 285 through 294? What's missing is a discussion of threads.

When the event handler is called, it actually comes back on a different thread than the one that Windows Forms controls are in. Recall that in Chapter 6, Multithreading, I said that Windows Forms is not thread-safe. This means that it is not safe to interact with Windows Forms controls across threads. To resolve this predicament we need to perform a synchronous invocation to marshal the call to the event handler—a thread used by remoting—to the same thread the Windows Forms controls are on. In short, we need to add a delegate and call Form.Invoke to move the data out of the event handler onto the same thread that the form and its controls are on.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020