Tải bản đầy đủ (.pdf) (86 trang)

Programming C# 4.0 phần 7 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (10.42 MB, 86 trang )

To make life easy for developers, Visual Studio’s installer sets up a special range of
addresses with an ACL that makes it open for any user logged in to the machine. Lis-
tening on anything starting with http://localhost:8732/Design_Time_Addresses/ will
work, even if you’re logged on with a nonadministrative account. That’s why Visual
Studio chooses the base address you see in Example 13-3—it means you don’t need to
run with elevated privileges.
After the <services> element
you’ll see a <behaviors> element in your App.config, con-
taining a <serviceBehaviors> element which contains a <behavior> element. This sec-
tion allows various WCF features to be switched on or off. You might wonder why
these settings don’t just go into the <services> section. The reason is that you might
want to host multiple services, all of which share common behavior configuration. You
can define a single named <behavior> element, and then point multiple <service> ele-
ments’ behaviorConfiguration attributes at that behavior, reducing clutter in your con-
figuration file. Or, as in this case, you can create an unnamed <behavior> element, which
defines default behavior that applies to all services in this host process. Since we’re
hosting only one service here, this doesn’t offer much advantage, but this separation
can be useful when hosting multiple services.
The <behavior> element that Visual Studio provides has some comments telling you
what you might want to change and why, but paring it down to the essential content
leaves just this:
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
This configures a couple of optional features. The first is related to the metadata ex-
change mentioned earlier—it just ensures that the service description can be fetched


in a certain way. Again, we’ll come back to metadata when we get to the client, so you
can ignore that for now.
The second behavior here—the serviceDebug element—doesn’t have any effect, be-
cause it sets the includeExceptionDetailInFaults property to its default value, False.
Nothing would change if you removed this. The only reason Visual Studio puts this
here at all is to help you out when debugging—sometimes it’s useful to set this to
True temporarily, and putting this entry in the file saves you from having to look up the
name of the setting. Making this True will mean that if your service throws an exception,
the full exception details including stack trace will be sent back to the client in the
response.
Generally speaking, you should never do this, because sending stack traces to your
clients reveals implementation details about your system. If some of your clients are
WCF | 491
evil hackers, this might make it easier for them to break into your system. (Technically,
if your system is completely secure, a stack trace won’t help them, but when did you
last hear about a computer system that was completely secure? It’s safe to presume that
everything has security flaws, so the less help you give hackers the better—this is often
described as reducing the attack surface area of your system.) While you don’t normally
want to send stack traces over the network, doing so can sometimes make it easier to
diagnose problems during development. So you might switch this setting on tempora-
rily to make your life easier. But remember to turn it off before you ship!
That’s everything Visual Studio put into our configuration file. This shows just a tiny
fraction of all the settings we could put in there, but this isn’t a book about WCF, so
that’ll do for now.
After all that, our program still isn’t ready to host the service. As well as putting con-
figuration entries into the application configuration file, our program needs to make
an API call to tell WCF that it wants to host services. (If we were writing a web appli-
cation, we wouldn’t need to do this—having the configuration in the web.config file
would be enough. But for other application types, we need to do this one last step.)
So we need to add a reference to the System.ServiceModel component—that’s

the main .NET Framework class library DLL for WCF—and we also need to add
using System.ServiceModel; and using ChatServerLibrary; directives to the top of the
Program.cs file in our ChatHost project. We can then write our Main method to look like
Example 13-4.
Example 13-4. Hosting a WCF service
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(ChatService)))
{
host.Open();
Console.WriteLine("Service ready");
Console.ReadKey();
}
}
This creates a ServiceHost object that will make the ChatService available. WCF will
load the configuration from our App.config file to work out how to host it. And we need
to make sure our program hangs around—the service will be available only for as long
as the program that hosts it. So we leave the program running until a key is pressed.
If you want to try this out, you’ll need to make sure the host console application is the
program Visual Studio runs by default—right now it won’t be because the ChatServer
Library is still set as the default. You’ll need to right-click on ChatHost in the Solution
Explorer and select Set as Startup Project. Now pressing F5 will run the program, and
a console window will appear showing the message “Service ready” once the
ServiceHost is ready.
492 | Chapter 13: Networking
If you didn’t delete the App.config file in the ChatServerLibrary project
earlier, you’ll now get an error. Even when you set ChatHost as the
startup application, Visual Studio will still attempt to launch the WCF
Service Host for the ChatServerLibrary project. That would be useful in
a solution that has just a WCF client and a service DLL. It’s unhelpful

here because we end up with two programs trying to host the same server
on the same URL—whichever one gets there second will fail.
If you don’t want to delete the App.config in that project, you can disable
the WCF Service Host by opening the ChatServerLibrary project’s Prop-
erties, going to the WCF Options tab, and unchecking the relevant
checkbox.
Now what? We no longer have the WCF Test Client, because Visual Studio thinks
we’re running a normal console application. Since the default wsHttpBinding for our
service endpoint uses HTTP we could try pointing a web browser at it. Remember, the
service is running on the address in the configuration file:
http://localhost:8732/Design_Time_Addresses/ChatServerLibrary/ChatService/
Strictly speaking, the service isn’t really designed to support a web browser. This chap-
ter is all about enabling programs to communicate with one another, not how to build
web user interfaces. However, WCF is rather generous here—it notices when we con-
nect with a web browser, and decides to be helpful. It generates a web page that pa-
tiently explains that the thing we’ve connected to is a service, and shows how to write
code that could talk to the service. And that’s exactly what we’re going to do next.
Writing a WCF Client
We need to create a client program to talk to our service. Again, to keep things simple
we’ll make it a console application. We’ll add this to the same solution, calling the
project ChatClient. (Obviously, you’ll need to stop the ChatHost program first if you’re
trying this out and it’s still running in the debugger.)
When you right-click on a project’s References item in Visual Studio’s Solution Ex-
plorer, you’re offered an Add Service Reference menu item as well as the normal Add
Reference entry. We’re going to use that to connect our client to our server via WCF.
The Add Service Reference dialog offers a Discover button (shown in Figure 13-6) which
attempts to locate services in your current solution. Disappointingly, if we were to click
it with our code as it is now, it would report that it didn’t find any services. That’s
because we wrote all the hosting code by hand for ChatHost—Visual Studio doesn’t
realize that our console application is hosting services. It usually looks only in web

