|
|
Browse by Tags
All Tags » Samples (RSS)
-
Jesse Liberty has posted a new tutorial demonstrating how to build a web service and Silverlight client application on top of a SQL data store . This shows off a number of features including LINQ, WCF, and Silverlight controls. Here are the major steps covered; some of the basic application functionality is recycled from earlier tutorials. Using a Silverlight web application project to create a solution with client and service projects Generating a LINQ mapping from your SQL tables Creating a web service to serve the data Creating a web service proxy in a Silverlight application Designing the Silverlight application interface Making the web service calls asynchronous Read More...
|
-
As part of the MySpace session at MIX they've put online the code for a sample called RESTchess . RESTchess is a WCF REST application that mimics a lot of the developments behind the WCF implementation of the MySpace developer API. If you're interested in getting details, then Vittorio Bertocci has a more extensive description of RESTchess . If you're just interested in poking at the code, then the link above is all you need to get started together with the MIX session videos I posted earlier. Read More...
|
-
Let's build on a few earlier samples to actually demonstrate a working call context initializer. I'll start with yesterday's skeleton for a call context initializer and behavior . To that skeleton I'll add implementations of BeforeInvoke and AfterInvoke that initialize the operation thread with custom culture information and then clean the thread up once the operation return. class CultureInitializer : ICallContextInitializer { CultureInfo newInfo; public CultureInitializer(CultureInfo newInfo) { this .newInfo = newInfo; } public void AfterInvoke( object correlationState) { Thread.CurrentThread.CurrentCulture = correlationState as CultureInfo; } public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message) { CultureInfo oldInfo = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = newInfo; return oldInfo; } } Then, I'll fill out ApplyDispatchBehavior to apply my call context initializer to every operation on the given endpoint. class CultureInitializerBehavior : IEndpointBehavior { CultureInfo newInfo; public CultureInitializerBehavior(CultureInfo newInfo) { this .newInfo = newInfo; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations) { operation.CallContextInitializers.Add( new CultureInitializer(newInfo)); } } public void Validate(ServiceEndpoint endpoint) { } } Finally, I'll take last week's custom fault encoding sample and install my behavior. Notice that the only change at the service level is to add the behavior to the endpoint behavior collection. If I had written the code to apply the behavior in an attribute or configuration file, either of those approaches would have worked as well. [ServiceContract] public interface IMyService { [OperationContract] Message Fail(); } public class MyService : IMyService { public Message Fail() { XmlDocument document = new XmlDocument(); document.LoadXml( "<tag attributeName=\"value\"><moretags>blah</moretags></tag>" ); throw new FaultException<XmlElement>(document.FirstChild as XmlElement); } } public class Program { static void Main( string [] args) { string address = "http://localhost:8000/" Read More...
|
-
DinnerNow is a sample restaurant marketplace application that demonstrates many different Microsoft web service technologies. You may have seen the application distributed as a standalone sample as well as appearing in many hands-on labs and booth demos for Microsoft developer conferences. If you've taken a look at DinnerNow in the past, then you might want to check out the new DinnerNow 2.5 release that's been updated for Orcas and Visual Studio 2008. In addition to the new platforms, there are several new features being demonstrated as well: Integration between WCF and WF in Orcas WCF Orcas Syndication API Hosting WCF services in IIS7 WCF on the .NET Compact Framework 3.5 LINQ to SQL Read More...
|
-
It feels like there have been a lot of these announcement posts lately, but that's because a lot of software is getting released. This online release of the Orcas samples should take care of everything relevant to WCF developers that has come out in the last week. My understanding is that you should use the online version of the samples in preference to other sources, such as the Orcas install download. Orcas WCF Technology Samples There's no download page for the complete sample collection, but this link should take you directly to the samples installer . This is the installer for all of the WCF samples. You don't have to download each sample individually. There are quite a few readme issues related to the samples. You can get all of the details about these issues from my earlier post on the Orcas release . Read More...
|
-
The final pieces needed for the ROT 128 sample are a stream upgrade initiator and a stream upgrade acceptor. The initiator starts the upgrade process by providing an upgrade type string from GetNextUpgrade. I've coded this so that the initiator and acceptor share a type string that is stored as a static member back on the binding element . You can produce or store your upgrade type string however you want. This is an opaque string to the runtime. using System; using System.IO; using System.ServiceModel; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { class InitiateAsyncResult : TypedCompletedAsyncResult<Stream> { internal InitiateAsyncResult(Stream stream, AsyncCallback callback, object state) : base (stream, callback, state) { } } class ROT128StreamUpgradeInitiator : StreamUpgradeInitiator { ROT128StreamUpgradeProvider provider; string nextUpgrade = ROT128StreamUpgradeBindingElement.ROT128UpgradeType; internal ROT128StreamUpgradeInitiator(ROT128StreamUpgradeProvider provider, EndpointAddress remoteAddress, Uri via) : base () { this .provider = provider; } public override IAsyncResult BeginInitiateUpgrade(Stream stream, AsyncCallback callback, object state) { return new InitiateAsyncResult( new ROT128Stream(stream), callback, state); } public override Stream EndInitiateUpgrade(IAsyncResult result) { return ((InitiateAsyncResult)result).Data; } public override string GetNextUpgrade() { string result = nextUpgrade; nextUpgrade = null ; return result; } public override Stream InitiateUpgrade(Stream stream) { return new ROT128Stream(stream); } } } Each time that GetNextUpgrade is called, you're expected to provide a different upgrade type string if you support multiple upgrades. Once you're out of upgrade types to suggest, GetNextUpgrade should return null forever afterwards. This sample only supports a single upgrade type. At some point in the future, after the upgrade is accepted, you'll get a call on InitiateUpgrade to actually perform the Stream transformation. There's no type string given to InitiateUpgrade so, implicitly, calling InitiateUpgrade means to perform the upgrade for the last upgrade type that you passed out of GetNextUpgrade. To get to the point where you're transforming Streams, you first need to make it through the upgrade acceptor. The upgrade acceptor takes an upgrade type string argument to the CanUpgrade method and returns whether it recognizes this upgrade type. If CanUpgrade returns false, then the Read More...
|
-
Last time, we built the binding element for the stream upgrade sample . The job of the binding element was to stash itself away in the binding context so that the transport could later pull out the stream upgrade and build the provider. This time we'll look at the implementation of the stream upgrade provider. The stream upgrade provider needs to do three things: Build the upgrade initiator for the client side of the connection. The initiator gets the remote address and via of the connection so that we can differentiate behavior based on where the connection is actually going. Build the upgrade acceptor for the server side of the connection. The acceptor and initiator pieces are the first time we have asymmetry in this simple example. Fundamentally, the initiator and acceptor use a request-reply exchange regardless of the shape of the contract or connection. This means that you're always going to get asymmetry at least at this level. Run any part of the protocol that happens independently from making the individual connections. These actions go in the Open, Close, and Abort methods of the provider. I've given all of these empty implementations because this sample doesn't need to do anything here. If you had resources allocated at pre-connect time, such as a socket connection, you would do the allocation and deallocation in these methods. using System; using System.ServiceModel; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { class OpenAsyncResult : CompletedAsyncResult { internal OpenAsyncResult(AsyncCallback callback, object state) : base (callback, state) { } } class CloseAsyncResult : CompletedAsyncResult { internal CloseAsyncResult(AsyncCallback callback, object state) : base (callback, state) { } } class ROT128StreamUpgradeProvider : StreamUpgradeProvider { public ROT128StreamUpgradeProvider(ROT128StreamUpgradeBindingElement element, BindingContext context) : base (context.Binding) { } public override StreamUpgradeAcceptor CreateUpgradeAcceptor() { return new ROT128StreamUpgradeAcceptor( this ); } public override StreamUpgradeInitiator CreateUpgradeInitiator(EndpointAddress remoteAddress, Uri via) { return new ROT128StreamUpgradeInitiator( this , remoteAddress, via); } protected override void OnAbort() { } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return new CloseAsyncResult(callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback Read More...
|
-
Building a stream upgrade for ROT 128 starts with creating a binding element to put in the channel stack. This binding element extends the special StreamUpgradeBindingElement base class , which functions very similarly to the specialized binding element base classes for transports and message encoders. We then need to override the channel factory and listener build processes because the stream upgrade binding element does not actually generate a channel. Stream upgrades are handled internally by the transport if it supports them. using System; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { public class ROT128StreamUpgradeBindingElement : StreamUpgradeBindingElement { internal static string ROT128UpgradeType = "application/rot128" ; public ROT128StreamUpgradeBindingElement() : base () { } protected ROT128StreamUpgradeBindingElement(ROT128StreamUpgradeBindingElement copyFrom) : base (copyFrom) { } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.BuildInnerChannelFactory<TChannel>(); } public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.BuildInnerChannelListener<TChannel>(); } public override StreamUpgradeProvider BuildClientStreamUpgradeProvider(BindingContext context) { return new ROT128StreamUpgradeProvider( this , context); } public override StreamUpgradeProvider BuildServerStreamUpgradeProvider(BindingContext context) { return new ROT128StreamUpgradeProvider( this , context); } public override bool CanBuildChannelFactory<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.CanBuildInnerChannelFactory<TChannel>(); } public override bool CanBuildChannelListener<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.CanBuildInnerChannelListener<TChannel>(); } public override BindingElement Clone() { return new ROT128StreamUpgradeBindingElement( this ); } public override T GetProperty<T>(BindingContext context) { return context.GetInnerProperty<T>(); } } } The only other important task when writing a stream upgrade binding element is to build your stream upgrade provider when asked by the transport. ROT 128 works the same on both the client and server so I've made those methods do exactly Read More...
|
-
The mission for the next five days is to build and demonstrate an implementation of a stream upgrade. For review, a stream upgrade is a component that plugs into the transport and rewrites the byte stream as it goes on and off of the network. Stream upgrades are stackable and composable. You add stream upgrades by including stream upgrade binding elements in the desired order in the binding. I went over the basics of stream upgrades a few weeks ago. Here are the articles in that series: Stream Upgrades, Part 1 Stream Upgrades, Part 2 Stream Upgrades, Part 3 The example I've picked out is a stream upgrade that applies ROT 128 (ROTate by 128). ROT 13 is a famous example of a Caesar cipher that occludes messages by replacing letters in the English alphabet with the letter that is 13 places higher. When you go past 'Z', you wrap back around to 'A'. Since there are 26 letters, applying ROT 13 twice returns a letter back to where it started. ROT 128 is the equivalent for a single byte value. Here is the stream class that I want to have my messages run through. It includes some debugging so that we can see the messages as they go in and out. using System; using System.IO; namespace Microsoft.ServiceModel.Samples { class ROT128Stream : Stream { Stream stream; public ROT128Stream(Stream stream) { this .stream = stream; } public override bool CanRead { get { return this .stream.CanRead; } } public override bool CanSeek { get { return this .stream.CanSeek; } } public override bool CanWrite { get { return this .stream.CanWrite; } } protected override void Dispose( bool disposing) { if (disposing) { this .stream.Dispose(); } else { this .stream.Close(); } base .Dispose(disposing); } public override void Flush() { this .stream.Flush(); } public override long Length { get { return this .stream.Length; } } public override long Position { get { return this .stream.Position; } set { this .stream.Position = value ; } } static void DumpBuffer( byte [] buffer, int offset, int count) { int pos = 0; while (pos < count) { int lineCount = count - pos; if (lineCount > 15) { lineCount = 15; } for ( int linePos = 0; linePos < lineCount; linePos++) { Console.Write( " {0:X2}" , buffer[offset + pos + linePos]); } for ( int linePos = 15 - lineCount; linePos >= 0; linePos--) { Console.Write( " " ); } for ( int linePos = 0; linePos < lineCount; linePos++) { byte item = buffer[offset + pos + linePos]; if (item < 0x20 || item >= 0x80) { Console.Write( "." ); } else { Console.Write(( Read More...
|
-
To finish up the series on one-way HTTP requests , I promised to supply a custom channel that fixes the scenario of using the POX message encoder together with one-way requests. This is primarily a code post since most of the interesting discussion is already taken care of. I'm supplying a binding element, channel factory, and request channel. There isn't a channel listener or reply channel because there's no conflict on the server-side between one-way and POX. It's only the client side that requires this patch. Stick the binding element in your channel stack between the transport and the OneWay channel. CustomBinding binding = new CustomBinding( new OneWayBindingElement(), new ReplyMangler(), new TextMessageEncodingBindingElement(), new HttpTransportBindingElement() ); The channel code is mostly plumbing to make the reply run through the FilterMessage method that I posted last time. I spent less than 15 seconds testing this so it would be unwise to drop the code directly into your production system. It does appear however to do the proper job of swallowing messages that come back from the POX message encoder. class ReplyManglerChannel : ChannelBase, IRequestChannel { IRequestChannel innerChannel; public ReplyManglerChannel(ChannelManagerBase channelManager, IRequestChannel innerChannel) : base (channelManager) { this .innerChannel = innerChannel; } public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginRequest(message, timeout, callback, state); } public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state) { return BeginRequest(message, DefaultSendTimeout, callback, state); } public Message EndRequest(IAsyncResult result) { return FilterMessage(innerChannel.EndRequest(result)); } Message FilterMessage(Message reply) { if (reply == null ) { return null ; } HttpResponseMessageProperty properties = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name]; if (properties != null && properties.StatusCode == HttpStatusCode.Accepted) { return null ; } return reply; } protected override void OnAbort() { innerChannel.Abort(); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginClose(timeout, callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginOpen(timeout, callback, Read More...
|
-
The article yesterday left off by stating that while the test program works fine with the messaging defaults, things break when we switch to the POX message encoder. Today and tomorrow, we'll look at what goes wrong, why, and how to fix it. To switch to the POX message encoder, I'm going to change the MessageVersion setting of the normal text message encoder that is already in my binding. You can do this by adding the following line to the test client after creating the custom binding. binding.Elements.Find<MessageEncodingBindingElement>().MessageVersion = MessageVersion.None; Now, when I run the test program, I see the correct and expected output on the server. HTTP/1.1 POST http://localhost:8000/ ::1:1147 --> ::1:8000 [Headers] Connection: Keep-Alive Content-Length: 126 Content-Type: application/xml; charset=utf-8 Expect: 100-continue Host: localhost:8000 [Request] < Ping xmlns ="http://tempuri.org/" > < clientInfo > my info </ clientInfo > < lastSeen > 2006-08-15T00:22:36.528375-07:00 </ lastSeen > </ Ping > All of the SOAP goo is gone from the request. Compared to the request we saw yesterday, the only thing left in the request is what was formerly the contents of the body tag. However, on the client side, there's an exception being thrown after the message gets sent. Unhandled Exception: System.ServiceModel.ProtocolException: A response was received from a one-way send over the underlying IRequestChannel. Make sure the remote endpoint has a compatible binding at its endpoint (one that contains OneWayBindingElement). Server stack trace: at System.ServiceModel.Channels.RequestOneWayChannelFactory.RequestOutputChannel.ValidateResponse(Message response) at System.ServiceModel.Channels.RequestOneWayChannelFactory.RequestOutputChannel.OnSend(Message message, TimeSpan timeout) at System.ServiceModel.Channels.OutputChannel.Send(Message message, TimeSpan timeout) It's fairly easy to figure out what happened from the message and stack trace although it may not be obvious why. Inside the OneWay channel, there is validation that the system is truly acting in a one-way fashion. If you look at the code for the ValidateResponse method, you would see that it is essentially checking that the response message is null. In this case, we got a reply back from the server even though we were not expecting it to send anything back. Remember, the server knows nothing about WCF and behaves exactly the same for every request regardless Read More...
|
-
I promised yesterday that we would start using the HttpListener test program to look at some HTTP requests. I'm going to start by creating a fictional IPing service and a simple custom binding over HTTP. The test listener from yesterday was very basic so it can passively receive requests but doesn't make any meaningful replies. That means that we have to make the binding and service all use one-way calls so that they don't expect any real content to come back. [ServiceContract] public interface IPing { [OperationContract(IsOneWay = true )] void Ping( string clientInfo, DateTime lastSeen); } I'm going to magic up the client proxy for this service. This is exactly what you would get back from svcutil if you ran it against the IPing service contract. There's nothing actually exciting going on in this client class. [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute( "System.ServiceModel" , "3.0.0.0" )] public partial class PingClient : ClientBase<IPing>, IPing { public PingClient() { } public PingClient( string endpointConfigurationName) : base (endpointConfigurationName) { } public PingClient( string endpointConfigurationName, string remoteAddress) : base (endpointConfigurationName, remoteAddress) { } public PingClient( string endpointConfigurationName, EndpointAddress remoteAddress) : base (endpointConfigurationName, remoteAddress) { } public PingClient(Binding binding, EndpointAddress remoteAddress) : base (binding, remoteAddress) { } public void Ping( string clientInfo, DateTime lastSeen) { base .Channel.Ping(clientInfo, lastSeen); } } We never need to write a service implementation because we're going to hit the listener test program. Now, we can hit the "service" with an operation call to see what happens on the wire. I've got a trivial test client that creates the binding and sends a single call using the generated client. class client { static void Main(String[] args) { CustomBinding binding = new CustomBinding( new OneWayBindingElement(), new TextMessageEncodingBindingElement(), new HttpTransportBindingElement() ); PingClient client = new PingClient(binding, new EndpointAddress( "http://localhost:8000/" )); client.Open(); client.Ping( "my info" , DateTime.Now); client.Close(); } } This gives us a view of the SOAP goo and serialized service request. I didn't change the defaults for the SOAP envelope and WS addressing versions, which means that we'll have more goo than actual message. HTTP/1.1 POST http://localhost:8000/ Read More...
|
-
I think that the HttpWebRequest class for making HTTP requests in System.Net is fairly well understood but not that many people seem to make use of the HttpListener class for receiving requests. I use HttpListener when I need to see what WCF is doing under the covers when making a web request. By eliminating WCF from the server, I can play with the requests without any of the processing that might secretly go on. Today's article is an example of one of those test programs. I wrote a simple listener service that just prints out information about the request for inspection. I would use something like this for testing a WCF client that fiddles with how the request is structured. Since I don't know what the request is for, I just say that the request is accepted for future processing without giving an immediate response. This type of test program is for a one-way contract that doesn't expect any return data. using System; using System.IO; using System.Net; using System.Threading; class HttpTestListener { static bool shutdown = false ; static void Process( object state) { HttpListener listener = (HttpListener)state; listener.Start(); while (listener.IsListening) { try { HttpListenerContext context = listener.GetContext(); HttpListenerRequest request = context.Request; Console.WriteLine( "HTTP/{0} {1} {2}" , request.ProtocolVersion, request.HttpMethod, request.Url); Console.WriteLine( "{0} --> {1}" , request.RemoteEndPoint, request.LocalEndPoint); Console.WriteLine(); Console.WriteLine( "[Headers]" ); Console.WriteLine(request.Headers); if (request.HasEntityBody) { Console.WriteLine(); Console.WriteLine( "[Request]" ); StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding); Console.WriteLine(reader.ReadToEnd()); } HttpListenerResponse response = context.Response; response.StatusCode = ( int )HttpStatusCode.Accepted; response.ContentLength64 = 0; response.Close(); } catch (HttpListenerException exception) { if (!shutdown) { Console.WriteLine(exception); } } } } static void Main( string [] args) { HttpListener listener = new HttpListener(); foreach ( string arg in args) { listener.Prefixes.Add(arg); } listener.Prefixes.Add( "http://localhost:8000/" ); new Thread( new ParameterizedThreadStart(Process)).Start(listener); Console.WriteLine( "Press <ENTER> to quit." ); Console.ReadLine(); Console.WriteLine( "Shutting down..." ); shutdown = true ; listener.Close(); Thread.Sleep(1000); Environment.Exit(0); } } As you might expect, we Read More...
|
-
I'm presenting a small sample I wrote to demonstrate the port sharing feature. The third part is the test client and example usage. I'm looking for feedback to help make the sample better. Right now, the sample configures the endpoints in code rather than configuration because it uses randomly-generated addresses for the server. I may just tack on the configuration details at the end because other port sharing topics already cover that material. You can use the test client to check that messages are correctly routed to services sharing the port. class client { static void Main( string [] args) { Console.Write( "Enter the service number to test: " ); ushort salt = ushort .Parse(Console.ReadLine()); string address = String.Format( "net.tcp://localhost:5555/calculator/{0}" , salt); ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>( new NetTcpBinding()); ICalculator proxy = factory.CreateChannel( new EndpointAddress(address)); // Call the Add service operation. double value1 = 100.00D; double value2 = 15.99D; double result = proxy.Add(value1, value2); Console.WriteLine( "Add({0},{1}) = {2}" , value1, value2, result); // Call the Subtract service operation. value1 = 145.00D; value2 = 76.54D; result = proxy.Subtract(value1, value2); Console.WriteLine( "Subtract({0},{1}) = {2}" , value1, value2, result); // Call the Multiply service operation. value1 = 9.00D; value2 = 81.25D; result = proxy.Multiply(value1, value2); Console.WriteLine( "Multiply({0},{1}) = {2}" , value1, value2, result); // Call the Divide service operation. value1 = 22.00D; value2 = 7.00D; result = proxy.Divide(value1, value2); Console.WriteLine( "Divide({0},{1}) = {2}" , value1, value2, result); Console.WriteLine(); Console.WriteLine( "Press <ENTER> to terminate client." ); Console.ReadLine(); factory.Close(); } } Each instance of the service will print out its unique number and address. For instance, you may see the following text when you run service.exe. Service #4381 listening on net.tcp://localhost:5555/calculator/4381. Press <ENTER> to terminate service. Enter the service number you see here when you run client.exe. Enter the service number to test: 4381 Add(100,15.99) = 115.99 Subtract(145,76.54) = 68.46 Multiply(9,81.25) = 731.25 Divide(22,7) = 3.14285714285714 Press <ENTER> to terminate client. Next time: More Binding Polymorphism Read More...
|
-
I'm presenting a small sample I wrote to demonstrate the port sharing feature. The second part is the server application. I'm looking for feedback to help make the sample better. Right now, the sample configures the endpoints in code rather than configuration because it uses randomly-generated addresses for the server. I may just tack on the configuration details at the end because other port sharing topics already cover that material. The following code demonstrates enabling port sharing on the server. It starts an instance of the ICalculator service on a fixed port with a random URI path. Even though two services can share the same port, their overall endpoint addresses still need to be unique so that the Net.Tcp Port Sharing Service can route messages to the correct application. // Define a service contract [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples" )] public interface ICalculator { [OperationContract] double Add( double n1, double n2); [OperationContract] double Subtract( double n1, double n2); [OperationContract] double Multiply( double n1, double n2); [OperationContract] double Divide( double n1, double n2); } // Service class that implements the service contract public class CalculatorService : ICalculator { public double Add( double n1, double n2) { return n1 + n2; } public double Subtract( double n1, double n2) { return n1 - n2; } public double Multiply( double n1, double n2) { return n1 * n2; } public double Divide( double n1, double n2) { return n1 / n2; } } class service { static void Main( string [] args) { // Configure a binding with TCP port sharing enabled NetTcpBinding binding = new NetTcpBinding(); binding.PortSharingEnabled = true ; // Start a service on a fixed TCP port ServiceHost host = new ServiceHost( typeof (CalculatorService)); ushort salt = ( ushort ) new Random().Next(); string address = String.Format( "net.tcp://localhost:5555/calculator/{0}" , salt); host.AddServiceEndpoint( typeof (ICalculator), binding, address); host.Open(); Console.WriteLine( "Service #{0} listening on {1}." , salt, address); Console.WriteLine( "Press <ENTER> to terminate service." ); Console.ReadLine(); host.Close(); } } With port sharing enabled, you can run the service multiple times without having a conflict over the port number. If you were to change the code to disable port sharing, starting up two copies of the service would result in the second failing with an AddressAlreadyInUseException. Unhandled Exception: System.ServiceModel.AddressAlreadyInUseException: Read More...
|
|
|
|