Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Programming C# 4.0 phần 7 pdf
Nội dung xem thử
Mô tả chi tiết
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. Listening 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, containing a <serviceBehaviors> element which contains a <behavior> element. This section 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> elements’ behaviorConfiguration attributes at that behavior, reducing clutter in your configuration 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 exchange 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, because 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 temporarily 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 configuration 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 application, 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 Properties, 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 chapter 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 connect with a web browser, and decides to be helpful. It generates a web page that patiently 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 Explorer, 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 representing the contract, and selecting this shows the one operation available in our contract, 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 information 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 References 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="http://tempuri.org/IChatService/PostNote",
ReplyAction="http://tempuri.org/IChatService/PostNoteResponse")]
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 generated 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 reasonable 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 evolution. 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 http://www.neopoleon.com/home/blogs/neo/archive/2003/09/29/5458
.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 versioning 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 rightclick 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 application 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 possibly, 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