NullReference Exception is thrown while getting a callback channel - wcf

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
}
}

Related

Catching WCF faults when hosting a service and a client in a single application

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?

EDIT SOAP of a WCF Service using IClientMessageInspector

In my WCF Service, I want to edit the SOAP in BeforeSendRequest and AfterReceiveReply of IClientMessageInspector.
I have created a custom Behavior like this:
public class MyBehavior : BehaviorExtensionElement, IEndpointBehavior
{
}
in the class MyBehavior, I implemented IEndpointBehavior method Like below code:
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
MyInspector inspector = new MyInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
MyInspector is nothing but the class which is inherited from IClientMessageInspector.
Now, my question is: ApplyClientBehavior of IEndpointBehavior is not getting fired. But at wcf client, when I add a reference of the project where the MyBehavior class is present and write below code at client side:
c.Endpoint.Behaviors.Add(new MyBehavior());
It works fine. I mean the Apply Client Behavior method getting fired.
I dont want to ask my clients to add this Behavior manually and I want this to happen automatically. How can I achive this?
Here is the full code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.IO;
namespace MethodChangeService
{
public class MyInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
XmlDocument doc = new XmlDocument();
MemoryStream ms = new MemoryStream();
XmlWriter writer = XmlWriter.Create(ms);
reply.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
doc.Load(ms);
ChangeMessage(doc, false);
ms.SetLength(0);
writer = XmlWriter.Create(ms);
doc.WriteTo(writer);
writer.Flush();
ms.Position = 0;
XmlReader reader = XmlReader.Create(ms);
reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
string action = request.Headers.GetHeader<string>("Action", request.Headers[0].Namespace);
if (action.Contains("GetData"))
{
XmlDocument doc = new XmlDocument();
MemoryStream ms = new MemoryStream();
XmlWriter writer = XmlWriter.Create(ms);
request.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
doc.Load(ms);
ChangeMessage(doc, true);
ms.SetLength(0);
writer = XmlWriter.Create(ms);
doc.WriteTo(writer);
writer.Flush();
ms.Position = 0;
XmlReader reader = XmlReader.Create(ms);
request = Message.CreateMessage(reader, int.MaxValue, request.Version);
}
request.Headers.Action += "1";
return null;
}
void ChangeMessage(XmlDocument doc, bool flag)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
nsManager.AddNamespace("tempuri", "http://tempuri.org/");
XmlNode node = doc.SelectSingleNode("//s:Body", nsManager);
if (node != null)
{
if (flag)
node.InnerXml = node.InnerXml.Replace("GetData", "GetData1");
else
node.InnerXml = node.InnerXml.Replace("GetData1Response", "GetDataResponse").Replace("GetData1Result", "GetDataResult");
}
}
}
public class MyBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
//endpoint.Behaviors.Add(new MyBehavior());
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
MyInspector inspector = new MyInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
protected override object CreateBehavior()
{
return new MyBehavior();
}
public override Type BehaviorType
{
get
{
Type t = Type.GetType("MethodChangeService.MyBehavior");
return t;
}
}
}
}
and the Service class is:
using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.IO;
using System.Xml;
namespace MethodChangeService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
public class HardcoadedService : IHardcoadedService
{
public string GetData(int i)
{
return string.Format("you entered {0}",i);
}
public string GetData1()
{
return string.Format("You got redirected to another method!!");
}
}
}
Here is the client code:
class Program
{
static void Main(string[] args)
{
HardcoadedServiceClient c = new HardcoadedServiceClient();
c.Endpoint.Behaviors.Add(new MyBehavior());
string s = c.GetData(3);
Console.WriteLine(s);
Console.ReadKey();
}
}
You can accomplish this using the <behaviors> and <extensions> sections in the app.config file for your client.
To register your custom behavior add the following to the <system.serviceModel> section of your app.config file:
<behaviors>
<endpointBehaviors>
<behavior name="MyBehavior">
<myBehavior/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="myBehavior" type="MethodChangeService.MyBehavior, MethodChangeService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
Then in <endpoint> element if the <client> section add the following attribute:
behaviorConfiguration="MyBehavior"
For more info on this, check out: Configuring and Extending the Runtime with Behaviors

WCF: How to actually get to use WSHttpBinding? I get exceptions instead

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();
}
}

How to set more than one fallback endpoints in WCF

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();
}
}

Duplex Contract GetCallbackChannel always returns a null-instance

