I created a WCF Service:
Shared.dll:
[ServiceContract(ConfigurationName = "ICalculator")]
public interface ICalculator
{
[OperationContract()]
int Add(int a, int b);
}
Server:
[ServiceBehavior()]
public class Calculator : ICalculator
{
public int Add(int a, int b) { return a + b; }
}
Client (Attempt #1):
public class CalculatorClient : ClientBase<ICalculator>, ICalculator
{
private static Binding binding = new WSHttpBinding("MyConfig");
private static EndpointAddress remoteAddress = new EndpointAddress(...);
public CalculatorClient() : base(binding, remoteAddress) { }
public int Add(int a, int b)
{
return Channel.Add(a, b); //Exception
}
}
Client (Attempt #2): -- Note: I added a Service Reference instead of creating a CalculatorClient myself (.NET created it for me).
static void Main(string[] args)
{
Binding binding = new WSHttpBinding("MyConfig");
EndpointAddress remoteAddress = new EndpointAddress(...);
CalculatorClient client = new CalculatorClient(binding, remoteAddress);
int result = client.Add(5, 4); //Exception
}
Client (Attempt #3): -- I changed it to be a BasicHttpBinding() instead
static void Main(string[] args)
{
Binding binding = new BasicHttpBinding("MyConfig");
EndpointAddress remoteAddress = new EndpointAddress(...);
CalculatorClient client = new CalculatorClient(binding, remoteAddress);
int result = client.Add(5, 4); //This works!
}
app.config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="MyConfig" /> <!-- did not add anything to this yet -->
</wsHttpBinding>
</bindings>
</system.serviceModel>
The exception I get is: Content Type application/soap+xml; charset=utf-8 was not supported by service http://localhost/CalculatorService.svc. The client and service bindings may be mismatched. I don't see how they can be mismatched when I use a shared dll file between my server and client. The BasicHttpBinding works, just not the WSHttpBinding (I haven't even attempted WS2007HttpBinding.
Exception: [System.ServiceModel.ProtocolException]
{"Content Type application/soap+xml; charset=utf-8 was not supported by service http://localhost/CalculatorService.svc. The client and service bindings may be mismatched."}
Inner Exception: [System.Net.WebException]
The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..
You need to set the security to be used on the WSHttpBinding
http://msdn.microsoft.com/en-us/library/ms731884(v=VS.90).aspx
Updated with Sample Client/Server WSHttpBinding, default security
Client
class Program
{
static void Main(string[] args)
{
var calcClient = new CalcClient();
int i = 1;
int j = 2;
Console.WriteLine("Result of Adding {0} and {1} is {2}", i, j, calcClient.Add(i, j));
Console.ReadKey();
}
}
public class CalcClient : ICalculator
{
public CalcClient()
{
CalcProxy = ChannelFactory.CreateChannel(new WSHttpBinding(), new EndpointAddress("http://localhost:5050/CalcServer"));
}
ICalculator CalcProxy { get; set; }
public int Add(int a, int b)
{
return CalcProxy.Add(a, b);
}
}
Server
class Program
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof (CalcSvr));
host.AddServiceEndpoint(typeof (ICalculator), new WSHttpBinding(), "http://localhost:5050/CalcServer");
host.Open();
Console.WriteLine("Server Running");
Console.ReadKey();
}
}
Related
I am experimenting with a WCF service in a Visual Studio unit test. Both the client and the service are configured programmatically.
Currently my code looks like this:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Tests
{
public abstract class EntityBase
{
}
public class TestEntity : EntityBase
{
public string Name { get; set; }
}
[ServiceContract]
[ServiceKnownType("GetKnownTypes", typeof(ServiceKnownTypesDiscoveryHelper))]
public interface ITestService
{
[OperationContract]
EntityBase GetEntity(string entityName);
}
public class TestService : ITestService
{
public EntityBase GetEntity(string entityName)
{
Type t = Type.GetType(entityName);
return (EntityBase)Activator.CreateInstance(t);
}
}
[TestClass]
public class ServiceTests
{
private static ServiceHost ServiceHost { get; set; }
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
ServiceHost = new ServiceHost(typeof(TestService));
NetTcpBinding wsBinding = new NetTcpBinding();
ServiceHost.AddServiceEndpoint(typeof(ITestService), wsBinding,
"net.tcp://localhost:8011/TestService");
// trying to turn on debugging here
var behavior = ServiceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
behavior.IncludeExceptionDetailInFaults = true;
ServiceHost.Open();
}
[ClassCleanup]
public static void ClassCleanup()
{
ServiceHost.Close();
}
[TestMethod]
public void TestSomething()
{
var binding = new NetTcpBinding();
var endpoint = new EndpointAddress("net.tcp://localhost:8011/TestService");
using (ChannelFactory<ITestService> testServiceFactory =
new ChannelFactory<ITestService>(binding, endpoint))
{
var proxy = testServiceFactory.CreateChannel();
using (proxy as IDisposable)
{
try
{
var entity = proxy.GetEntity(typeof(TestEntity).FullName);
Assert.IsInstanceOfType(entity, typeof(TestEntity));
}
catch (FaultException ex)
{
// copied this from MSDN example
string msg = "FaultException: " + ex.Message;
MessageFault fault = ex.CreateMessageFault();
if (fault.HasDetail == true)
{
var reader = fault.GetReaderAtDetailContents();
if (reader.Name == "ExceptionDetail")
{
ExceptionDetail detail = fault.GetDetail<ExceptionDetail>();
msg += "\n\nStack Trace: " + detail.StackTrace;
}
}
System.Diagnostics.Trace.WriteLine(msg);
}
}
}
}
}
}
If my ServiceKnownTypesDiscoveryHelper does not return known types, I know that my service and client should throw something serialisation related somewhere deep in .NET servicemodel code (if I modify it to return my TestEntity then of course everything works without any issues).
But currently if the service fails, I get only some vague exception messages like:
The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue.
and at the end of using() I get
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
(which also is weird - why can't I even dispose the ServiceChannel if it's in a faulted state...)
How do I catch the actual fault which caused the service or the client to fail instead of those vague exception messages?
How can I force an IMessageInspector to fire only when a specific operation is called, rather than firing whenever a call to the service is made? Currently, I am applying a custom IEndpointBehavior to the endpoint which calls this IMessageInspector, but this is not exactly what I would like to do...
The message inspectors are bound to the dispatch runtime object, which is a single one for each endpoint, not operation. If a parameter inspector works, then you can use it (it's bound to the operation), but if you need a message inspector, then it cannot be bound to a single operation. But you can, inside your inspector, check whether the operation is what you expect (based on the Action header, which is unique per operation), as shown in the code below.
public class StackOverflow_7502134
{
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
public int Add(int x, int y)
{
return x + y;
}
}
public class MyInspector : IEndpointBehavior, IDispatchMessageInspector
{
string[] operationNames;
public MyInspector(params string[] operationNames)
{
this.operationNames = operationNames ?? new string[0];
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
public void Validate(ServiceEndpoint endpoint)
{
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// by default, action == <serviceContractNamespace>/<serviceContractName>/<operationName>
string operationName = request.Headers.Action.Substring(request.Headers.Action.LastIndexOf('/') + 1);
if (this.operationNames.Contains(operationName))
{
Console.WriteLine("Inspecting request to operation {0}", operationName);
Console.WriteLine(request);
Console.WriteLine();
return operationName;
}
else
{
return null;
}
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
string operationName = correlationState as string;
if (operationName != null)
{
Console.WriteLine("Inspecting reply from operation {0}", operationName);
Console.WriteLine(reply);
Console.WriteLine();
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
MyInspector inspector = new MyInspector("Add"); // inspecting Add, not Echo
host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "").Behaviors.Add(inspector);
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine("Calling Echo");
Console.WriteLine(proxy.Echo("Hello world"));
Console.WriteLine();
Console.WriteLine("Calling Add");
Console.WriteLine(proxy.Add(4, 5));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
I have a WCF proxy generated at runtime with DuplexChannelFactory.
How can I access the binding information given only the service interface returned from DuplexChannelFactory?
I can get most stuff by casting to an IClientChannel, but I can't seem to find binding info on there. The closest I can get is IClientChannel.RemoteAddress which is an endpoint, but that doesn't seem to have binding info either. :-/
You can't (directly). There are a few things which you can get from the channel, such as the message version (channel.GetProperty<MessageVersion>()), and other values. But the binding isn't one of those. The channel is created after the binding is "deconstructed" (i.e., expanded into its binding elements, while each binding element can add one more piece to the channel stack.
If you want to have the binding information in the proxy channel, however, you can add it yourself, using one of the extension properties of the context channel. The code below shows one example of that.
public class StackOverflow_6332575
{
[ServiceContract]
public interface ITest
{
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public int Add(int x, int y)
{
return x + y;
}
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
return result;
}
class MyExtension : IExtension<IContextChannel>
{
public void Attach(IContextChannel owner)
{
}
public void Detach(IContextChannel owner)
{
}
public Binding Binding { get; set; }
}
static void CallProxy(ITest proxy)
{
Console.WriteLine(proxy.Add(3, 5));
MyExtension extension = ((IClientChannel)proxy).Extensions.Find<MyExtension>();
if (extension != null)
{
Console.WriteLine("Binding: {0}", extension.Binding);
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
((IClientChannel)proxy).Extensions.Add(new MyExtension { Binding = factory.Endpoint.Binding });
CallProxy(proxy);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
How to set fall-back endpoint. I'm having more than one endpoint specified in the conifg file like follows. If the service was not accessible, then my client should check the next address specified in the list.
Client Configuration File:
<client>
<endpoint address="http://192.168.1.4/SampleAppWeb/Services/SampleAppService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISampleAppService"
contract="SampleAppServiceReference.ISampleAppService" name="BasicHttpBinding_ISampleAppService" />
<endpoint address="http://172.168.12.121/SampleAppWeb/Services/SampleAppService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISampleAppService"
contract="SampleAppServiceReference.ISampleAppService" name="BasicHttpBinding_ISampleAppService1" />
<endpoint address="http://127.0.0.1/Services/SampleAppWeb/SampleAppService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISampleAppService"
contract="SampleAppServiceReference.ISampleAppService" name="BasicHttpBinding_ISampleAppService2" />
<endpoint address="http://172.168.111.115/Services/SampleAppWeb/SampleAppService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISampleAppService"
contract="SampleAppServiceReference.ISampleAppService" name="BasicHttpBinding_ISampleAppService3" />
</client>
Code Behind:
var pass = new SampleAppServiceReference.SampleAppServiceClient("BasicHttpBinding_ISampleAppService3");
WCF itself doesn't have any built-in feature for this, but you can easily create a class which will do the retrying for you. The example below shows one such way. Notice that if your binding uses session, you may need to recreate the client (instead of simply reusing it), as its channel will likely be faulted if an error happens.
public class StackOverflow_4968244
{
[ServiceContract]
public interface ITest
{
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public int Add(int x, int y)
{
Console.WriteLine("Request at service: {0}", OperationContext.Current.Host.BaseAddresses[0]);
if (new Random().Next(3) == 0)
{
throw new InvalidOperationException("Random error");
}
return x + y;
}
}
public class Client : ClientBase<ITest>, ITest
{
public Client(string address) : base(new BasicHttpBinding(), new EndpointAddress(address)) { }
public int Add(int x, int y)
{
return this.Channel.Add(x, y);
}
}
public class SafeClient : ITest
{
List<Client> clients;
public SafeClient(params Client[] clients)
{
this.clients = new List<Client>(clients);
}
public int Add(int x, int y)
{
foreach (var client in this.clients)
{
try
{
return client.Add(x, y);
}
catch (CommunicationException)
{
Console.WriteLine("Error calling client {0}, retrying with next one", client.Endpoint.Address.Uri);
}
}
throw new InvalidOperationException("All services seem to be down");
}
}
public static void Test()
{
string baseAddress1 = "http://" + Environment.MachineName + ":8000/Service";
string baseAddress2 = "http://" + Environment.MachineName + ":8001/Service";
string baseAddress3 = "http://" + Environment.MachineName + ":8002/Service";
ServiceHost host1 = new ServiceHost(typeof(Service), new Uri(baseAddress1));
ServiceHost host2 = new ServiceHost(typeof(Service), new Uri(baseAddress2));
ServiceHost host3 = new ServiceHost(typeof(Service), new Uri(baseAddress3));
host1.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
host2.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
host3.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
host1.Open();
host2.Open();
host3.Open();
Console.WriteLine("Hosts opened");
SafeClient safeClient = new SafeClient(
new Client(baseAddress1),
new Client(baseAddress2),
new Client(baseAddress3));
for (int i = 0; i < 20; i++)
{
Console.WriteLine(safeClient.Add(i, 10));
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host1.Close();
host2.Close();
host3.Close();
}
}
I am trying to get along with WCF's duplex contracts. A code from this article
(http://msdn.microsoft.com/en-us/library/ms731184.aspx)
ICalculatorDuplexCallback callback = null;
callback = OperationContext.Current.GetCallbackChannel();
throws a NullReferenceException. So how can i manage this?
Thanks for your attention!
Have you decorated the interface for ICalculatorDuplex
with
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required,
CallbackContract=typeof(ICalculatorDuplexCallback))]
When a service receives a message it looks at a replyTo element in the message to determine where to reply to, I would guess that if your missing the callback contract attribute it, it would result in you getting a NullReferenceException, as it doesn't know where to replyTo.
I've just ran quickly through the example.
The code for my service is :
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
namespace DuplexExample
{
// Define a duplex service contract.
// A duplex contract consists of two interfaces.
// The primary interface is used to send messages from client to service.
// The callback interface is used to send messages from service back to client.
// ICalculatorDuplex allows one to perform multiple operations on a running result.
// The result is sent back after each operation on the ICalculatorCallback interface.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required,
CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
[OperationContract(IsOneWay=true)]
void Clear();
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract(IsOneWay = true)]
void SubtractFrom(double n);
[OperationContract(IsOneWay = true)]
void MultiplyBy(double n);
[OperationContract(IsOneWay = true)]
void DivideBy(double n);
}
// The callback interface is used to send messages from service back to client.
// The Equals operation will return the current result after each operation.
// The Equation opertion will return the complete equation after Clear() is called.
public interface ICalculatorDuplexCallback
{
[OperationContract(IsOneWay = true)]
void Equals(double result);
[OperationContract(IsOneWay = true)]
void Equation(string eqn);
}
// Service class which implements a duplex service contract.
// Use an InstanceContextMode of PerSession to store the result
// An instance of the service will be bound to each duplex session
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
{
double result;
string equation;
ICalculatorDuplexCallback callback = null;
public CalculatorService()
{
result = 0.0D;
equation = result.ToString();
callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
}
public void Clear()
{
callback.Equation(equation + " = " + result);
result = 0.0D;
equation = result.ToString();
}
public void AddTo(double n)
{
result += n;
equation += " + " + n;
callback.Equals(result);
}
public void SubtractFrom(double n)
{
result -= n;
equation += " - " + n;
callback.Equals(result);
}
public void MultiplyBy(double n)
{
result *= n;
equation += " * " + n;
callback.Equals(result);
}
public void DivideBy(double n)
{
result /= n;
equation += " / " + n;
callback.Equals(result);
}
}
class Program
{
static void Main()
{
var host = new ServiceHost(typeof(CalculatorService));
host.Open();
Console.WriteLine("Service is open");
Console.ReadLine();
}
}
}
My application config looks like :
<?xml version="1.0" encoding="utf-8" ?>
<services>
<service behaviorConfiguration="NewBehavior" name="DuplexExample.CalculatorService">
<endpoint address="dual" binding="wsDualHttpBinding" bindingConfiguration=""
contract="DuplexExample.ICalculatorDuplex" />
<endpoint address="mex" binding="mexHttpBinding" bindingConfiguration=""
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8081/duplex" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
I then used the host address in the config file to create a service reference named CalculatorService.
so my client looks like :
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using Client.CalculatorService;
namespace Client
{
class Program
{
static void Main(string[] args)
{
var context = new InstanceContext(new CallbackHandler());
var client = new CalculatorDuplexClient(context);
Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");
Console.WriteLine();
// Call the AddTo service operation.
var value = 100.00D;
client.AddTo(value);
// Call the SubtractFrom service operation.
value = 50.00D;
client.SubtractFrom(value);
// Call the MultiplyBy service operation.
value = 17.65D;
client.MultiplyBy(value);
// Call the DivideBy service operation.
value = 2.00D;
client.DivideBy(value);
// Complete equation
client.Clear();
Console.ReadLine();
//Closing the client gracefully closes the connection and cleans up resources
client.Close();
}
}
// Define class which implements callback interface of duplex contract
public class CallbackHandler : ICalculatorDuplexCallback
{
public void Result(double result)
{
Console.WriteLine("Result({0})", result);
}
public void Equation(string eqn)
{
Console.WriteLine("Equation({0})", eqn);
}
#region ICalculatorDuplexCallback Members
public void Equals(double result)
{
Console.WriteLine("Equals{0} ",result );
}
#endregion
}
}