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
Related
I'm having a problem with a self-host WCF REST service.
When I try to issue a GET via browser or Fiddler, I get a 400 Bad Request. Tracing is reporting an inner exception of XmlException "The body of the message cannot be read because it is empty."
I don't have any configuration in app.config (do I need any?). I have tried changing WebServiceHost to ServiceHost, and WSDL is returned, but the operations still return 400.
What am I missing here?
// Add Reference to System.ServiceModel and System.ServiceModel.Web
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
namespace WCFRESTTest
{
class Program
{
static void Main(string[] args)
{
var baseAddress = new Uri("http://localhost:8000/");
var host = new WebServiceHost(typeof(RestService), baseAddress);
try
{
host.AddServiceEndpoint(typeof(IRestService), new WSHttpBinding(), "RestService");
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
host.Open();
Console.WriteLine("Service Running. Press any key to stop.");
Console.ReadKey();
}
catch(CommunicationException ce)
{
host.Abort();
throw;
}
}
}
[ServiceContract]
public interface IRestService
{
[OperationContract]
[WebGet(UriTemplate = "Test")]
bool Test();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestService : IRestService
{
public bool Test()
{
Debug.WriteLine("Test Called.");
return true;
}
}
}
When you use the WebServiceHost, you typically don't need to add a service endpoint - it will add one with all behaviors required to make it a "Web HTTP" (a.k.a. REST) endpoint (i.e., an endpoint which doesn't use SOAP and you can easily consume with a tool such as Fiddler, which seems to be what you want). Also, Web HTTP endpoints aren't exposed in the WSDL, so you don't need to add the ServiceMetadataBehavior either.
Now for why it doesn't work - sending a GET request to http://localhost:8000/Test should work - and in the code below it does. Try running this code, and sending the request you were sending before with Fiddler, to see the difference. That should point out what the issue you have.
public class StackOverflow_15705744
{
[ServiceContract]
public interface IRestService
{
[OperationContract]
[WebGet(UriTemplate = "Test")]
bool Test();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestService : IRestService
{
public bool Test()
{
Debug.WriteLine("Test Called.");
return true;
}
}
public static void Test()
{
var baseAddress = new Uri("http://localhost:8000/");
var host = new WebServiceHost(typeof(RestService), baseAddress);
// host.AddServiceEndpoint(typeof(IRestService), new WSHttpBinding(), "RestService");
// var smb = new ServiceMetadataBehavior();
// smb.HttpGetEnabled = true;
// host.Description.Behaviors.Add(smb);
host.Open();
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress.ToString().TrimEnd('/') + "/Test"));
Console.WriteLine("Service Running. Press any key to stop.");
Console.ReadKey();
}
}
I am trying to write an integration test that runs the service and then connects a client to this service.
ConnectClientToTestService() throws error:
System.ServiceModel.Security.SecurityNegotiationException: Secure
channel cannot be opened because security negotiation with the remote
endpoint has failed. This may be due to absent or incorrectly
specified EndpointIdentity in the EndpointAddress used to create the
channel. Please verify the EndpointIdentity specified or implied by
the EndpointAddress correctly identifies the remote endpoint. --->
System.ServiceModel.FaultException: The request for security token has
invalid or malformed elements.
Can you do this in the same exe? There are certificates involved which have been installed on my machine, but these also might be the issue.
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ServiceModel;
using ECS.Services;
using ECS.App.Core.ECSDataService;
using System.ServiceModel.Description;
namespace ECS.Test.ClientSide
{
[TestClass]
public class TestValidUserIntegrationTest
{
private static TestContext context;
[ClassInitialize()]
public static void ClassInitialize(TestContext testContext)
{
context = testContext;
ResourcingServiceHost.StartService();
}
/// <summary>
/// Shut down the WCF service once all tests have been run
/// </summary>
[ClassCleanup()]
public static void MyClassCleanup()
{
ResourcingServiceHost.StopService();
}
//Point the client at the test ResourceingServiceHost service
[TestMethod]
public void ConnectClientToTestService()
{
WSHttpBinding myBinding = new WSHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8733/ECS.Services/DataService/");
var factory = new ChannelFactory<ECS.App.Core.ECSDataService.IDataService>("debug", new EndpointAddress("http://localhost:8733/ECS.Services/DataService/"));//new ChannelFactory<ECS.App.Core.ECSDataService.IDataService>(myBinding, myEndpoint);//
{
ClientCredentials clientCredentials = new ClientCredentials();
clientCredentials.UserName.UserName = "admin";
clientCredentials.UserName.Password = "a";
factory.Endpoint.Behaviors.RemoveAll<ClientCredentials>();
factory.Endpoint.Behaviors.Add(clientCredentials);
ECS.App.Core.ECSDataService.IDataService client = factory.CreateChannel();
using (Channel.AsDisposable(client))
{
client.GetConnectionStrings();
}
}
}
}
internal class ResourcingServiceHost
{
internal static ServiceHost Instance = null;
internal static void StartService()
{
Instance = new ServiceHost(typeof(DataService));
WSHttpBinding wsBinding = new WSHttpBinding();
wsBinding.Security.Mode = SecurityMode.Message;
wsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
Instance.AddServiceEndpoint(typeof(ECS.Services.IDataService), wsBinding, "http://localhost:8733/ECS.Services/DataService/");
Instance.Open();
}
internal static void StopService()
{
if (Instance.State != CommunicationState.Closed)
{
Instance.Close();
}
}
}
//This allows us to see the inner exceptions from the WCF service
public class Channel : IDisposable
{
private ICommunicationObject _channel;
private Channel(ICommunicationObject channel)
{
_channel = channel;
}
public static IDisposable AsDisposable(object client)
{
return new Channel((ICommunicationObject)client);
}
public void Dispose()
{
bool success = false;
try
{
if (_channel.State != CommunicationState.Faulted)
{
_channel.Close(); success = true;
}
}
finally
{
if (!success)
{
_channel.Abort();
}
}
}
}
}
I am trying to create a WCF DataService using in-memory object graph. This means that the backend is not an Entity Framework store, but a bunch of objects that reside in memory.
I am trying to create a service operation called GetUsersByName that has a single parameter for name and returns the matching users as an IQueryable collection.
I followed the documentation and added the access rules for this operation
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
But when the SetServiceOperationAccessRule method is called I receive an exception on the client:
System.AggregateException was unhandled.
Here is the full code for my console application
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Data.Services;
using System.Data.Services.Common;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Net;
using System.IO;
namespace WCF_OData
{
class Program
{
static void Main(string[] args)
{
string serviceAddress = "http://localhost:8080";
Uri[] uriArray = { new Uri(serviceAddress) };
Type serviceType = typeof(UserDataService);
using (var host = new DataServiceHost(serviceType, uriArray)) {
host.Open();
var client = new HttpClient() { BaseAddress = new Uri(serviceAddress) };
Console.WriteLine("Client received: {0}", client.GetStringAsync("Users?$format=json").Result);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:8080");
request.Method = "GET";
request.Accept = #"application/json";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.ContentType);
Console.WriteLine((new StreamReader(response.GetResponseStream())).ReadToEnd());
}
Console.WriteLine("Press any key to stop service");
Console.ReadKey();
}
}
}
[EnableJsonSupport]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class UserDataService : DataService<UserService> {
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
public class UserService
{
private List<User> _List = new List<User>();
public UserService()
{
_List.Add(new User() { ID = 1, UserName = "John Doe" });
_List.Add(new User() { ID = 2, UserName = "Jane Doe" });
}
public IQueryable<User> Users
{
get
{
HttpContext x = HttpContext.Current;
return _List.AsQueryable<User>();
}
}
[OperationContract]
[WebGet(UriTemplate="GetUsersByName")]
public IQueryable<User> GetUsersByName(string name)
{
return new List<User>().AsQueryable();
}
}
[DataServiceKey("ID")]
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
}
}
It looks like there are a few things going on here, so this may take a couple of iterations to work through. The first problem that should be fixed is the service operation. Service operations need to be declared on the class that inherits from DataService: "Service operations are methods added to the data service class that derives from DataService". Here's a sample:
using System.Data.Entity;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Scratch.Web
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ScratchService : DataService<ScratchEntityFrameworkContext>
{
static ScratchService()
{
Database.SetInitializer(new ScratchEntityFrameworkContextInitializer());
}
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public IQueryable<Product> FuzzySearch(string idStartsWith)
{
var context = new ScratchEntityFrameworkContext();
return context.Products.ToList().Where(p => p.ID.ToString().StartsWith(idStartsWith)).AsQueryable();
}
}
}
You should then be able to call your service operation from a browser, with a URL format similar to the following: http://localhost:59803/ScratchService.svc/FuzzySearch()?idStartsWith='1'
Can we start by trying to get this functional in a browser and then see whether the AggregateException still happens?
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
}
}
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.