Programming C# 4.0 phần 7 pdf

86 423 0
Programming C# 4.0 phần 7 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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="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 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 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 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="ian@idg.interact" /> </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 [...]... becomes inaccessible 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 | 5 07 This code is a little simplistic, for two reasons First, we might want to be... rarely used In practice, basic authentication over HTTPS seems to be the popular choice For either kind of authentication, you specify the username and password in the way shown in Example 13- 17 Example 13- 17 Providing credentials for basic or digest authentication HttpWebRequest request = (HttpWebRequest) WebRequest.Create("https://intraweb/"); request.Credentials = new NetworkCredential("user1",... 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 CS 172 9: '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... 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);... there’s an asynchronous version of this But WebRequest doesn’t support the event-based pattern that WebClient uses Instead, it uses the more complex but slightly more flexible methodbased Asynchronous Programming Model, in which you call BeginGetRequestStream, passing in a delegate to a method that the request will call back once it’s ready to proceed, at which point you call EndGetRequestStream This... asynchronously Example 13-15 Obtaining a response asynchronously HttpWebRequest req = (HttpWebRequest) WebRequest.Create("http://oreilly.com/"); req.BeginGetResponse(delegate(IAsyncResult asyncResult) { HTTP | 5 17 using (HttpWebResponse resp = (HttpWebResponse) req.EndGetResponse(asyncResult)) using (Stream respStream = resp.GetResponseStream()) using (StreamReader reader = new StreamReader(respStream)) { string... 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 errorhandling code and the Disconnect method that’s part... to refer to the local machine, so if you run Example 13-22, you’ll see that it prints out two addresses, one for IPv6 and one for IPv4: Type: InterNetworkV6, Address: ::1 Type: InterNetwork, Address: 1 27. 0.0.1 For years, IPv4 was the only IP version in use, so it’s often not qualified with a version number, which is why this IPv4 address’s AddressFam ily property is just displayed as InterNetwork, and . name="WSHttpBinding_IChatService" closeTimeout=" ;00 :01 :00 " openTimeout=" ;00 :01 :00 " receiveTimeout=" ;00 : 10: 00& quot; sendTimeout=" ;00 :01 :00 " bypassProxyOnLocal="false". maxArrayLength="163 84& quot; maxBytesPerRead=" ; 40 96" maxNameTableCharCount="163 84& quot; /> <reliableSession ordered="true" inactivityTimeout=" ;00 : 10: 00& quot; enabled="false". professional. ‡ It could be worse. See http://www.neopoleon.com/home/blogs/neo/archive/ 200 3 /09 /29/ 545 8 .aspx. 49 6 | Chapter 13: Networking doesn’t make this problem any easier to solve, of course,

Ngày đăng: 06/08/2014, 09:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan