Here's the scenario: I'm trying to send a SOAP message to an intermediary router service. That service only cares about my SOAP message headers, and uses the WS-Addressing To header to forward along my message.
I need to basically POST a request like the following to the router service:
POST http://gatewayRouter/routingService HTTP/1.1
Content-Type: application/soap+xml; charset=utf-8
Host: gatewayRouter
Content-Length: 8786
Expect: 100-continue
Connection: Keep-Alive
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header> <!-- ... -->
<a:To s:mustUnderstand="1">http://actualDestination</a:To>
</s:Header> <!-- ... body, /envelope, etc --->
I'm currently able to set other custom headers that the routing service requires by using Custom Behaviors without a problem:
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
request.Headers.To = new Uri("http://actualDestination");
request.Headers.Add(new CustomHeader());
return null;
}
The above code works fine to add my CustomHeader to the message, but fails to modify the outgoing WS-Addressing To field - it always gets set back to the same URI as the HTTP POST value. In fact, I used .NET Reflector to debug when this field gets set- and sure enough, it is getting overwritten (screenshot of the stack trace and breakpoint).
Is there some other way for me to change the To SOAP header that I'm not understanding correctly?
I figured it out on my own with a hint from here. Programatically, I can set the Via on the ClientRuntime inside the custom behavior. This allows the POST to differ from the actual endpoint address that gets set automatically due to my usage of WSHttpBinding.
public void ApplyClientBehavior
(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
CustomMessageInspector inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
clientRuntime.Via = new Uri("http://gatewayRouter/routingService");
}
Related
I'm using an external SOAP service, the european National Medicines Verification Systems, over which I have no control. My application uses a WCF client automatically generated from the supplied WSDL. The client uses System.ServiceModel.Channels.Message messages.
The SOAP service uses the same message structure, an "I7RequestType", for 2 messages, the G482LoadTermsAndConditions and the G487LoadDataPrivacyPolicies, and does not "understand" any Action header.
This is my request message, with the ignored Action header :
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="0">ns:G482RequestMessage</a:Action>
</s:Header>
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<I7RequestType xmlns="urn:wsdltypes.nmvs.eu:v4.0">
<Header xmlns="urn:types.nmvs.eu:v4.0">
<Auth>
<ClientLoginId>xxxxx</ClientLoginId>
<UserId>xxxxx</UserId>
<Password>xxxxx</Password>
</Auth>
<UserSoftware xmlns:d5p1="urn:types.nmvs.eu:v4.0" d5p1:name="xxxxx" d5p1:supplier="xxxxx" d5p1:version="2.0" />
<Transaction>
<ClientTrxId>xxxxxxxxxxxxxxx</ClientTrxId>
<Language>fra</Language>
</Transaction>
</Header>
</I7RequestType>
</s:Body>
</s:Envelope>
The Header Action is ignored by the server, which returns a fault if mustUnderstand is set to 1.
Apparently I should be using the HTTP content-type to permit the server to differentiate the two messages. This is the example I have been given:
Content-Type : application/soap+xml;charset=utf-8;method=SUPPORT-I7RequestType;action="urn:G482LoadTermsAndConditions";
My custom MessageInspector can manipulate the message content in BeforeSendRequest(), but I cannot find any way of adding/modifying the encapsulating HTTP message's headers.
Does anybody know any way of modifying the HTTP headers of an outbound System.ServiceModel.Channels.Message ?
Thanks in advance.
After much searching the problem seems to be due to the generated WCF client not respecting the WSDL and the SOAP 1.2 specification.
This answer WCF Service with wsHttpBinding - Manipulating HTTP request headers gave me all that I needed to create a workaround.
This is the hard-coded proof-of-concept code, based very heavily on Dejan Janjušević's reply, that I added in the function BeforeSendRequest() of my CustomMessageInspector (implements IEndpointBehavior and IClientMessageInspector).
// Making sure we have a HttpRequestMessageProperty
HttpRequestMessageProperty httpRequestMessageProperty;
if( request.Properties.ContainsKey( HttpRequestMessageProperty.Name ) )
{
httpRequestMessageProperty = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
if( httpRequestMessageProperty == null )
{
httpRequestMessageProperty = new HttpRequestMessageProperty();
request.Properties.Add( HttpRequestMessageProperty.Name, httpRequestMessageProperty );
}
}
else
{
httpRequestMessageProperty = new HttpRequestMessageProperty();
request.Properties.Add( HttpRequestMessageProperty.Name, httpRequestMessageProperty );
}
Console.WriteLine("original Request:");
Console.WriteLine("{0}\r\n", request);
request.Headers.Clear();
httpRequestMessageProperty.Headers.Add( "Content-Type", "application/soap+xml;charset=utf-8;method=SUPPORT-I7RequestType;action=\"urn:G482LoadTermsAndConditions\"" );
Maybe i am mixing up some things but i can not find any questions or documentation on overloading actions for a wcf service recieving SOAP messages.
The goal: I have 3 SOAP messages coming in to my wcf service with the same actionname on the same endpoint. This is fixed and i can not change this.
I would excpect the following wcf interface would work:
[OperationContract(Action = "urn:oasis:names:tc:SPML:2:0:req/active", Name = "addRequest")]
void Add(data data);
[OperationContract(Action = "urn:oasis:names:tc:SPML:2:0:req/active", Name = "modifyRequest")]
void Modify(psoID psoID, modification modification);
[OperationContract(Action = "urn:oasis:names:tc:SPML:2:0:req/active", Name = "deleteRequest")]
void Delete(psoID psoID);
The problem: If i only have one operationalcontract like this my service works but if i have multiple operationalcontracts the following error will popup: `
500System.ServiceModel.ServiceActivationException
I believe it can not have multiple operational contracts with the same actionname. I also believe this should be possible because i am replacing a soap service that does handle all 3 messages with the same actionname. (wcf and soap shouldn't be that far appart?)
I added the operational names in order to fix the problem but without luck.
Any help would be appriciated. Thanks!
The Action property indicates the address the client request, which will be sent to the server and determines the method to be called on the server-side.
Here is a client request captured by the Fiddler.
POST http://10.157.13.69:21011/ HTTP/1.1
Content-Type: text/xml;charset=utf-8
SOAPAction: "urn:oasis:names:tc:SPML:2:0:req/active"
Host: 10.157.13.69:21011
Content-Length: 162
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
The SOAPAction HTTP header is the Action name of the operation.
The Name property determined the name of the practical method on the client-side.
ServiceReference1.ServiceClient client = new ServiceClient();
client.addRequest(23);
Thereby, unless we change the WCF web service from SOAP web service to Rest API, otherwise, this feature cannot be achieved because the SOAP web service addressing style depends on the Action field.
Namely, we need to change the service to Restful API by using Webhttpbinding.
[OperationContract(Action = "urn:oasis:names:tc:SPML:2:0:req/active", Name = "addRequest")]
[WebGet]
void Add(int data);
Feel free to let me know if there is anything I can help with.
This is how I'm currently (and successfully) connecting to a WCF web service in C#. I do not have any control over this web service as it's not developed by me, so I cannot change it. Here is the C# code I use:
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.NegotiateServiceCredential = true;
binding.Security.Message.AlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Default;
binding.Security.Message.EstablishSecurityContext = true;
EndpointAddress endpoint = new EndpointAddress("<address>");
fooClient client = new fooClient(binding, endpoint);
client.ClientCredentials.UserName.UserName = "the_username";
client.ClientCredentials.UserName.Password = "the_password";
//fooClient class came from running
// svcutil.exe https://<thedomain>/foo/foo.svc?wsdl
//I now work with fooClient, call methods on it, etc.
I want to connect to the web service without C# - by manually creating a SOAP envelope and doing a POST request on the endpoint. I tried doing a POST request that looks like this:
POST /foo/foo.svc HTTP/1.1
Content-Type: application/soap+xml; charset=utf-8
Host: <thedomain>
Connection: close
User-Agent: <my user agent>
Content-Length: 416
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<someWebServiceFunction xmlns="http://<thedomain>/foo/foo">
<someParameter>some parameter value</someParameter>
</someWebServiceFunction>
</soap12:Body>
</soap12:Envelope>
But this does not work because the credentials are missing. (I get an error back: "BadContextToken", "The security context token is expired or is not valid. The message was not processed.")
My question is, how do I add credentials to my SOAP envelope / HTTP request? I tried doing plain HTTP Basic Auth (in the Authorization HTTP header), but this continues to give me the same "BadContextToken" error.
There are two simple ways to trouble shoot your issue:
If you are using Visual Studio, in debug, send the request and intercept what is the detailed content in the request, and you can simulate it using plain HTTP POST.
If you can use SoapUI, you can target the service using SoapUI and send one working request, in the raw tab, you will see what's the accepted request with credentials.
I'm using the HttpClient class to send some data to specific host. I just want to send a pure header without any additional lines in it like ("Host: http"). So this line is the last to be removed from the header, but I don't know how.
The code:
HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Post, aUrl);
msg.Headers.Clear();
msg.Headers.Remove("Host");
msg.Headers.ExpectContinue = false;
Encoding encoding = ConfiguratorASUST.Instance.Encoding ?? Encoding.GetEncoding(ConfiguratorASUST.ENCODING_DEFAULT);
msg.Content = new StringContent(aStr, encoding);
_client.SendAsync(msg);
The result header in Fiddler:
POST http://http//localhost.fiddler:60001/POS/POSTELESPIS HTTP/1.1
Content-Type: text/plain; charset=windows-1251
Host: http
This line Host: http needs to be removed from the message's header. But how on earth can I do that?! I tried the following:
msg.Headers.Clear();
msg.Headers.Remove("Host");
To no avail. Actually I also see the header Proxy-Connection: Keep-Alive being added.
If you carefully inspect your URL, it looks like your it is wrong anyway: http://http// - is your host really named http, and do you really need two slashes after it? Anyway if you fix that, the Host header will carry localhost.fiddler:60001.
By removing the Host header, you're essentially downgrading your request to HTTP/1.0.
You can set the HTTP version in the HttpRequestMessage as explained in Set HTTP protocol version in HttpClient:
msg.Version = HttpVersion.Version10;
But when using Fiddler, it acts as a proxy, and forwards your request as an HTTP/1.1 request - including the host header again. You can also alter the request in Fiddler. This is explained in How do I prevent fiddler from insering "Host" HTTP header?, but note the bold text, emphasis mine:
Per the RFC, as a HTTP/1.1 proxy, Fiddler is required to add a Host header.
It's not clear why this is problematic-- any server that has a problem with this is, by definition, buggy and should be fixed.
You can remove the header if you'd like (although doing so can cause problems elsewhere). Click Rules > Customize Rules. Scroll to OnBeforeRequest and add the following:
if (oSession.oRequest.headers.HTTPVersion == "HTTP/1.0")
{
oSession["x-overridehost"] = oSession.host;
oSession.oRequest.headers.Remove("Host");
}
I'm writing two small pieces of C# code. The first is for a client-side Portable Class Library. All it does is send messages to an Azure Service Bus topic via the Azure Service Bus REST API, using HttpClient.
I populate the BrokerProperties header on the REST call with valid JSON, and I expect that on the server side, when I receive the message through a subscription, that I'll get my instance of BrokeredMessage.Properties populated with the values I sent from the client.
The one problem I've had on this side is that the documentation says to set Content-Type to application/atom+xml;type=entry;charset=utf-8, but even when I do I get application/json; charset=utf-8, so I'm just using application/json.
With that aside, as far as I can tell, this does what it's supposed to do. It creates the client and the request message, sets the headers, and sends the message. I get a 201 Created every time. Here's all of it:
private async static void SendServiceBusMessage(Command command)
{
// Create the HttpClient and HttpRequestMessage objects
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, topicUri);
// Add the authorization header (CreateAuthToken does the SHA256 stuff)
request.Headers.Add("Authorization", CreateAuthToken(topicUri, authSasKeyName, authSasKey));
// Add the content (command is a normal POCO)
// I've tried application/atom+xml;type=entry;charset=utf-8, always see application/json in the request
request.Content = new StringContent(JsonConvert.SerializeObject(command), Encoding.UTF8, "application/json");
// Add the command name and SessionId as BrokeredMessage properties
var brokeredMessageProperties = new Dictionary<string, string>();
brokeredMessageProperties.Add("CommandName", command.GetType().Name);
brokeredMessageProperties.Add("SessionId", Guid.NewGuid().ToString());
// Add the BrokerProperties header to the request
request.Content.Headers.Add("BrokerProperties", JsonConvert.SerializeObject(brokeredMessageProperties));
// I've also tried adding it directly to the request, nothing seems different
// request.Headers.Add("BrokerProperties", JsonConvert.SerializeObject(brokeredMessageProperties));
// Send it
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
// Do some error-handling
}
}
and here's an example of the HTTP request it sends. Compare it to the example at the bottom of Send Message documentation... aside from the Content-Type, it looks (functionally) identical to me.
POST https://myawesomesbnamespace.servicebus.windows.net/commands/messages HTTP/1.1
Authorization: SharedAccessSignature sr=https%3A%2F%2Fmyawesomesbnamespace.servicebus.windows.net%2Fcommands%2Fmessages&sig=SomeValidAuthStuffHere
Content-Type: application/json; charset=utf-8
BrokerProperties: {"CommandName":"CreateJob_V1","SessionId":"94932660-54e9-4867-a020-883a9bb79fa1"}
Host: myawesomesbnamespace.servicebus.windows.net
Content-Length: 133
Expect: 100-continue
Connection: Keep-Alive
{"JobId":"6b76e7e6-9499-4809-b762-54c03856d5a3","Name":"Awesome New Job Name","CorrelationId":"47fc77d9-9470-4d65-aa7d-690b65a7dc4f"}
However, when I receive the message on the server, the .Properties are empty. This is annoying.
The server code looks like this. It just gets a batch of messages and does a foreach loop.
private async Task ProcessCommandMessages()
{
List<BrokeredMessage> commandMessages = (await commandsSubscriptionClient.ReceiveBatchAsync(serviceBusMessageBatchSize, TimeSpan.FromMilliseconds(waitTime_ms))).ToList();
foreach (BrokeredMessage commandMessage in commandMessages)
{
// commandMessage.Properties should have CommandName and SessionId,
// like I sent from the client, but it's empty
// that's not good
if (commandMessage.Properties.ContainsKey("CommandName"))
{
string commandName = commandMessage.Properties["CommandName"] as string;
// Do some stuff
}
else
{
// This is bad, log an error
}
}
}
So, I'm a bit stuck. Can anyone spot something I'm doing wrong here? Maybe it's the Content-Type problem and there's a way around it?
Thanks!
Scott
Seattle, WA, USA
OK, finally getting back to this. What I misunderstood (and I'd argue the documentation isn't clear about) is that arbitrary properties cannot be passed through the BrokerProperties header. Only named properties from the BrokeredMessage class (like SessionId, Label, etc.) will come through Service Bus to the server.
For properties to show up in BrokeredMessage.Properties, they have to be passed as custom headers on the request. So, in my case,
request.Headers.Add("CommandName", command.GetType().Name);
gets the CommandName property to show up on the server after the message is passed through Service Bus.
And to pass the SessionId value, I'll still want to pass it through BrokerProperties header.