projects—if we’d hosted the service in an ASP.NET web application, it would have
found it. But with the approach we’re taking here, it needs a little help.
WCF | 493
If you left the App.config
file
in place in the ChatServerLibrary project,
it would find that and would launch the WCF Service Host for you when
you click Discover. But be careful—ChatHost is our real service, and
when we start modifying settings in its App.config (which we’ll do later)
it’s important that the Add Service Reference dialog is talking to the
right service. That’s why we suggested deleting the App.config from the
DLL project earlier—it avoids any possibility of accidentally configuring
your client for the wrong service host.
For Visual Studio to be able to connect to our console-hosted service we need the service
to be up and running before the Add Service Reference dialog is open. The easiest way
to do this is to run the project, without debugging it. Instead of pressing F5, we choose
Debug→Start Without Debugging, or we press Ctrl-F5. This runs the ChatHost program
without debugging, leaving Visual Studio free for other tasks, such as adding a service
reference.
We’ll need the address of the service handy, and since it’s quite long, it’s easiest to open
our host’s App.config and copy the service address to the clipboard. (It’s the
baseAddress attribute in the <host> section.) Then we can go to the ChatClient project
and add a Service Reference. If we paste the address of the service into the Address box
and then click the Go button, after a few seconds we’ll see the Services panel on the
left display a ChatService entry. Expanding this shows an IChatService item repre-
senting the contract, and selecting this shows the one operation available in our con-
tract, PostNote, as Figure 13-6 shows.
While the list of services, contracts, and operations in the Add Service Reference dialog
is useful for verifying that we have the service we wanted, the significance of the infor-
mation here goes a little deeper—it’s part of an important feature of how systems

communicate in WCF. Remember that we defined a contract earlier, to describe the
operations our service provides to its clients. For the client to communicate successfully
with the server, it also needs a copy of that contract. So the best way to think of the
Add Service Reference dialog is that it’s a tool for getting hold of the contract from a
service.
Figure 13-6. Add Service Reference
494 | Chapter 13: Networking
This is the purpose of the metadata exchange entry we saw earlier when we looked at
the configuration Visual Studio generated for our WCF service. Metadata exchange is
just a fancy way of saying that a service provides a way for a client to discover the
contract and related information about the service. The Add Service Reference dialog
uses this information to configure a client application to communicate with the service,
and to provide it with a copy of the contract.
To see the results of this, we’ll finish with this dialog. In the Namespace text box near
the bottom, we’ll type ChatService—Visual Studio will put the contract and any other
types relating to this service into this namespace. When we click OK a Service Refer-
ences item appears in the project in the Solution Explorer, and it will contain an entry
called ChatService. (Now that we’ve done this, we can stop the service host console
window we ran earlier.)
Visual Studio generates some code when adding a service reference. By default, it hides
this, but we can take a look at it. At the top of the Solution Explorer, there’s a toolbar,
and if you hover your mouse pointer over the buttons you’ll find that one has a tool tip
of Show All Files. This button toggles each time you click it. When it’s pressed in, the
ChatService service reference can be expanded, as Figure 13-7 shows.
Figure 13-7. Generated files in a service reference
The most
interesting file in here is Reference.cs, inside the Reference.svcmap item. Inside
this file, near the top, there’s a copy of IChatService—the contract we wrote earlier:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel",
"4.0.0.0")]

