WCF net.tcp with ssl - wcf

Well im trying to setup a net.tcp connection with SSL encryption
I have the following classes
Wcf Interface:
[ServiceContract()]
public interface IServer
{
[OperationContract]
void Send(string message);
}
Server:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
class Server : IServer
{
readonly ServiceHost host;
public Server()
{
host = new ServiceHost(this);
host.AddServiceEndpoint(typeof(IServer), Program.GetBinding(), string.Format("net.tcp://localhost:{0}/", 1010));
host.Credentials.ServiceCertificate.Certificate = Program.LoadCert();
host.Open();
}
public void Send(string message)
{
Console.WriteLine(message);
}
}
Program (Client/Server):
class Program
{
static ChannelFactory<IServer> channelFactory;
static IServer server;
static RemoteCertificateValidationCallback callback = null;
public const int MaxMessageSize = 1024 * 1024 * 2;
public static Binding GetBinding()
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Message.ClientCredentialType = MessageCredentialType.None;
binding.ReaderQuotas.MaxArrayLength = MaxMessageSize;
binding.ReaderQuotas.MaxBytesPerRead = MaxMessageSize;
binding.MaxBufferSize = MaxMessageSize;
binding.MaxReceivedMessageSize = MaxMessageSize;
binding.MaxBufferPoolSize = binding.MaxBufferSize * 10;
binding.TransactionFlow = false;
binding.ReliableSession.Enabled = false;
binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
return binding;
}
public static X509Certificate2 LoadCert()
{
X509Certificate2 cert = new X509Certificate2();
cert.Import(#"Test2.pfx", "Test", X509KeyStorageFlags.DefaultKeySet);
return cert;
}
static void Main(string[] args)
{
try
{
callback = new RemoteCertificateValidationCallback(ValidateCertificate);
ServicePointManager.ServerCertificateValidationCallback += callback;
Console.WriteLine("[C]lient or [S]erver?");
ConsoleKeyInfo key = Console.ReadKey(true);
if (key.Key == ConsoleKey.S)
{
StartServer();
}
else if (key.Key == ConsoleKey.C)
{
StartClient();
}
}
finally
{
if (callback != null)
ServicePointManager.ServerCertificateValidationCallback -= callback;
}
}
private static void StartClient()
{
Console.WriteLine("Starting client mode!");
Console.Write("Host:");
string host = Console.ReadLine();
channelFactory = new ChannelFactory<IServer>(GetBinding());
server = channelFactory.CreateChannel(new EndpointAddress(string.Format("net.tcp://{0}:{1}/", host, 1010)));
while (true)
{
Console.Write("Message:");
server.Send(Console.ReadLine());
}
}
private static void StartServer()
{
Console.WriteLine("Starting server mode!");
Server server = new Server();
Console.ReadKey();
GC.KeepAlive(server);
}
public static bool ValidateCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine("ValidateCertificate");
return true;
}
}
This works fine when both the server and client are run on the same computer (but ValidateCertificate is never called like it should be).
But if i run them on separate computers i get the following exception on the client:
Description: The process was terminated due to an unhandled exception.
Exception Info:
System.ServiceModel.Security.SecurityNegotiationException Stack:
Server stack trace: at
System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream
stream, SecurityMessageProperty& remoteSecurity) at
System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorBase.InitiateUpgrade(Stream
stream) at
System.ServiceModel.Channels.ConnectionUpgradeHelper.InitiateUpgrade(StreamUpgradeInitiator
upgradeInitiator, IConnection& connection, ClientFramingDecoder
decoder, IDefaultCommunicationTimeouts defaultTimeouts, TimeoutHelper&
timeoutHelper) at
System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.SendPreamble(IConnection
connection, ArraySegment`1 preamble, TimeoutHelper& timeoutHelper)
at
System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.DuplexConnectionPoolHelper.AcceptPooledConnection(IConnection
connection, TimeoutHelper& timeoutHelper) at
System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnection(TimeSpan
timeout) at
System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpen(TimeSpan
timeout) at
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan
timeout) at
System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan
timeout) at
System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel
channel, TimeSpan timeout) at
System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan
timeout, CallOnceManager cascade) at
System.ServiceModel.Channels.ServiceChannel.Call(String action,
Boolean oneway, ProxyOperationRuntime operation, Object[] ins,
Object[] outs, TimeSpan timeout) at
System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage
methodCall, ProxyOperationRuntime operation) at
System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage
message) at
System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(System.Runtime.Remoting.Messaging.IMessage,
System.Runtime.Remoting.Messaging.IMessage) at
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(System.Runtime.Remoting.Proxies.MessageData
ByRef, Int32) at
WcfChatTest.Client.Program+IServer.Send(System.String) at
WcfChatTest.Client.Program.StartClient() at
WcfChatTest.Client.Program.Main(System.String[])
What did i configure wrong, and why does it call "WindowsStreamSecurityUpgradeProvider" when i provided it a certificate?
Or if some one else got a great example how to get transport encryption on net.tcp without the client and server being in the same domain?

I solved the problem on the line
server = channelFactory.CreateChannel(new EndpointAddress(string.Format("net.tcp://{0}:{1}/", host, 1010)));
i had to add a addittional parameter that i created with the following code
EndpointIdentity.CreateX509CertificateIdentity(certificate)

Related

Asynchronous message sending WCF

The addMessage method asynchronously sends messages to clients.
The problem is that those who send it receive it themselves
That is, the client hears himself
public void AddMessage(byte[] message)
{
IAsyncMessageCallback other = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
other.BeginOnMessageAdded(message, DateTime.Now, delegate (IAsyncResult ar)
{
other.EndOnMessageAdded(ar);
}, null);
}
I tried to implement it in the following way:
public void AddMessage(byte[] message)
{
lock (subscribers)
{
var connection = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
string user;
if (!subscribers.TryGetValue(connection, out user))
return;
foreach (var other in subscribers.Keys)
{
//Console.WriteLine(other + " " + connection);
if (other == connection)
continue;
Console.WriteLine(other + " " + connection);
other.BeginOnMessageAdded(message, DateTime.Now, delegate (IAsyncResult ar)
{
other.EndOnMessageAdded(ar);
}, null);
Console.WriteLine(message.Length);
}
}
}
But this leads to terrible brakes
Thank you in advance
The essence of the problem:
1.When more than two users enter, the chat starts to hang (a very long delay appears), and then the connection is broken: The communication object System.ServiceModel.Channels.ServiceChannel cannot be used for communication, since its operation is interrupted
If you remove the foreach loop, there will be no breaks and the chat will work calmly with ten users, but the message will be sent to everyone (that is, I will hear my voice in the chat)
Everything works, but with two users. If more come in then System.ServiceModel.Channels.ServiceChannel cannot be used for communication, since its operation is interrupted
public void SendMessage(byte[] message)
{
var connection = OperationContext.Current.GetCallbackChannel<IChatClient>();
string user;
if (!_users.TryGetValue(connection, out user))
return;
foreach (var other in _users.Keys)
{
if (other == connection)
continue;
other.RecievMessage(user, message);
Console.WriteLine(message.Length);
}
}
If you remove the foreach loop, ten users can come in, but they will hear themselves because the server sends this message to everyone indiscriminately.
if the person who sent the video is the current subscriber they don't send the video to THAT subscriber.
How to implement it?
public class StackOverflow_5979252
{
[ServiceContract(Name = "IMessageCallback")]
public interface IAsyncMessageCallback
{
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginOnMessageAdded(string msg, DateTime timestamp, AsyncCallback callback, object asyncState);
void EndOnMessageAdded(IAsyncResult result);
}
[ServiceContract(CallbackContract = typeof(IAsyncMessageCallback))]
public interface IMessage
{
[OperationContract]
void AddMessage(string message);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IMessage
{
public void AddMessage(string message)
{
IAsyncMessageCallback callback = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
callback.BeginOnMessageAdded(message, DateTime.Now, delegate(IAsyncResult ar)
{
callback.EndOnMessageAdded(ar);
}, null);
}
}
class MyClientCallback : IAsyncMessageCallback
{
public IAsyncResult BeginOnMessageAdded(string msg, DateTime timestamp, AsyncCallback callback, object asyncState)
{
Action<string, DateTime> act = (txt, time) => { Console.WriteLine("[{0}] {1}", time, txt); };
return act.BeginInvoke(msg, timestamp, callback, asyncState);
}
public void EndOnMessageAdded(IAsyncResult result)
{
Action<string,DateTime> act = (Action<string,DateTime>)((System.Runtime.Remoting.Messaging.AsyncResult)result).AsyncDelegate;
act.EndInvoke(result);
}
}
static Binding GetBinding()
{
return new NetTcpBinding(SecurityMode.None);
}
public static void Test()
{
string baseAddress = "net.tcp://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(IMessage), GetBinding(), "");
host.Open();
Console.WriteLine("Host opened");
InstanceContext instanceContext = new InstanceContext(new MyClientCallback());
DuplexChannelFactory<IMessage> factory = new DuplexChannelFactory<IMessage>(instanceContext, GetBinding(), new EndpointAddress(baseAddress));
IMessage proxy = factory.CreateChannel();
proxy.AddMessage("Hello world");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
((IClientChannel)proxy).Close();
factory.Close();
host.Close();
}
}

Closing Duplex Channel still produces error in tracing

I have a duplex WCF channel used in a publish/subscribe pattern. I can not figure out how to cleanly disconnect clients without producing an error in the tracing. The error does not affect the application but shows up in the tracing logs. Since this expected error message is filling up our logs it is hard to figure other issues.
Error that shows up in the server side logs :
"An existing connection was forcibly closed by the remote host"
System.ServiceModel.Channels.SocketConnection.HandleReceiveAsyncCompleted()
System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(Object sender, SocketAsyncEventArgs eventArgs)
System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
server code:
public static void StartHost()
{
var startTime = DateTime.Now;
var duplex = new ServiceHost(typeof(HostChannel));
try
{
duplex.AddServiceEndpoint(typeof(IHostChannel),
CreateTpcBinding(),
AppConfiguration.SchedulerHostChannel);
duplex.Open();
_duplex = duplex;
}
catch (Exception e)
{
log.LogError("Error acquiring HostChannel:" + e);
if (duplex.State == CommunicationState.Faulted)
duplex.Abort();
duplex.Close();
throw;
}
}
private static NetTcpBinding CreateTpcBinding()
{
return new NetTcpBinding()
{
ReliableSession = new OptionalReliableSession() { InactivityTimeout = TimeSpan.FromDays(5) },
ReceiveTimeout = TimeSpan.FromDays(5),
ReaderQuotas = new XmlDictionaryReaderQuotas() { MaxStringContentLength = 5242880 }
};
}
/// <summary>
/// First method the client calls
/// </summary>
public void EstablishConnection()
{
var channelBinder = ServiceLocator.Current.GetInstance<RemoteResourceManager>();
var remoteChannel = OperationContext.Current.GetCallbackChannel<IRemoteChannel>();
_processHandle = channelBinder.RegisterRemoteChannel(remoteChannel);
}
/// <summary>
/// Last method that is called
/// </summary>
public void NotifyComplete()
{
_processHandle.Events.OnCompleted(_processHandle.RemoteChannel, new EventArgs());
log.LogInfo("Try to clean callback channel");
((IClientChannel)_processHandle.RemoteChannel).Close();
((IClientChannel)OperationContext.Current.GetCallbackChannel<IRemoteChannel>()).Close();
log.LogInfo("Cleaned callback channel");
}
Client callback interface :
public interface IRemoteChannel
{
[OperationContract(IsOneWay = true)]
void StartTask(TaskDefinition taskDefinition);
[OperationContract(IsOneWay = true)]
void RequestCancelTask();
[OperationContract(IsOneWay = true)]
void HealthCheck();
}
Client clean up code after calling NotifyComplete
if (_proxy is IClientChannel)
channel = ((IClientChannel)_proxy);
try
{
if (channel != null)
{
if (channel.State != CommunicationState.Faulted)
{
channel.Close();
}
else
{
channel.Abort();
}
}
}
catch (CommunicationException e)
{
channel.Abort();
}
catch (TimeoutException e)
{
channel.Abort();
}
catch (Exception e)
{
channel.Abort();
throw;
}
finally
{
_proxy = null;
}

Error "This message cannot support the operation because it has been read"

I have the same problem that is listed in the following thread.
WSDL first WCF server where client does not send SOAPAction
I performed the steps that are listed in the same thread (also shown below)
1) Download the Microsoft WCF examples.
Add the following files to your project from WF_WCF_Samples\WCF\Extensibility\Interop\RouteByBody\CS\service
DispatchByBodyOperationSelector.cs
DispatchByBodyBehaviorAttribute.cs
2) Add the following attributes to your interface (next to your ServiceContract)
XmlSerializerFormat
DispatchByBodyBehavior
3) Add the following to your service interface
[OperationContract(Action = "")]
public void DoNothing()
{
}
4) For my service the WrapperName and Wrappernamespace are null for all messages. I had to go into DispatchByBodyBehaviorAttribute and edit ApplyDispatchBehavior() to add the following lines to check for this:
if (qname.IsEmpty) {
qname = new XmlQualifiedName(operationDescription.Messages[0].Body.Parts[0].Name, operationDescription.Messages[0].Body.Parts[0].Namespace);
}
Now, I am getting an error message "This message cannot support the operation because it has been read". I turned the tracing on and captured the stack trace (below). If anyone has any idea on how this can be resolved, I appreciate if you could post some comments. Thanks for any help!
at System.ServiceModel.Channels.Message.GetReaderAtBodyContents()
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.Runtime.InputQueue`1.AsyncQueueReader.Set(Item item)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(T item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, Action callback)
at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult result)
at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContext(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
</StackTrace>
class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
{
Dictionary<XmlQualifiedName, string> dispatchDictionary;
public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName, string> dispatchDictionary)
{
this.dispatchDictionary = dispatchDictionary;            
}
#region IDispatchOperationSelector Members
private Message CreateMessageCopy(Message message, XmlDictionaryReader body)
{
//Message copy = Message.CreateMessage(message.Version, message.Headers.Action, body);
//copy.Headers.CopyHeaderFrom(message, 0);
//copy.Properties.CopyProperties(message.Properties);
//return copy;
MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);
Message copy = buffer.CreateMessage();
buffer.Close();
copy.Headers.CopyHeaderFrom(message, 0);
copy.Properties.CopyProperties(message.Properties);
return copy;
}
public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
XmlQualifiedName lookupQName = new XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
message = CreateMessageCopy(message,bodyReader);
if (dispatchDictionary.ContainsKey(lookupQName))
{
return dispatchDictionary[lookupQName];
}
else
{
return null;
}
}
#endregion
}
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
sealed class DispatchByBodyBehaviorAttribute : Attribute, IContractBehavior
{
#region IContractBehavior Members
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// no binding parameters need to be set here
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
// this is a dispatch-side behavior which doesn't require
// any action on the client
return;
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
// We iterate over the operation descriptions in the contract and
// record the QName of the request body child element and corresponding operation name
// to the dictionary to be used for dispatch 
Dictionary<XmlQualifiedName,string> dispatchDictionary = new Dictionary<XmlQualifiedName,string>();
foreach( OperationDescription operationDescription in contractDescription.Operations )
{
XmlQualifiedName qname =
new XmlQualifiedName(operationDescription.Messages[0].Body.WrapperName, operationDescription.Messages[0].Body.WrapperNamespace);
if (qname.IsEmpty)
{
qname = new XmlQualifiedName(operationDescription.Messages[0].Body.Parts[0].Name, operationDescription.Messages[0].Body.Parts[0].Namespace);
}
dispatchDictionary.Add(qname, operationDescription.Name);                
}
// Lastly, we create and assign and instance of our operation selector that
// gets the dispatch dictionary we've just created.
dispatchRuntime.OperationSelector = 
new DispatchByBodyElementOperationSelector(dispatchDictionary);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
// 
}
#endregion
}
You should use MessageBuffer.CreateMessage:
The body of a Message instance can only be consumed or written once.
If you wish to consume a Message instance more than once, you should
use the MessageBuffer class to completely store an entire Message
instance into memory.
http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messagebuffer.aspx
Code from my current project:
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
reply = buffer.CreateMessage();
Message m = buffer.CreateMessage();
LogMessage(m, " Response => ");
}
Add ref for Message param and return new message.
private Message CreateMessageCopy(ref Message message, XmlDictionaryReader body)
{
...
message = buffer.CreateMessage();
I had a very similar issue, using code from WCF Samples (RouteByBody to be precise) as well, and was able to solve it in a different way so I'll post it here in case it helps anybody.
Situation:
The client application (consumer) would work in Release, however, when the debugger was attached it would always fail with the error "This message cannot support the operation because it has been read".
After much tracing and logging WCF messages, the only solution that worked for me turned out to be so simple:
My Service was hosted on IIS, and with debug="true" in the <compilation> section of the web.config.
Changing it to debug="false" on the service fixed all my problems.
Dmitry Harnitski's answer does not work if you are debugging the service (It will give you a "This message cannot support the operation because it has been copied." error.)
This works even in debug mode:
XmlDictionaryReader GetReader(ref Message message)
{
MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);
message = buffer.CreateMessage();
newMessage = buffer.CreateMessage();
XmlDictionaryReader rv = buffer.CreateMessage().GetReaderAtBodyContents();
buffer.Close();
return rv;
}
static System.ServiceModel.Channels.Message newMessage = null;
static System.ServiceModel.Channels.Message lastMessage = null;
public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
try
{
if(message == lastMessage)
message = newMessage;
XmlDictionaryReader bodyReader = GetReader(ref message);
lastMessage = message;
XmlQualifiedName lookupQName = new XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
if (dispatchDictionary.ContainsKey(lookupQName))
{
return dispatchDictionary[lookupQName];
}
else
{
return null;
}
}
catch(Exception ex)
{
throw ex;
}
}