Here is the server code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ServiceModel.Description;
namespace Console_Chat
{
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallbackContract))]
public interface IMyService
{
[OperationContract(IsOneWay = true)]
void NewMessageToServer(string msg);
[OperationContract(IsOneWay = false)]
bool ServerIsResponsible();
}
[ServiceContract]
public interface IMyCallbackContract
{
[OperationContract(IsOneWay = true)]
void NewMessageToClient(string msg);
[OperationContract(IsOneWay = true)]
void ClientIsResponsible();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
public IMyCallbackContract callback = null;
/*
{
get
{
return OperationContext.Current.GetCallbackChannel<IMyCallbackContract>();
}
}
*/
public MyService()
{
callback = OperationContext.Current.GetCallbackChannel<IMyCallbackContract>();
}
public void NewMessageToServer(string msg)
{
Console.WriteLine(msg);
}
public void NewMessageToClient( string msg)
{
callback.NewMessageToClient(msg);
}
public bool ServerIsResponsible()
{
return true;
}
}
class Server
{
static void Main(string[] args)
{
String msg = "none";
ServiceMetadataBehavior behavior = new
ServiceMetadataBehavior();
ServiceHost serviceHost = new
ServiceHost(
typeof(MyService),
new Uri("http://localhost:8080/"));
serviceHost.Description.Behaviors.Add(behavior);
serviceHost.AddServiceEndpoint(
typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexHttpBinding(),
"mex");
serviceHost.AddServiceEndpoint(
typeof(IMyService),
new WSDualHttpBinding(),
"ServiceEndpoint"
);
serviceHost.Open();
Console.WriteLine("Server is up and running");
MyService server = new MyService();
server.NewMessageToClient("Hey client!");
/*
do
{
msg = Console.ReadLine();
// callback.NewMessageToClient(msg);
} while (msg != "ex");
*/
Console.ReadLine();
}
}
}
Here is the client's:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ServiceModel.Description;
using Console_Chat_Client.MyHTTPServiceReference;
namespace Console_Chat_Client
{
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallbackContract))]
public interface IMyService
{
[OperationContract(IsOneWay = true)]
void NewMessageToServer(string msg);
[OperationContract(IsOneWay = false)]
bool ServerIsResponsible();
}
[ServiceContract]
public interface IMyCallbackContract
{
[OperationContract(IsOneWay = true)]
void NewMessageToClient(string msg);
[OperationContract(IsOneWay = true)]
void ClientIsResponsible();
}
public class MyCallback : Console_Chat_Client.MyHTTPServiceReference.IMyServiceCallback
{
static InstanceContext ctx = new InstanceContext(new MyCallback());
static MyServiceClient client = new MyServiceClient(ctx);
public void NewMessageToClient(string msg)
{
Console.WriteLine(msg);
}
public void ClientIsResponsible()
{
}
class Client
{
static void Main(string[] args)
{
String msg = "none";
client.NewMessageToServer(String.Format("Hello server!"));
do
{
msg = Console.ReadLine();
if (msg != "ex")
client.NewMessageToServer(msg);
else client.NewMessageToServer(String.Format("Client terminated"));
} while (msg != "ex");
}
}
}
}
callback = OperationContext.Current.GetCallbackChannel();
This line constanly throws a NullReferenceException, what's the problem?
Thanks!
You can't just start a WCF service with a callback contract and immediately try to execute a client callback. There are no clients yet.
In your code, I see you manually instantiating a MyService and trying to execute a callback method. This simply won't work. If you want to use the GetCallbackChannel method then it has to be done when there is actually a channel - i.e. in the context of an actual operation invoked by a remote WCF client. Otherwise, there is no current OperationContext and you'll get a null reference exception because OperationContext.Current returns null.
Callbacks are intended to be used with long-running service operations. For example:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
// One-way method
public void PerformLongRunningOperation()
{
var callback =
OperationContext.Current.GetCallbackChannel<IMyCallbackContract>();
var result = DoLotsOfWork();
callback.LongRunningOperationFinished(result);
}
}
To test this you would have to actually create a client - start a new project, add a reference to this service, implement the callback that the importer generates, create an InstanceContext with the callback, create the client proxy using that InstanceContext, and finally invoke its PerformLongRunningOperation method.
If you are trying to develop a pub/sub implementation, where clients do not actually initiate the operations but simply register themselves to receive some callback, have a look at this page: Using Callback Contracts in WCF for Asynchronous Publish/Subscribe Event-Style Communication.