I have a custom header stored in a "string" variable, I need to replace the header of a outgoing SOAP request from my WCF client with the header in the "string" type variable. Based on research I see that implementing the MessageHeaders.WriteHeaderContents can work but this method accepts only XmlDictionaryWriter or XmlWriter types as input. I have a string input. How do I code in C# ..
Message headers are a SOAP concept, and SOAP requests are XML documents, so you really need some XML-ness there. But for your scenario, you actually don't need to override MessageHeaders, you can use an inspector and simply replace the header at that point, as shown in the example below (the conversion to XML will be done by the MessageHeader class).
public class StackOverflow_7141998
{
[MessageContract]
public class MyMC
{
[MessageHeader(Name = "MyHeader", Namespace = "http://my.namespace.com")]
public string HeaderValue { get; set; }
[MessageBodyMember(Name = "MyBody", Namespace = "http://my.namespace.com")]
public string BodyValue { get; set; }
}
[ServiceContract]
public interface ITest
{
[OperationContract]
void Process(MyMC mc);
}
public class Service : ITest
{
public void Process(MyMC mc)
{
Console.WriteLine("Header value: {0}", mc.HeaderValue);
}
}
public class MyInspector : IEndpointBehavior, IClientMessageInspector
{
public string NewHeaderValue { get; set; }
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
int originalIndex = request.Headers.FindHeader("MyHeader", "http://my.namespace.com");
if (originalIndex >= 0)
{
request.Headers.Insert(originalIndex, MessageHeader.CreateHeader("MyHeader", "http://my.namespace.com", this.NewHeaderValue));
request.Headers.RemoveAt(originalIndex + 1);
}
return null;
}
}
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), new WSHttpBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WSHttpBinding(), new EndpointAddress(baseAddress));
MyInspector inspector = new MyInspector { NewHeaderValue = "Modified header value" };
factory.Endpoint.Behaviors.Add(inspector);
ITest proxy = factory.CreateChannel();
proxy.Process(new MyMC { HeaderValue = "Original header value", BodyValue = "The body" });
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Related
A few days ago I opened a question if I succeed with the answers. I had not focused the question well, and now with something more knowledge I ask again.
I need to capture the errors of all my endpoints to have them included in the same site. The idea is to add a behavior to these endpoints.
namespace SIPE.Search.Helpers
{
/// <summary>
/// Implements methods that can be used to extend run-time behavior for an endpoint in either a client application.
/// </summary>
public class ExternalClientBehavior : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ExternalClientBehaviorClass();
}
public override Type BehaviorType
{
get
{
return typeof(ExternalClientBehaviorClass);
}
}
/// <summary>
/// JSON REST[GET] Converter Behavior
/// </summary>
private class ExternalClientBehaviorClass : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
ExternalClientMessageInspector clientInspector = new ExternalClientMessageInspector(endpoint);
clientRuntime.MessageInspectors.Add(clientInspector);
foreach (ClientOperation op in clientRuntime.Operations)
{
op.ParameterInspectors.Add(clientInspector);
}
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
//("Behavior not supported on the consumer side!");
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
}
}
Now I know that it will never enter my ApplyDispatchBehaviour if the client does not implement my behaviour, and this will NEVER happen, since they are external providers and I do not have access to the code.
Also, my first error does not even leave my code, since I'm causing a NOT FOUND error.
I have found many similar sources with my problem without solution. I have found by several sites to add the following in ApplyClientBehaviour:
IErrorHandler errorHandler = new CustomErrorHandler();
clientRuntime.CallbackDispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(errorHandler);
But this does not work.
Other sources that happened to me: https://riptutorial.com/csharp/example/5460/implementing-ierrorhandler-for-wcf-services
It is NOT a solution, since it is for Services Behavior. I need to do it in EndPoint Behavior.
Thank you
Please refer to the following example.
Server side.
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:1100");
BasicHttpBinding binding = new BasicHttpBinding();
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri("http://localhost:1100/mex");
sh.Description.Behaviors.Add(smb);
}
MyEndpointBehavior bhv = new MyEndpointBehavior();
se.EndpointBehaviors.Add(bhv);
sh.Open();
Console.WriteLine("service is ready");
Console.ReadKey();
sh.Close();
}
}
}
[ServiceContract(ConfigurationName = "isv")]
public interface IService
{
[OperationContract]
string Delete(int value);
[OperationContract]
void UpdateAll();
}
[ServiceBehavior(ConfigurationName = "sv")]
public class MyService : IService
{
public string Delete(int value)
{
if (value <= 0)
{
throw new ArgumentException("Parameter should be greater than 0");
}
return "Hello";
}
public void UpdateAll()
{
throw new InvalidOperationException("Operation exception");
}
}
public class MyCustomErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, error.Message);
}
}
public class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
MyCustomErrorHandler myCustomErrorHandler = new MyCustomErrorHandler();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(myCustomErrorHandler);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
Client.
static void Main(string[] args)
{
ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
try
{
client.Delete(-3);
}
catch (FaultException fault)
{
Console.WriteLine(fault.Reason.GetMatchingTranslation().Text);
}
}
Result.
Feel free to let me know if there is anything I can help with.
I have a legacy app that logs the input / output of services. Currently, every method has the same lines to log the request and response objects. I would like to use AOP, but without adding any extra tool (Postsharp, Castle, etc), or wrap every service class into another class (ServiceWrapper).
In order to do that, I'm trying to create a Generic class that knows that it should log the request and response objects. Here's what I'm trying:
using System;
namespace ProxyTest
{
class Program
{
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(r => fooService.DoFoo(request), "abc");
Console.Read();
}
}
class ServiceProxy
{
public static void Invoke(Func<object, object> service, object request)
{
Console.WriteLine("input:" + request);
var response = service(request);
Console.WriteLine("output:" + response);
}
}
class FooService
{
public string DoFoo(object a)
{
return a + ": returning: Do Foo";
}
}
}
Although it's working, the "abc" string is just to compile the application, but it's not being used as the request parameter. If I remove that, the code does not compile. Am I missing something?
UPDATE
Changing to the following did the trick:
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(r => fooService.DoFoo(r), request);
Console.Read();
}
You should call it like this:
class Program
{
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(fooService.DoFoo, "abc"); // lose the DoFoo parameter.
Console.Read();
}
}
You should pass the DoFoo as Func, instead of calling it. Also you should change the method signature to:
class FooService
{
public object DoFoo(object a)
{
return a + ": returning: Do Foo";
}
}
For this task you can just add logging behavior on dispatcher.
First, you create ServiceBehavior with such content:
public class ServiceLoggingBehavior : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
IOperationBehavior behavior = new LoggingOperationBehavior();
operation.Behaviors.Add(behavior);
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
Then you need to create operation behavior:
internal class LoggingOperationBehavior : IOperationBehavior
{
public void Validate(OperationDescription operationDescription)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new LoggingOperationInvoker(dispatchOperation.Invoker, dispatchOperation);
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
}
And finally create invoker for all methods on server side:
internal class LoggingOperationInvoker : IOperationInvoker
{
private readonly IOperationInvoker _baseInvoker;
private readonly string _operationName;
public LoggingOperationInvoker(IOperationInvoker baseInvoker, DispatchOperation operation)
{
_baseInvoker = baseInvoker;
_operationName = operation.Name;
}
public bool IsSynchronous
{
get { return _baseInvoker.IsSynchronous; }
}
public object[] AllocateInputs()
{
return _baseInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
var sw = new Stopwatch();
try
{
LogBegin();
sw.Start();
var response = _baseInvoker.Invoke(instance, inputs, out outputs);
return response;
}
finally
{
sw.Stop();
LogEnd(sw.Elapsed);
}
}
private void LogBegin()
{
//you can log begin here.
}
private void LogEnd(TimeSpan elapsed)
{
//you can log end here.
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
return _baseInvoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
return _baseInvoker.InvokeEnd(instance, out outputs, result);
}
}
If you want to log request, you can just serialize and log inputs variable in Invoke method. For response - just serialize and log response variable.
And finaly, most enjoyable part, just attach it like attribute:
[ServiceLoggingBehavior]
public MyService : IMyServiceContract
{
...
}
Your Invoke-method clearly asks for a Func- and an object-parameter, so you have to provide both. No idea what exactly you´re expecting when you omit one of the params. I assume you want to make the Func to return the response created by a specific request-object. Furthermore it might be good idea to make your request- and response-arguments generic:
class Program
{
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(r => fooService.DoFoo(r), request);
Console.Read();
}
}
class ServiceProxy
{
public static void Invoke<TRequest, TResponse>(Func<TRequest, TResponse> service, TRequest request)
{
Console.WriteLine("input:" + request.ToString());
var response = service(request);
Console.WriteLine("output:" + response.ToString());
}
}
The Invoke-call can further be simplyfied to ServiceProxy.Invoke(fooService.DoFoo, request);
Thanks for all the responses. I was able to achieve what I was looking for using:
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(fooService.DoFoo, request);
Console.Read();
}
or
static void Main(string[] args)
{
var request = "request";
var fooService = new FooService();
ServiceProxy.Invoke(r => fooService.DoFoo(r), request);
Console.Read();
}
I want to write a C# code which calls a (remote) web service in another machine. For this I have to pass username and password in the SOAP header of the call.
I would like to know an example of code to make this in C#.
the produced XML should be like :
<env:Header>
<ns1:Security>
<ns1:UsernameToken>
<ns1:Username>XXXXXXXXXXXXXXXX</ns1:Username>
<ns1:Password>YYYYYYYYYYYYYYYY</ns1:Password>
</ns1:UsernameToken>
</ns1:Security>
</env:Header>
Thanks in advance
J.
Are many ways to do that. The CustomBinding is more flexible because it allow more controll, for that i propose you with that. Pasing header to endpoint is a simple way:
// binding
var binding = new CustomBinding();
binding.Elements.Clear();
binding.Elements.Add(new TextMessageEncodingBindingElement{MessageVersion = MessageVersion.Soap12});
binding.Elements.Add(new HttpTransportBindingElement{MaxReceivedMessageSize = 20000000,});
// endpoint
var endpoint = new EndpointAddress(new Uri(listeningUri), new MySecurityHeader())
var client = new Client(binding, endpoint);
client.SomeMethod();
where MySecurityHeader is an AddressHeader, for example:
public class MySecurityHeader : AddressHeader
{
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "<provide the appropiate namespace>"; }
}
protected override void OnWriteAddressHeaderContents(System.Xml.XmlDictionaryWriter writer)
{
// here you do what you want
writer.WriteRaw(String.Format(#"
<UsernameToken xmlns=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<Username>user</Username>
<Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"">pass</Password>
</UsernameToken>").Trim());
}
}
this is an example using an IEndpointAdress
var customBinding = new CustomBinding();
customBinding.Elements.Add(new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap12, });
customBinding.Elements.Add(new HttpTransportBindingElement { MaxReceivedMessageSize = 20000000, });
var endpointAddres = new EndpointAddress(listeningUri);
var client = new Client(customBinding, endpointAddres);
// add my own IEndpointBehavior
client.ChannelFactory.Endpoint.Behaviors.Add(new CustomBehavior());
client.SomeMethod();
and this is the CustomBehavior definition
public class CustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
var inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{}
public void Validate(ServiceEndpoint endpoint)
{}
}
public class CustomMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(new MyMessageHeader());
return null;
}
}
public class MyMessageHeader : MessageHeader
{
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteRaw(String.Format(#"
<UsernameToken xmlns=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<Username>user</Username>
<Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"">pass</Password>
</UsernameToken>").Trim());
}
public override string Name
{
get { return "MyHeaderName"; }
}
public override string Namespace
{
get { return "MyHeaderNamespace"; }
}
}
Note you have control before send the request and after receive your reply.
I hope this resolve your issue, if you have some problems with this yust ask me.
Can I reference the proxy client instance from a client message inspector?
The reason, I'd like to access the values of the following properties:
ClientCredentials.UserName.UserName
ClientCredentials.UserName.Password
Thanks
I managed to retrieve the credentials from within the inspector by passing a reference to "ClientCredentials" from my custom EndpointBehavior:
CustomBehaviour:
public class CustomEndpointBehaviour:IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
ClientCredentials credentials = endpoint.Behaviors.Find<ClientCredentials>();
clientRuntime.MessageInspectors.Add(new CustomMessageInspector(credentials));
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
And inspector:
public class CustomMessageInspector : IClientMessageInspector
{
ClientCredentials crendentials = null;
public CustomMessageInspector(ClientCredentials credentials)
{
this.crendentials = credentials;
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
string userName = "";
string passWord = "";
if (!(crendentials == null))
{
userName = crendentials.UserName.UserName;
passWord = crendentials.UserName.Password;
}
return null;
}
}
Is it possible to change the data of incoming HTTP request in WCF?
I've already found out how to change HTTP method (using IDispatchOperationSelector and HttpRequestMessageProperty of the incoming message).
I'm writing behavior that would enable to make "POST" requests using GET requests (with method and data stored in query string). I can override the HTTP method, but I can't find the solution to override the data. I need to load the data stored in query string and use them as HTTP body.
Any thoughts?
You'll need to recreate the incoming message so that the message body contains the information you want to pass. The body will likely be in either XML or JSON format (supported out-of-the-box). The code below shows one example of how this can be done.
public class StackOverflow_10391354
{
[ServiceContract]
public class Service
{
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public int Add(int x, int y)
{
return x + y;
}
}
class MyWebHttpBehavior : WebHttpBehavior
{
protected override WebHttpDispatchOperationSelector GetOperationSelector(ServiceEndpoint endpoint)
{
return new MyOperationSelector();
}
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
}
}
class MyOperationSelector : WebHttpDispatchOperationSelector
{
protected override string SelectOperation(ref Message message, out bool uriMatched)
{
HttpRequestMessageProperty prop = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
if (message.Headers.To.LocalPath.EndsWith("/Add") && prop.Method == "GET")
{
prop.Method = "POST";
uriMatched = true;
message = CreateBodyMessage(message);
return "Add";
}
else
{
return base.SelectOperation(ref message, out uriMatched);
}
}
private Message CreateBodyMessage(Message message)
{
HttpRequestMessageProperty prop = message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
string queryString = prop.QueryString;
NameValueCollection nvc = HttpUtility.ParseQueryString(queryString);
StringBuilder sb = new StringBuilder();
sb.Append('{');
bool first = true;
foreach (string key in nvc.Keys)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
sb.Append('\"');
sb.Append(key);
sb.Append("\":\"");
sb.Append(nvc[key]);
sb.Append('\"');
}
sb.Append('}');
string json = sb.ToString();
XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), XmlDictionaryReaderQuotas.Max);
Message result = Message.CreateMessage(MessageVersion.None, null, jsonReader);
result.Properties.Add(HttpRequestMessageProperty.Name, prop);
result.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
result.Headers.To = message.Headers.To;
return result;
}
}
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(Service), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new MyWebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/Add?x=66&y=88"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
You can use a message inspector: http://weblogs.asp.net/paolopia/archive/2007/08/23/writing-a-wcf-message-inspector.aspx