Implementing RequestWrapper for RESTful WCF service

I've written a simple object called RequestWrapper that contains single method of type:
TResult WrapRequest<TResult>(Func<TResult> action)
It wraps any action with try..catch, error handling, logging, database connection, transaction (commit & rollback), etc.
Currently I use it like this: (example, not production code)
return RequestWrapper.WrapRequest(() =>
{
Topic entity = GetRepository<Topic>().Find(uid);
if (entity == null)
throw new EntityNotFoundException("Topic not found.");
return new Topic
{
Name = entity.Name,
Posts = entity.Posts.Select(x => new Post
{
Body = x.Body,
}).ToList()
};
});
I simply wrap around every method of my RESTful web service (using WCF and WebHttpBinding).
My question is: How should I implement behavior that would do the wrapping for me automatically? Is it possible?
You can use a custom IOperationInvoker which wraps the original one does what you need. The code below shows a sample implementation of one, and you can find more information about invokers at http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/17/wcf-extensibility-ioperationinvoker.aspx.
public class StackOverflow_10156890
{
[ServiceContract]
public interface ITest
{
[WebGet]
[WrappedOperationBehavior]
string Echo(string text);
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[WrappedOperationBehavior]
int Divide(int x, int y);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
public int Divide(int x, int y)
{
return x / y;
}
}
class RequestWrapperOperationInvoker : IOperationInvoker
{
IOperationInvoker originalInvoker;
public RequestWrapperOperationInvoker(IOperationInvoker originalInvoker)
{
this.originalInvoker = originalInvoker;
}
public object[] AllocateInputs()
{
return this.originalInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
Console.WriteLine("Do initialization, etc. here");
object result = null;
try
{
result = this.originalInvoker.Invoke(instance, inputs, out outputs);
}
catch (Exception e)
{
Console.WriteLine("Log exception: {0}: {1}", e.GetType().FullName, e.Message);
result = null;
outputs = null;
}
finally
{
Console.WriteLine("Do finalization, etc. here");
}
return result;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new NotSupportedException("Only synchronous operations supported");
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotSupportedException("Only synchronous operations supported");
}
public bool IsSynchronous
{
get { return true; }
}
}
class WrappedOperationBehaviorAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new RequestWrapperOperationInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/Echo?text=Hello%20world"));
c = new WebClient();
c.Headers[HttpRequestHeader.ContentType] = "application/json";
Console.WriteLine(c.UploadString(baseAddress + "/Divide", "{\"x\":12,\"y\":0}"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

How do I implement Orchestration on WCF using MessageInspector

I have a WCF Service, And I inspect that using MessageInspector ( inherit IDispatchMessageInspector)
I want to do some thing before running service and result of that ı want not run service.
I want to prevent client call but client dont receive exception.
Can you help me
This scenario looks like the post in the MSDN WCF forum entitled "IDispatchMessageInspector.AfterReceiveRequest - skip operation and manually generate custom response instead". If this is what you need (when you receive a message in the inspector, you decide that you want to skip the service operation, but return a message to the client and the client should not see an exception), then this answer should work for you as well. Notice that you'll need to create the response message in the same format as the client expects, otherwise you'll have an exception.
This code uses three of the (many) WCF extensibility points to achieve that scenario, a message inspector (as you've mentioned you're using), a message formatter and an operation invoker. I've blogged about them in an ongoing series about WCF extensibility at http://blogs.msdn.com/b/carlosfigueira/archive/2011/03/14/wcf-extensibility.aspx.
public class Post_55ef7692_25dc_4ece_9dde_9981c417c94a
{
[ServiceContract(Name = "ITest", Namespace = "http://tempuri.org/")]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
return result;
}
public class MyOperationBypasser : IEndpointBehavior, IOperationBehavior
{
internal const string SkipServerMessageProperty = "SkipServer";
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(new MyInspector(endpoint));
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new MyFormatter(dispatchOperation.Formatter);
dispatchOperation.Invoker = new MyInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
class MyInspector : IDispatchMessageInspector
{
ServiceEndpoint endpoint;
public MyInspector(ServiceEndpoint endpoint)
{
this.endpoint = endpoint;
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Message result = null;
HttpRequestMessageProperty reqProp = null;
if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
{
reqProp = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
}
if (reqProp != null)
{
string bypassServer = reqProp.Headers["X-BypassServer"];
if (!string.IsNullOrEmpty(bypassServer))
{
result = Message.CreateMessage(request.Version, this.FindReplyAction(request.Headers.Action), new OverrideBodyWriter(bypassServer));
}
}
return result;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
Message newResult = correlationState as Message;
if (newResult != null)
{
reply = newResult;
}
}
private string FindReplyAction(string requestAction)
{
foreach (var operation in this.endpoint.Contract.Operations)
{
if (operation.Messages[0].Action == requestAction)
{
return operation.Messages[1].Action;
}
}
return null;
}
class OverrideBodyWriter : BodyWriter
{
string bypassServerHeader;
public OverrideBodyWriter(string bypassServerHeader)
: base(true)
{
this.bypassServerHeader = bypassServerHeader;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("EchoResponse", "http://tempuri.org/");
writer.WriteStartElement("EchoResult");
writer.WriteString(this.bypassServerHeader);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}
class MyFormatter : IDispatchMessageFormatter
{
IDispatchMessageFormatter originalFormatter;
public MyFormatter(IDispatchMessageFormatter originalFormatter)
{
this.originalFormatter = originalFormatter;
}
public void DeserializeRequest(Message message, object[] parameters)
{
if (message.Properties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
Message returnMessage = message.Properties[MyOperationBypasser.SkipServerMessageProperty] as Message;
OperationContext.Current.IncomingMessageProperties.Add(MyOperationBypasser.SkipServerMessageProperty, returnMessage);
OperationContext.Current.OutgoingMessageProperties.Add(MyOperationBypasser.SkipServerMessageProperty, returnMessage);
}
else
{
this.originalFormatter.DeserializeRequest(message, parameters);
}
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
if (OperationContext.Current.OutgoingMessageProperties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
return null;
}
else
{
return this.originalFormatter.SerializeReply(messageVersion, parameters, result);
}
}
}
class MyInvoker : IOperationInvoker
{
IOperationInvoker originalInvoker;
public MyInvoker(IOperationInvoker originalInvoker)
{
if (!originalInvoker.IsSynchronous)
{
throw new NotSupportedException("This implementation only supports synchronous invokers");
}
this.originalInvoker = originalInvoker;
}
public object[] AllocateInputs()
{
return this.originalInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
if (OperationContext.Current.IncomingMessageProperties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
outputs = null;
return null; // message is stored in the context
}
else
{
return this.originalInvoker.Invoke(instance, inputs, out outputs);
}
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new NotSupportedException();
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotSupportedException();
}
public bool IsSynchronous
{
get { return true; }
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
endpoint.Behaviors.Add(new MyOperationBypasser());
foreach (var operation in endpoint.Contract.Operations)
{
operation.Behaviors.Add(new MyOperationBypasser());
}
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
Console.WriteLine("And now with the bypass header");
using (new OperationContextScope((IContextChannel)proxy))
{
HttpRequestMessageProperty httpRequestProp = new HttpRequestMessageProperty();
httpRequestProp.Headers.Add("X-BypassServer", "This message will not reach the service operation");
OperationContext.Current.OutgoingMessageProperties.Add(
HttpRequestMessageProperty.Name,
httpRequestProp);
Console.WriteLine(proxy.Echo("Hello"));
}
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}