[System.ServiceModel.ServiceContractAttribute(
ConfigurationName="ChatService.IChatService"]
public interface IChatService
{
[System.ServiceModel.OperationContractAttribute(
Action=" /> ReplyAction=" /> void PostNote(string from, string note);
}
WCF | 495
It looks a little more complex than the original, because Visual Studio has annotated it
with various attributes, but it’s simply being explicit about the values that WCF fills in
by default.

Aside from these extra details, you can see that it is essentially a copy of
the original contract.
Sharing contracts
You might wonder why we jumped through all these hoops rather than just copying
IChatService from the service project to the client. In fact, that would have worked,
and we could even have written a separate DLL project to define the contract interface
and shared that DLL across the two projects. As you’ll see shortly, Visual Studio gen-
erated a few other useful things for us as part of this Add Service Reference process,
but as it happens, sharing the contract definition directly is sometimes a perfectly rea-
sonable thing to do—you’re not obliged to use metadata exchange.
Of course, you won’t always own the code at both ends. If you need to connect to a
service on the Internet provided by someone else, metadata exchange becomes more
important—it provides a way to get hold of a contract you didn’t write. And since the
metadata exchange mechanisms are standards-based, this can work even when the
service is not written in .NET.
Metadata exchange is not universally supported. In practice, contract
discovery can
happen in all sorts of ways, including (and we’re not

making this up) being faxed a printout showing samples of the messages
the service expects to send and receive.

If you’re getting the contract
through that kind of informal channel, you’ll need to write an interface
(by hand) in your client program to represent the service contract.
The process of metadata import also highlights an important point about service evo-
lution. You might modify the ChatService after the ChatClient has added its reference.
If these modifications involve changing the contract, it’s clear that there’s a problem:
the client’s copy of the contract is out of date. You might think that sharing the interface
directly through a common DLL would be a good way to avoid this problem, but it
might only make the problem harder to see: what if you’ve already deployed a version
of the client? If you then modify the contract the modified code might run fine on your
machine, but if you deploy an update to the service with this changed contract any
copies of the old client out there will now be in trouble because they’re still working
with an old copy of the contract. Explicitly going through the metadata exchange
† In fact, it has revealed a small problem: the tempuri.org
that appears in the URL indicates something
temporary that we’re supposed to fill in—the ServiceContract attribute on the original service definition has
a Namespace attribute, and we’re supposed to pick a URI that is unique to our service. It’s not mandatory in
this particular scenario because everything works with the default, but a temporary-looking URI doesn’t look
entirely professional.
‡ It could be worse. See />.aspx.
496 | Chapter 13: Networking
doesn’t make this problem any easier to solve, of course, but it makes it less likely for
changes to creep in by accident and go undetected. A complete solution to the problem
of service evolution is beyond the scope of this book, so for now, just be aware that
changing a contract should not be undertaken lightly.
Michele Leroux Bustamante’s Learning WCF (O’Reilly) discusses ver-
sioning of service contracts.

Proxy
Looking further through the Reference.cs file generated by adding the service reference,
the next most interesting feature after the contract is a class called ChatServiceClient.
This implements IChatService, because it acts as a proxy for the service. If we want to
communicate with the service, all we need to do is create an instance of this proxy and
invoke the method representing the operation we’d like to perform. So if we add a
using ChatClient.ChatService; directive to the top of Program.cs in ChatClient, we
can then modify its Main method as shown in Example 13-5.
Example 13-5. Invoking a web service with a WCF proxy
static void Main(string[] args)
{
using (ChatServiceClient chatProxy = new ChatServiceClient())
{
chatProxy.PostNote("Ian", "Hello again, world");
}
}
Notice the using statement—it’s important to ensure that you dispose of WCF proxies
when you have finished using them. When the client calls this method on the proxy,
WCF builds a message containing the inputs, and it sends that to the service. Over in
the service (which is running in a separate process, perhaps on a different machine)
WCF will receive that message, unpack the inputs, and pass them to the PostNote
method in the ChatService class.
To try this out, we’re going to need to run both the client and the server simultaneously.
This means configuring the solution in Visual Studio a little differently. If you right-
click on the WcfChat solution in the Solution Explorer and select Set Startup Projects,
the dialog that opens offers three radio buttons. If you select the Multiple Startup
Projects radio button, you can choose which of your projects you’d like to run when
debugging. In this case, we want to change the Action for both the ChatClient and
ChatHost projects from None to Start. (We leave the ChatServerLibrary Action as
None—we don’t need to run that project, because our ChatHost project hosts the server

library.) Also, we want to give the service a head start so that it’s running before the
WCF | 497
client tries to use it, so select ChatHost and click the up arrow next to the list, to tell
Visual Studio to run it first. (In theory, this is not a reliable technique, because there’s
no guarantee that the server will get enough of a head start. In practice, it appears to
work well enough for this sort of debugging exercise.) Figure 13-8 shows how these
settings should look.
Figure 13-8. Starting multiple projects simultaneously
If we
run the program by pressing F5, two console windows will open, one for the client
and one for the service.
If you’re following along, it’s possible that you’ll see an AddressAlrea
dyInUseException with
an error message complaining that “Another ap-
plication has already registered this URL with HTTP.SYS.” This usually
means you have a copy of ChatHost still running—somewhere on your
desktop you’ll find a console window running the service host. Or pos-
sibly, the WCF Service Host is still running. This error occurs when you
launch a second copy of the service because it tries to listen on the same
address as the first, and only one program can receive requests on a
particular URL at any one time.
Visual Studio displays the message in its Output window because of the call to
Debug.WriteLine in PostNote, just like it did when using the WCF Test Client earlier,
verifying that the proxy was able to invoke an operation on the service. (You might
498 | Chapter 13: Networking
need to look carefully to see this—the message can get buried among the various other
notifications that appear in the Output window.)
Notice that in Example 13-5 we didn’t need to tell the proxy what address to use. That’s
because the Add Service Reference dialog imported more than just the contract defi-
nition. It adds information to the ChatClient project’s App.config file, shown in all its

gory detail in Example 13-6.
Example 13-6. Generated client-side App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IChatService"
closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8"
useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true"
inactivityTimeout="00:10:00" enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows"
proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows"
negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>

</bindings>
<client>
<endpoint address="http://localhost:8732/Design_Time_Addresses/
ChatServerLibrary/ChatService/"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IChatService"
contract="ChatService.IChatService"
name="WSHttpBinding_IChatService">
<identity>
<userPrincipalName value="" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
WCF | 499
Like the service configuration we examined earlier, this also has an <endpoint> element
with an address, binding, and contract, although being on the client side, this
<endpoint> appears inside a <client> element instead of a <service> element. The proxy
gets the address from this endpoint definition.
You can provide the proxy with an address from code if you want to. It
offers various constructor overloads, some of which accept a URL. But
if you don’t provide one, it will look in the configuration file.
Notice that the endpoint also has a bindingConfiguration attribute—this refers to a
<binding> element earlier in the file that contains information on exactly how the
wsHttpBinding should be configured. There was nothing like this in the service, because
we were just using the defaults. But the Add Service Reference dialog always generates
a binding configuration entry, even if you happen to be using the defaults.
Our “chat” application is demonstrating the ability for the client to send a note to the
server, but it’s not complete yet. The client needs a couple of extra features. To make

our conversation a bit less one-sided, we should be able to see notes written by other
people. And unless our conversations are all going to be exceptionally brief, we need
to be able to type in more than just one note.
We’ll fix that second problem by modifying the code in Example 13-5. We’ll put the
call to the proxy inside a loop, and we’ll also ask for the user’s name, so we can support
notes from people who may not be called Ian (see Example 13-7).
Example 13-7. Client with input loop
static void Main(string[] args)
{
ChatServiceClient chatProxy = new ChatServiceClient();
Console.WriteLine("Please enter your name:");
string name = Console.ReadLine();
while (true)
{
Console.WriteLine("Type a note (or hit enter to quit):");
string note = Console.ReadLine();
if (string.IsNullOrEmpty(note))
{
break;
}
chatProxy.PostNote(name, note);
}
}
500 | Chapter 13: Networking
We’ll also modify the server so that it prints out the note, rather than sending it to the
debug output—that’ll make it a bit easier to see when notes are coming in. So change
PostNote in ChatService to this:
public void PostNote(string from, string note)
{
Console.WriteLine("{0}: {1}", from, note);

}
If
you run both programs again by pressing F5, the client program will ask you to type
in your name, and will then let you type in as many notes as you like. Each new note
will be sent to the server, and you should see the notes appear in the server console
window.
This is an improvement, but there’s still no way for the client to find out when other
users have typed notes. For this, we’ll need to add bidirectional communication.
Bidirectional Communication with Duplex Contracts
The contract for our chat service is a one-sided affair—it’s all about the notes the client
sends to the server. But WCF supports duplex contracts, which provide a means for the
server to call the client back. (Note that there are some issues with HTTP that can make
duplex communication tricky—see the sidebar on the next page.) A duplex contract
involves two interfaces—as well as an interface that the server implements, we also
define an interface that the client must implement if it wants to use the service. In our
example, the service wants to notify clients whenever any user posts a note. So the
client-side interface, shown in Example 13-8, looks pretty similar to our current server
interface.
Example 13-8. Callback interface for duplex contract
public interface IChatClient
{
[OperationContract]
void NotePosted(string from, string note);
}
Notice that while methods in a callback interface require the usual OperationCon
tract attribute, the interface itself does not need to be marked with ServiceContract.
That’s because this callback interface is not a contract in its own right—it’s one half of
a duplex contract. So we need to modify the existing IChatService to associate it with
this new callback interface (see Example 13-9).
WCF | 501

Duplex Communication, HTTP, and Firewalls
Bidirectional communication is problematic on the Internet today. The vast majority
of computers are behind firewalls. Firewalls are usually configured to reject most in-
coming connections. There will be exceptions for machines such as web servers and
mail servers—administrators set up firewalls to allow certain kinds of traffic through
to such machines—but the default presumption is that any incoming attempts to con-
nect to a service should be blocked unless the firewall has been explicitly told to leave
it open.
This is a good default from a security perspective, because the vast majority of unex-
pected incoming connections are from hackers. Any machine connected directly to the
Internet without a firewall will be subject to a continuous stream of traffic from hackers
looking for machines that they might try to break into. Typical firewall configuration
insulates machines from this stream of attacks, providing an extra line of defense, just
in case you get behind on installing OS updates or some hacker uses a so-called zero-
day attack that exploits a bug which hasn’t yet been fixed.
One problem with this is that it makes bidirectional communication difficult if you’re
using HTTP. HTTP operations can be initiated only by the computer that opened the
connection in the first place—there’s no way to open a connection to a web server and
then wait for it to send a message to you. HTTP is asymmetric, in that nothing happens
until the client sends a request. (The lower-level protocol that HTTP runs on top of
[TCP] is more flexible than this, by the way—that’s one reason for using sockets. Either
party in a TCP connection is free to send data at any time regardless of which end
originally initiated the connection.)
To enable full bidirectional communication over HTTP, you need both ends to be
running an HTTP server. When using duplex communication with WCF in conjunc-
tion with an HTTP-based binding, WCF runs what is effectively a miniature web server
in the client process. Of course, this is only any use if the server is able to connect back
to that client-side mini server.
If both the client and the server are behind the same firewall, that won’t be a problem.
But if the server is on the Internet, publicly accessible to anyone, it almost certainly

won’t be able to connect back to most clients. So the technique that is shown in Ex-
ample 13-8 works only for private networks. To make a chat program that works over
the Internet requires the use of either TCP and sockets, or some slightly hacky HTTP
tricks that are beyond the scope of this book.
The upshot of this is that you’ll want to avoid duplex contracts for Internet-facing
applications.
Example 13-9. Duplex contract
[ServiceContract(
CallbackContract=typeof(IChatClient),
SessionMode=SessionMode.Required)]
public interface IChatService
{
502 | Chapter 13: Networking
[OperationContract]
bool Connect(string name);
[OperationContract]
void PostNote(string note);
[OperationContract]
void Disconnect();
}
By
setting
the ServiceContract attribute’s CallbackContract property, we’ve declared
that this is a duplex contract, and have identified the interface that defines the client
side of the contract. Example 13-9 also makes a couple of other changes that turn out
to be necessary for our service to work as intended: we’ve set the SessionMode property
of the ServiceContract attribute, and we’ve added a couple of extra methods to enable
clients to connect and disconnect. We’ve also removed the string name argument from
PostNote—as you’ll see, this will turn out to be redundant. All of these changes are
related to sessions.

Session-based communication
The ServiceContract attribute’s SessionMode property determines the nature of the re-
lationship between the server and any particular client. By default, the relationship is
presumed to be transient, not necessarily lasting any longer than a single operation.
This reflects the fact that WCF is designed to support web services, and HTTP does
not offer any idea of a connection between the client and the server that lasts longer
than a single request.
It’s true that HTTP allows
a
single TCP connection to be reused across
multiple requests, but this is just a performance optimization, and noth-
ing is allowed to depend on it. Either the client or the server is free to
close the connection at the end of a request, forcing a new one to be
established for the next operation, without changing the semantics of
the operations. (And even if the client and server both want to keep the
connection alive between requests, a proxy in the middle is free to over-
rule them.) Logically speaking, each HTTP request is completely disas-
sociated from the ones that came before or after.
This connectionless behavior is very useful for scalability and robustness—it means
you can load-balance across large numbers of web servers, and it doesn’t greatly matter
whether all of a particular client’s requests are handled by the same machine. It’s often
possible to take a single machine in a web farm out of service without disrupting any
of the clients. However, the absence of connections is sometimes unhelpful—some
applications need some sort of session concept. For example, it would be annoying to
have to type in your username and password every time you move from one page to
another in a website—once you’ve logged in to a website, you want it to remember
WCF | 503
who you are. Likewise, if our chat application is going to be able to call clients back to
notify them that notes have arrived, that implies that the application needs to know
which clients are currently connected.

Although HTTP has no standard way to represent a session, various ad hoc systems
have been developed to add such a feature. Websites typically use cookies. (Cookies
are not part of the HTTP specification, but they are supported by all popular web
browsers. Some users disable them, though, so they’re not necessarily universally avail-
able.) The web service standards supported by WCF prefer a slightly different
solution—it’s similar to how cookies work, but it puts the relevant information in the
messages being sent, rather than in the HTTP headers.
§
Since our contract is now duplex, it requires the ability to maintain a connection be-
tween each client and the server. We tell WCF this by setting the SessionMode property
to SessionMode.Required. Note that this doesn’t actually switch on sessions; it merely
says that anything that wants to communicate using this contract had better do so with
sessions enabled. Remember, the contract is separate from implementations that con-
form to the contract. The effect of this setting is that WCF will produce an error if you
try to use this contract without enabling sessions; we’ll see how to enable sessions by
modifying the client and server configuration files once we’ve finished modifying the
code.
A session will be established the first time a client connects to a service, which presents
our application with another problem. WCF won’t send a message until it has some-
thing to send, so our chat client will first connect to the service when we send our first
note. (Creating an instance of the ChatServiceProxy does not connect—nothing goes
over the network until the first time you try to invoke an operation.) But we want clients
to be able to receive notes straight away, without being required to post one first. So
we need a way for clients to announce their presence to the service without sending a
note. That’s why Example 13-9 adds a Connect method. And we’ve also provided a
Disconnect method for clients to announce that they are leaving so that the chat server
doesn’t attempt to send notes to clients that are no longer there. (Without this, the
server would get an exception the next time it tried to send a message. Although it
would notice that the clients had gone, an explicit disconnect is a bit neater—it also
makes it possible to tell the difference between users who deliberately leave the con-

versation and users who get cut off due to problems.)
We now need to update the server to implement the modified contract, and to track
the clients.
§ In general, the WS-* family of web service protocols avoids depending on HTTP. This may seem like a peculiar
tendency for web service standards, but a lot of the organizations involved in creating these specifications
wanted the message formats to be useful in message-queue-based systems as well as HTTP. So in general,
they tend to avoid transport-specific mechanisms.
504 | Chapter 13: Networking
Calling the client from the server
Our service is going to need to maintain a list of connected clients so that it can notify
every client when it receives each note. We can store the list as private data in our service
class, but since that one list needs to be available across all sessions, we need to tell
WCF that we only ever want it to create one instance of that class.
WCF offers several different modes for creating instances of your service class. It can
create one per client session—that’s useful when you want per-session state. But in our
case, all notes get sent to everyone, so the only interesting state is global. Since our
application state is global, we don’t have much use for per-client instances here. WCF
can also create a new instance of your service class for every single request—if you don’t
hold any state in the service class itself this is a reasonable thing to do. But in our case,
we want one instance for the lifetime of the service. We can indicate this like so:
[ServiceBehavior(
InstanceContextMode=InstanceContextMode.Single,
ConcurrencyMode=ConcurrencyMode.Reentrant)]
public class ChatService : IChatService
{
We added a ServiceBehavior attribute to the code to specify this single-instance be-
havior. Notice that we also asked for a ConcurrencyMode of Reentrant. This tells WCF
to have our service work on requests for only one session at a time—if requests from
multiple clients come in simultaneously, WCF will service them one after another. This
is convenient as it means that as long as any single client does only one thing at a time,

we don’t need to write any code to ensure the thread safety of our state handling.
An alternative to the single-instance context mode would have been to
store our
state in a static field. This would share the data across all
clients, which is what we need. But then we’d be on our own for thread
safety. The ConcurrencyMode property applies only to any particular in-
stance of the service, so if you don’t choose the single-instance mode,
WCF will let different instances of your service execute simultaneously.
In practice, real applications are likely to need to do their own thread
synchronization. Here we’re relying on clients making only one call at
a time, which might work in a small, controlled example but is a risky
thing to do if you don’t completely trust your client machines. (Even
with only one session at a time, a single client session could invoke
multiple operations simultaneously.) You may be wondering why we
didn’t use ConcurrencyMode.Single, which enforces a completely strict
one-at-a-time model. Unfortunately, that turns out to prevent you from
calling back into clients while you’re in the middle of handling a call
from a client—a blocking outbound call from a nonreentrant single-
threaded context presents an opportunity for deadlocks, so WCF for-
bids it.
WCF | 505
Next, we’ll add a field to hold the state—a collection of currently connected clients:
private Dictionary<IChatClient, string> clientsAndNames =
new Dictionary<IChatClient, string>();
This is a dictionary where the key type is the client callback interface we defined earlier.
The value is the client’s name. To see how this gets used, here’s the Connect
implementation:
public bool Connect(string name)
{
if (clientsAndNames.ContainsValue(name))

{
// Name already in use, so refuse connection
return false;
}
IChatClient clientCallback =
OperationContext.Current.GetCallbackChannel<IChatClient>();
// clientsAndNames is shared state, but we're not locking
// here, because we're relying on ConcurrentMode.Reentrant
// to give us messages one at a time.
clientsAndNames.Add(clientCallback, name);
Console.WriteLine(name + " connected");
return true;
}
The first thing we do is check that the username is unique. Now that we’re maintaining
a list of connected clients, we’re in a position to prevent multiple users from picking
the same name. If a new user is trying to sign up with a duplicate name, we return
false. (A return code here makes more sense than an exception because this isn’t really
an exceptional condition.)
If the name looks OK, we retrieve the client callback interface with the following
expression:
OperationContext.Current.GetCallbackChannel<IChatClient>()
OperationContext is a WCF class whose Current property provides information about
the operation that your code is handling right now. One of the services it provides is
the ability to retrieve the callback interface when a duplex contract is in use.
GetCallbackChannel returns a proxy object similar to the one the client uses to talk to
the service, but this proxy goes in the other direction—it invokes operations on the
client that called our Connect method. We just add this to the dictionary of connected
clients, associating it with the client’s chosen name, and then return true to indicate
that we’re happy that the user’s name wasn’t previously in use and that we have ac-
cepted the user’s connection.

506 | Chapter 13: Networking
Next, let’s look at the modified PostNote:
public void PostNote(string note)
{
IChatClient clientCallback =
OperationContext.Current.GetCallbackChannel<IChatClient>();
string name = clientsAndNames[clientCallback];
Console.WriteLine("{0}: {1}", name, note);
// ToArray() makes copy of the collection. This avoids an
// exception due to the collection being modified if we have
// to disconnect a client part way through the loop.
KeyValuePair<IChatClient, string>[] copiedNames =
clientsAndNames.ToArray();
foreach (KeyValuePair<IChatClient, string> client in copiedNames)
{
// Avoid sending the message back to the client that just sent
// it - they already know what they just typed.
if (client.Key != clientCallback)
{
Console.WriteLine("Sending note to {0}", client.Value);
try
{
client.Key.NotePosted(name, note);
}
catch (Exception x)
{
Console.WriteLine("Error: {0}", x);
DisconnectClient(client.Key);
}
}

}
}
Again,
we begin by retrieving the callback interface for the current client. Remember,
our chat server will usually have multiple clients attached, and this lets us discover
which particular one is sending a note. The next line looks up the callback interface in
the dictionary to find out what name this user originally passed to Connect—this is why
we were able to remove the argument we previously had on this method in which the
caller passed her name. We remember her name from before—we have to remember
it to guarantee uniqueness—and since we’re remembering it, there’s no need to make
the client pass in the name every single time.
This code then iterates through all the connected clients in the clientsAndNames dic-
tionary, to deliver the new note to each client. It calls the NotePosted on the proxy.
Notice that we wrapped this in exception-handling code. If a client becomes inacces-
sible because of a network failure, a crash, a machine failure, or a programming error
that caused it to exit without remembering to call Disconnect, the proxy’s NotePosted
method will throw an exception. Our code catches this and removes the client from
the list, to avoid trying to send it any more notes.
WCF | 507
This code is a little simplistic, for two reasons. First, we might want to
be a little more lenient with errors—perhaps we should give the client
a chance to recover before giving up on it entirely. One way to do this
would be to have a second collection of connections to act as a kind of
sin bin—you could give failed clients another chance after a certain
amount of time. (Another strategy would be to require that the client
attempt to reconnect in the event of a failure, in which case the server’s
error handling is just fine as it is.)
Second, calling each client in turn using a loop will perform poorly as
the number of clients gets large, or if some clients are on slow connec-
tions. This code will be OK for small groups on a private network, but

for a larger scale, an asynchronous approach would work better. WCF
provides full support for asynchronous use of proxies, but the chapter
on threading and asynchronous programming is coming later, so we
can’t show you that just yet.
The code to disconnect clients is in a separate method, because it’s shared by the error-
handling code and the Disconnect method that’s part of the new contract. Here’s the
common code:
private void DisconnectClient(IChatClient clientCallback)
{
string name = clientsAndNames[clientCallback];
Console.WriteLine(name + " disconnected");
clientsAndNames.Remove(clientCallback);
}
This just removes the client from the dictionary. This makes the Disconnect method
very simple:
public void Disconnect()
{
IChatClient clientCallback =
OperationContext.Current.GetCallbackChannel<IChatClient>();
DisconnectClient(clientCallback);
}
Once again, we get hold of the callback interface, and then call the same disconnection
helper as the error-handling code.
We have one more modification to make on the server: the wsHttpBinding we’re using
doesn’t support the duplex behavior we require, so we need to modify the ChatHost
program’s configuration.
Server configuration for duplex and sessions
As we mentioned earlier, WCF lets us change the communication mechanism we’re
using by configuring a different binding. We don’t need to change any code to do this.
We just need to modify our host project’s App.config file, specifically the <endpoint> tag:

508 | Chapter 13: Networking
<endpoint address=""
binding="wsHttpBinding"
contract="ChatServerLibrary.IChatService">
</endpoint>
We
change
that binding attribute’s value to wsDualHttpBinding. This binding is very
similar to wsHttpBinding; it just adds support for callbacks. It also enables sessions
automatically. (Sessions are available with wsHttpBinding, but they are off by default,
so you’d need to add further configuration to switch them on if you wanted sessions
without duplex communication.)
Our server is now ready to work in duplex mode, so next we need to update the client.
Duplex client
We’ve made several changes to the contract: we modified the one existing method,
added two new methods, and turned it into a duplex contract. We also changed the
binding. Any one of these changes would need the client to be updated, because each
has an impact on the work done by the Add Service Reference operation. (All these
things change the contract, the configuration, or both.) However, we don’t need to
completely redo the work of adding the service reference. If you right-click on an item
in a client’s Service References in the Solution Explorer, you’ll see an Update Service
Reference item. This modifies the generated source code and application configuration,
saving you from having to build it all again from scratch. This refetches the metadata,
so the service needs to be running when you do this, just as when adding the reference
in the first place.
Once we’ve updated the reference, rebuilding the solution now produces two compiler
errors. The call to PostNote fails, because we’re passing in two arguments where the
new contract requires only one. And we also see the following error on the line where
we construct the ChatServiceClient proxy:
error CS1729: 'ChatClient.ChatService.ChatServiceClient' does not contain

a constructor that takes 0 arguments
Because the service now has a duplex contract, the generated proxy insists that the
client implement its half of the contract—we need to provide an implementation of the
callback interface and pass that to the proxy. Example 13-10 shows a straightforward
implementation of the interface.
Example 13-10. Implementing the client-side callback interface
[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
class ChatCallback : IChatServiceCallback
{
public void NotePosted(string from, string note)
{
Console.WriteLine("{0}: {1}", from, note);
}
}
WCF | 509
The callback interface seems to have changed names. We called it IChat
Client on the server, but here it’s IChatServiceCallback. This is the
normal if slightly surprising behavior when using metadata exchange
through Visual Studio’s Add Service Reference feature. It’s nothing to
worry about. As far as WCF is concerned, a contract has only one name
(IChatService in this case), even when it happens to be split into server-
side and client-side pieces. WCF considers the name of the client-side
interface to be irrelevant, and doesn’t advertise it through metadata ex-
change. When you add or update a reference to a service with a duplex
contract, Visual Studio just makes up the client-side interface name by
appending Callback to the contract name.
Notice the CallbackBehavior attribute—it specifies a ConcurrencyMode just like on the
server. Again, we’ve specified Reentrant—this means that this particular callback han-
dler expects to be dealing with just one session at a time, but can cope with being called
back by the server while it’s waiting for the server to do something. We need this so

that the server can send notifications to the client inside its PostNote implementation.
We need to provide WCF with an instance of this callback implementation, so we
modify the code at the start of Main from Example 13-7 that creates the proxy:
ChatCallback callbackObject = new ChatCallback();
InstanceContext clientContext = new InstanceContext(callbackObject);
ChatServiceClient chatProxy = new ChatServiceClient(clientContext);
This wraps the callback object in an InstanceContext—this represents the session, and
is essentially the client-side counterpart of the object returned by OperationContext.Cur
rent on the server. It provides various utility members for managing the session, but
here the only thing we need it for is to pass our callback object to the proxy—the proxy
won’t take the callback directly and demands that we wrap it in an instance context.
We have a few more modifications to make. Remember that the client now needs to
tell the server that it wants to connect, so we can do that directly after asking for the
user’s name:
Console.WriteLine("Please enter your name:");
bool ok = false;
while (!ok)
{
string name = Console.ReadLine();
ok = chatProxy.Connect(name);
if (!ok)
{
Console.WriteLine("That name is taken. Please try another.");
}
}
This checks the return code to see if the name we entered was already in use, and asks
for a different name if it was. The end user can go through the relevant legal procedures
to change her name, and then try again.
510 | Chapter 13: Networking
The line that calls PostNote no longer needs to pass our name each time, because the

server now remembers our name based on our session:
chatProxy.PostNote(note);
And finally, we should add a line of code at the very end of Main to let the server know
we’re going away:
chatProxy.Disconnect();
We’re now ready to test the application. We can run the client and service as before,
but we want an extra client or two, to test out this multiuser chat service. Visual Studio
doesn’t provide a way to debug two instances of the same application, so we need to
run the extra instances manually. We can do this by finding the folder where the com-
piled program lives. This will be in a subfolder of the project folder—the program will
be in a bin\debug subfolder. Running a couple of instances of the client we can type in
some different names, and we see notes appear in the service’s console window as the
users connect:
Service ready
Ian connected
Matthew connected
When we type a note in one of the clients, it appears in all of the client console windows,
as well as the server.
Our application’s user interface has a long way to go before it’ll become the new live
chat tool of choice, but we have now demonstrated a complete, if rather basic, WCF-
based application. We have only scratched the surface of WCF, of course—it’s a large
enough technology to warrant a book in its own right. Learning WCF, a book we already
mentioned a couple of times, is a good choice if you’d like to learn more about what
WCF can do. Next, we’re going to look at how to work directly with HTTP.
HTTP
The .NET Framework class library provides various classes for working directly with
HTTP. Some of these are for client scenarios, and are useful when you need to fetch
resources from a web server such as bitmaps, or if you need to use an HTTP-based
service that WCF cannot easily work with. You can also provide server-side HTTP
support. You would normally do that by writing an ASP.NET web application, which

we’ll look at in a later chapter. But there is a class that enables other program types to
receive incoming HTTP requests, called HttpListener. (We won’t be covering that, and
we mention it mainly for completeness—it’s more normal to use ASP.NET, to which
we have devoted a whole chapter.)
HTTP | 511
WebClient
The most common starting point for client-side HTTP code is the WebClient class in
the System.Net namespace. It offers a few ways of working with HTTP, starting from
very simple but inflexible methods, through to relatively complex mechanisms that give
you complete control over detailed aspects of HTTP. We’ll start with the simplest ones.
Although the examples in this section are HTTP-based, WebClient sup-
ports other protocols, including https:, ftp:, and file: URLs. It is ex-
tensible, so in principle you can adapt it to support any protocol that
has a URL scheme.
Downloading resources
Example 13-11 illustrates one of the simplest ways of using the WebClient class. We
construct an instance, and then use its DownloadString method to fetch data at a par-
ticular URL. (You can specify the URL as either a string or a Uri object.)
URLs, URIs, and the Uri Class
HTTP resources
are identified by Uniform Resource Locators (URLs), strings which
contain enough information for a computer to work out where to find the resource.
The specification for URLs defines them as being a special kind of Uniform Resource
Identifier (URI). A URI is a slightly more general idea—URIs give something a name,
and that name may or may not have anything to say about where the resource can be
found. All URLs are URIs, but only URIs that indicate a resource’s location are URLs.
These two kinds of identifiers have a common syntax, so .NET provides just one class
to deal with them: the Uri class, which is defined in the System namespace. It defines
helper properties that give you access to the various parts of the URI. Consider this
example:

Uri blog =
new Uri(" />This represents the URL for one of the authors’ blogs. Its Scheme property’s value is
"http", its Host is "www.interact-sw.co.uk“, and there are properties for all the other
syntactic elements found in URIs.
Methods and properties in the .NET Framework class library that require a URL will
have a signature that accepts a Uri object. (Some APIs also offer overloads that accept
a string.)
Incidentally, there’s a peculiarly persistent and mistaken belief that the plural of URI
is URI. (Apparently this is on the basis that some Latin words have plurals that end in
i, but that conclusion requires an almost heroic misunderstanding of etymology.) Sir
Tim Berners-Lee calls them URIs and he would know, since he invented them (and,
not coincidentally, invented the World Wide Web too).
512 | Chapter 13: Networking
Example 13-11. Fetching content with WebClient
WebClient client = new WebClient();
string pageContent = client.DownloadString(" />Console.WriteLine(pageContent);
Of
course, DownloadString succeeds only if the URL you’re fetching happens to contain
textual content. The URL in Example 13-11 is an HTML web page, which is a text-
based format, so it works just fine, but what if you’re fetching a bitmap, or a ZIP? In
that case, there’s DownloadData, which works in the same way, except it returns an array
of bytes instead of a string:
byte[] data =
client.DownloadData(" />There’s a third easy method for fetching data, DownloadFile. This downloads the re-
source into a local file:
client.DownloadFile(" @"c:\temp\oreilly.html");
These three methods will block—they don’t return until they have finished fetching the
data you asked for (or they have tried and failed, in which case they’ll throw some kind
of exception). This could take awhile. You might be on a slow network, or talking to
a busy server, or just downloading a particularly large resource. If you’re building a

GUI, it’s a bad idea to call blocking APIs.

Fortunately, WebClient offers asynchronous
versions of all these methods. You use these by attaching an event handler to the rele-
vant completion event, for example:
client.DownloadFileCompleted += OnDownloadComplete;
client.DownloadFileAsync(new Uri (" @"c:\temp\");

static void OnDownloadComplete(object sender, AsyncCompletedEventArgs e)
{
MessageBox.Show("Download complete");
}
The DownloadXxxAsync methods all return straight away. WebClient raises the relevant
DownloadXxxCompleted event once the data has been fetched. (This means that you’ll
need to ensure that your application hangs around long enough for that to happen; if
you were to use these asynchronous techniques in a console application, you’d need
to take steps to make sure the program doesn’t exit before the work completes.) Of
course, DownloadStringAsync and DownloadDataAsync cannot provide the fetched data
as a return value, unlike their blocking counterparts, so they provide it as the Result
argument of their completion event argument.
‖ If
it’s a multithreaded application, it’s usually OK to call a blocking API on a worker thread. It’s a bad idea
only if you’re on the UI thread, but that’s the thread that all the interesting UI stuff happens on, so it’s an
easy mistake to make.
HTTP | 513
If you’re writing a Silverlight client, you’ll find that WebClient offers
only the asynchronous versions. And in general, that’s true of all of Sil-
verlight’s networking support—since Silverlight is designed just for
building user interfaces, it doesn’t even offer you the blocking forms.
As well as providing completion event notifications, WebClient also offers progress no-

tifications through its DownloadProgressChanged event. This is raised from time to time
during asynchronous downloads, regardless of which of the three methods you used.
It provides two properties, BytesReceived and TotalBytesToReceive, which tell you how
far the download has gotten and how far it has to go.
If you use these asynchronous methods in a GUI built with either WPF
or Windows Forms, you don’t need to worry about threading issues. As
you’ll see in later chapters, that is not true for all asynchronous APIs,
but these automatically take care of UI threading for you—as long as
you start asynchronous operations from the UI thread, WebClient will
raise completion and progress events on the UI thread.
Uploading resources
WebClient offers the UploadString, UploadData, and UploadFile methods. These corre-
spond directly to the DownloadString, DownloadData, and DownloadFile methods, but
instead of fetching data with an HTTP GET, they send data to the server, typically using
an HTTP POST, although overloads are available that let you specify other verbs, such
as PUT.
Stream-based uploads and downloads
Lots of APIs in the .NET Framework work with the Stream abstraction defined in the
System.IO namespace. The XML classes can load data from a Stream, or write data into
one, for example. The bitmap decoding and encoding classes in WPF can also work
with streams. The first three lines of Example 13-12 obtain a stream for an Atom
feed
#
from a WebClient and use it to initialize an XDocument. The code then uses LINQ
to XML to extract the list of titles and links advertised by this particular feed.
Example 13-12. From HTTP to LINQ to XML via a Stream
WebClient client = new WebClient();
Stream feedStm = client.OpenRead(" />XDocument feedXml = XDocument.Load(feedStm);
string ns = " />var entries = from entryElement in feedXml.Descendants(XName.Get("entry", ns))
#Atom

is a common format for representing sets of items, such as blog entries or news articles. It’s similar to
RSS, but tries to avoid some of RSS’s inconsistencies and limitations.
514 | Chapter 13: Networking
select new
{
Title = entryElement.Element(XName.Get("title", ns)).Value,
Link = entryElement.Element(XName.Get("link", ns)).
Attribute("href").Value
};
foreach (var entry in entries)
{
Console.WriteLine("{0}: {1}", entry.Title, entry.Link);
}
For
sending
data there’s an OpenWrite method. With HTTP or HTTPS, this defaults to
POST, but as with the Upload methods, you can call an overload that takes the verb as
well as the URL.
You can use streams asynchronously. Following the same pattern as the other methods
we’ve looked at so far, you’ll find OpenReadAsync and OpenWriteAsync methods, with
corresponding completion events. But streams add an extra dimension: the Stream ab-
stract base class also offers both synchronous and asynchronous operation. For exam-
ple, if you’re reading data, you can call either Read or BeginRead. You are free to use the
Stream in either mode, regardless of whether you obtained it from the WebClient syn-
chronously or asynchronously. But bear in mind that if you are trying to avoid blocking
in order to keep your user interface responsive, you’ll most likely want to get hold of
the stream asynchronously (e.g., use OpenReadAsync) and use the stream asynchro-
nously. When you open a stream asynchronously, the completion notification tells you
that the WebClient is ready to start reading (or writing) data, but that’s no guarantee
that you’ll be able to finish reading data immediately. For example, if you use

OpenReadAsync to fetch a 1 GB file by HTTP, WebClient won’t wait until it has down-
loaded the whole 1 GB before giving you a stream. You’ll get an OpenReadCompleted
event when it has begun to fetch data so that you can start processing it straight away,
but if you try to read data from the stream faster than your network connection can
download it, you’ll be made to wait. So if you want nonblocking behavior for the whole
download, you’ll need to use the Stream asynchronously too.
While the asynchronous methods offered by WebClient will
call you back
on the correct thread in a GUI application, the asynchronous stream
methods will not, and you’ll have to deal with threading issues yourself.
The WebClient class’s most powerful mechanism is accessed through its GetWebRe
quest and GetWebResponse methods. But these turn out to be wrappers around another
set of classes altogether—WebClient just provides these wrappers as convenient helpers.
So we’ll move on to the classes that do the real work for these methods.
HTTP | 515

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×