I tried to emit an http header but somehow wcf filters it.
[ServiceContract()]
public interface IHelloWorld
{
[OperationContract(Action = "*", IsOneWay = false, ReplyAction = "*")]
void Hello(string text);
}
var channel = new ChannelFactory<IHelloWorld>(new WebHttpBinding(), "http://some.where");
channel.Endpoint.Behaviors.Add(new WebHttpBehavior());
var proxy = channel.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
MessageProperties messageProperties = OperationContext.Current.OutgoingMessageProperties;
var requestMessageProperty = new HttpRequestMessageProperty();
messageProperties.Add(HttpRequestMessageProperty.Name, requestMessageProperty);
requestMessageProperty.Headers.Add("SOAPAction", "test");
requestMessageProperty.Headers.Add("Test", "test2");
proxy.Hello("test");
}
When testing this code, the header Test is in the request but SOAPAction is not.
I tried with a IClientMessageInspector but it doesn't work either.
I cannot use another binding (Basic or a more Soap dedicated one).
As far as I know the SOAPAction field is the HTTP header default field and is used to indicate that the action method being invoked when server service uses a non-webhttpbinding.
I don't think it could be arbitrarily specified by intercepting the message/operation context.
Related
I have a customer who requires TLS 1.2 for PCI compliance. Xamarin Android does not support TLS 1.2 very well. According to this
Native HttpClientHandler and this Transport Layer Security, you can either use HttpClient with their special mechanism to access the native Java support on Android 5 and higher, or you can use the ModernHttpClient.
However, WCF SOAP Webservice proxies generated with SvcUtil appear to use HttpWebRequest, and not HttpClient.
What's the recommended way to call WCF SOAP services using HttpClient (or ModernHttpClient)? Will I have to manually write my own interfaces or can I use the proxy classes and serialize/deserialize them myself? I'd rather not have to completely start from scratch, especially since it looks like TLS 1.2 is currently being added to Mono.
i have used that type of service and it is working here i have share relevant code please try it.
static void TryByWebRequest(string soapMethod)
{
XmlDocument soapEnvelopeXml = new XmlDocument();
soapEnvelopeXml.LoadXml(#"
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<" + soapMethod + #"
xmlns=""your URI""
xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<InputXMLString>
" +
System.Security.SecurityElement.Escape(inputXML)
+ #"
</InputXMLString>
<OutputXMLString/>
</" + soapMethod + #">
</s:Body>
</s:Envelope>");
using (Stream stream = request.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
using (WebResponse response = request.GetResponse())
{
using (StreamReader rd = new StreamReader(response.GetResponseStream()))
{
string soapResult = rd.ReadToEnd();
Console.WriteLine(soapResult);
}
}
}
static HttpWebRequest CreateWebRequest(string soapMethod)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(#"Your .asmx URL ");
webRequest.Headers.Add(#"SOAPAction", "your URI \" + soapMethod);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
return webRequest;
}
I got this working. Since this is a (hopefully) temporary solution, my goal was to create drop-in replacements for the Proxy-generated classes, and I got pretty close. The key was to figure out how to use the DataContractSerializer to create a SOAP envelope to send, and deserialize the results.
I was successful with everything except for the serialization of the SOAP envelope that gets sent to the web service. I ended up manually wrapping the XML in the <envelope> and <body> tags. Nothing that I did could get the DataContractSerializer to create these correctly, although the Body contents were ok. The Deserializer was able to handle the response from the web service without any problem though. WCF services are very picky about the format of the SOAP envelope, and getting the classes annotated just right was a challenge.
For each function call, I had to create a Request object that wraps the parameters being sent to the web service, and a Response object that wraps the out parameters and the return code.
These look something like this, where the FunctionName is the name of the WCF function call that the proxys generated.
// request envelope
[System.Runtime.Serialization.DataContractAttribute(Name = "FunctionName", Namespace = "http://tempuri.org/")]
public class FunctionName_Request
{
[System.Runtime.Serialization.DataMemberAttribute()]
public NameSpaceFunctionNameObject1 CallingObject1;
[System.Runtime.Serialization.DataMemberAttribute()]
public NameSpaceFunctionNameObject2 CallingObject2;
}
// response envelope
[System.Runtime.Serialization.DataContractAttribute(Name = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class FunctionName_ResponseEnvelope
{
[System.Runtime.Serialization.DataContractAttribute(Name = "Body", Namespace = "http://tempuri.org/")]
public class FunctionName_ResponseBody
{
[System.Runtime.Serialization.DataContractAttribute(Name = "FunctionNameResponse", Namespace = "http://tempuri.org/")]
public class FunctionName_Response
{
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionNameReturnCodes Result;
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionNameResponseObject Response;
}
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionName_Response FunctionNameResponse;
}
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionName_ResponseBody Body;
}
Then, I can write a replacement function that my client code can call, which has exactly the same signature as the original Proxy-generated function.
// FunctionName
public FunctionNameReturnCodes FunctionName(out FunctionNameResponseObject Response, NameSpaceFunctionNameObject1 CallingObject1, NameSpaceFunctionNameObject2 CallingObject2)
{
// create the request envelope
FunctionName_Request req = new FunctionName_Request();
req.CallingObject1 = CallingObject1;
req.CallingObject2 = CallingObject2;
// make the call
FunctionName_ResponseEnvelope resp = MakeWCFCall<FunctionName_ResponseEnvelope>(_EndpointAddress, _ServerName, req);
// get the response object
Response = resp.Body.FunctionName_Response.Response;
// result
return resp.Body.FunctionName_Response.Result;
}
Finally, this is the function that actually serializes and deserializes the object into the HttpClient. In my case, these are synchronous, but you could easily adapt this to work in the standard async case. It's template so it works with any of the proxy-generated types.
/////////////////////////////////////////////////////////////////////
// make a WCF call using an HttpClient object
// uses the DataContractSerializer to serialze/deserialze the messages from the objects
//
// We manually add the <s:Envelope> and <s:Body> tags. There should be a way to get
// the DataContractSerializer to write these too, but everything I tried gave a message
// that was not able to be procesed by the service. This includes the Message object.
// Deserializing works fine, but serializing did not.
private T MakeWCFCall<T>(string strEndpoint, string strServerName, object SourceObject)
{
T Response = default(T);
string strSoapMessage = "";
string strSoapAction = "";
// get the Soap Action by using the DataContractAttribute's name
// start by getting the list of custom attributes.
// there should only be the one
object[] oaAttr = SourceObject.GetType().GetCustomAttributes(false);
if (oaAttr.Length > 0)
{
// iterate just in case there are more
foreach (DataContractAttribute oAttr in oaAttr)
{
// make sure we got one
if (oAttr != null)
{
// this is the action!
strSoapAction = oAttr.Name;
break;
}
}
}
// serialize the request into a string
// use a memory stream as the source
using (MemoryStream ms = new MemoryStream())
{
// create the DataContractSerializer
DataContractSerializer ser = new DataContractSerializer(SourceObject.GetType());
// serialize the object into the memory stream
ser.WriteObject(ms, SourceObject);
// seek to the beginning so we can read back out of the stream
ms.Seek(0, SeekOrigin.Begin);
// create the stream reader
using (var streamReader = new StreamReader(ms))
{
// read the message back out, adding the Envelope and Body wrapper
strSoapMessage = #"<s:Envelope xmlns:s = ""http://schemas.xmlsoap.org/soap/envelope/""><s:Body>" + streamReader.ReadToEnd() + #"</s:Body></s:Envelope>";
}
}
// now create the HttpClient connection
using (var client = new HttpClient(new NativeMessageHandler()))
{
//specify to use TLS 1.2 as default connection
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
// add the Soap Action header
client.DefaultRequestHeaders.Add("SOAPAction", "http://tempuri.org/" + strServerName + "/" + strSoapAction);
// encode the saop message
var content = new StringContent(strSoapMessage, Encoding.UTF8, "text/xml");
// post to the server
using (var response = client.PostAsync(new Uri(strEndpoint), content).Result)
{
// get the response back
var soapResponse = response.Content.ReadAsStringAsync().Result;
// create a MemoryStream to use for serialization
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(soapResponse)))
{
// create the reader
// set the quotas
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(
memoryStream,
Encoding.UTF8,
new XmlDictionaryReaderQuotas() { MaxArrayLength = 5000000, MaxBytesPerRead = 5000000, MaxStringContentLength = 5000000 },
null);
// create the Data Contract Serializer
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
// deserialize the response
Response = (T)serializer.ReadObject(reader);
}
}
}
// return the response
return Response;
}
This approach allowed me to quickly write wrappers for all of my WCF service functions, and it's working well so far.
I am trying to use DataContractResolver as an alternative to KnownTypes in WCF.
I have the following code and I've used it before on the server side. But on the client side, the code returns null when trying to find DataContractSerializerOperationBehavior in operation behaviors collection.
public override IMyService CreateProxy(Uri url)
{
ServiceEndpoint endpoint = CreateEndpoint(url);
var channelFactory = new ChannelFactory<IMyService>(endpoint);
InjectResolver(channelFactory.Endpoint);
return channelFactory.CreateChannel();
}
private void InjectResolver(ServiceEndpoint endpoint)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
var behavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new DerivedTypeResolver(); // behavior is null here!
}
}
Why is the behavior missing?
UPDATE: I found out the real issue is that WCF was using XmlSerializer instead of DataContractSerializer. Is there a way to force a DataContractSerializer instead? Does WCF choose the serializer based on the wsdl? Considering I don't (yet) have the capacity to change the server side, what is my option? XmlSerializer behavior doesn't seem to have a similar option of resolving the type myself.
See here for example on how to create DataContractSerializerOperationBehavior if it does not exist:
private void DataContractBehavior()
{
WSHttpBinding b = new WSHttpBinding(SecurityMode.Message);
Uri baseAddress = new Uri("http://localhost:1066/calculator");
ServiceHost sh = new ServiceHost(typeof(Calculator), baseAddress);
sh.AddServiceEndpoint(typeof(ICalculator), b, "");
// Find the ContractDescription of the operation to find.
ContractDescription cd = sh.Description.Endpoints[0].Contract;
OperationDescription myOperationDescription = cd.Operations.Find("Add");
// Find the serializer behavior.
DataContractSerializerOperationBehavior serializerBehavior =
myOperationDescription.Behaviors.
Find<DataContractSerializerOperationBehavior>();
// If the serializer is not found, create one and add it.
if (serializerBehavior == null)
{
serializerBehavior = new DataContractSerializerOperationBehavior(myOperationDescription);
myOperationDescription.Behaviors.Add(serializerBehavior);
}
// Change the settings of the behavior.
serializerBehavior.MaxItemsInObjectGraph = 10000;
serializerBehavior.IgnoreExtensionDataObject = true;
sh.Open();
Console.WriteLine("Listening");
Console.ReadLine();
}
example from https://msdn.microsoft.com/en-us/library/system.servicemodel.description.datacontractserializeroperationbehavior.aspx
I have created a ServiceStack service on top of Asp.Net that implements Basic authentication. Everything is working fine on the service routes. I am able to login and I get the session cookies which are validated on subsequent calls. I'm using an HttpClient for those requests.
I also have a SignalR Hub that runs on the same Asp.Net service, but the Principal is not authenticated on my Hub methods.
Basically what I need is for ServiceStack to intercept calls into my Hub and validate the session cookie and populate the Context.User.Identity and mark it as authenticated. If I can get that set up, a simple [Authorize] attribute on my hub will do the rest.
Here is a sample of my code:
// set up a HttpClient with a cookie container to hold the session cookie
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cookieJar, UseCookies = true, UseDefaultCredentials = false };
var client = new HttpClient(handler) { BaseAddress = _baseUri };
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password))));
// do client login and get response with session cookie...
var response = client.PostAsync(...);
// add the cookies to the SignalR hub connection
var responseCookies = cookieJar.GetCookies(_baseUri);
var cookieContainer = new CookieContainer();
foreach (Cookie cookie in responseCookies)
{
cookieContainer.Add(cookie);
}
_hubConnection = new HubConnection(_baseUri.ToString()) { CookieContainer = cookieContainer };
After this setup, my session cookies are sent to the Hub on each invocation. Somehow I need for ServiceStack to intercept those requests and set the authenticated user.
Let ServiceStack do the authenication and persisting the user session. Then in the SignalR hub endpoints that need authentication put this code:
var cache = AppHostBase.Resolve<ICacheClient>();
var sess = cache.SessionAs<AuthUserSession>();
if (!sess.IsAuthenticated)
throw new AuthenticationException();
Johan's answer works but it is not very convenient to put this code to every method and if you put it in the hub constructor, it will fail in the web on page refresh with "Only ASP.NET Requests accessible via Singletons are supported" exception.
Instead, I have chosen to create a custom attribute that gives you more control on the hub and method call authorization.
Simplest attribute would look like this:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AuthorizeServiceStack : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return CheckAuthorization();
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return CheckAuthorization();
}
private static bool CheckAuthorization()
{
var cache = AppHostBase.Resolve<ICacheClient>();
var sess = cache.SessionAs<AuthUserSession>();
return sess.IsAuthenticated;
}
}
As you can see, the code is the same as in Johan's answer but it will work in the web as well, ensuring that HttpContext.Current is not null when you are calling cache.SessionAs
I am having an issue posting an object to my WCF REST Web Service.
On the WCF side I have the following:
[WebInvoke(UriTemplate = "", Method = "POST")]
public void Create(myObject object)
{
//save some stuff to the db
}
When I am debugging, the break point is never hit.However, the break point is hit when I remove the parameter.So, I am guessing I have done something wrong on the RestSharp side of things.
Here's my code for that part:
var client = new RestClient(ApiBaseUri);
var request = new RestRequest(Method.POST);
request.RequestFormat = DataFormat.Xml;
request.AddBody(myObject);
var response = client.Execute(request);
Am I doing this wrong? How can the WCF side see my object? What way should I be making the request? Or should I be handling it differently on the WCF side?
Things that I have tried:
request.AddObject(myObject);
and
request.AddBody(request.XmlSerialise.serialise(myObject));
Any help and understanding in what could possibly be wrong would be much appreciated. Thanks.
I have been struggling with the same problem. Once you try to add the object to pass, it becomes a "Bad request". I tried a variety of things based on various sites I found and got nothing. Then I changed the format from Xml to Json, and it just started working. Must be some glitch with XML passing. Might need to setup a 2nd PC and try to sniff the actual http with something like wireshark or fiddler. (Or maybe I'll just stick to json)
Below is the function from my experimental WCF interface
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "manualselect", ResponseFormat = WebMessageFormat.Json)]
void PostManualSelect(ManualUpdateRequest S);
then my test RestSharp client
var client = new RestClient();
client.BaseUrl = "http://127.0.0.1:8000";
/* Initialization of ManualUpdateRequest instance "DR" here */
var request = new RestRequest(Method.POST);
request.Resource = "manualselect";
request.RequestFormat = DataFormat.Json;
request.AddBody(DR);
RestResponse response = client.Execute(request);
Perhaps someone can shed some more light on the matter. I am also new to REST services. I'd thought I'd add my findings to steer towards a better answer.
(--EDIT--)
I did some more digging and found this tidbit
So I added the [XmlSerializerFormat] attribute to ServiceContract interface like so
[ServiceContract]
[XmlSerializerFormat]
public interface IMyRestService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "manualselect", ResponseFormat = WebMessageFormat.Xml)]
void PostManualSelect(ManualUpdateRequest S);
}
and then this finally worked and I got an object in my service
var client = new RestClient();
client.BaseUrl = "http://127.0.0.1:8000";
/* Initialization of ManualUpdateRequest instance "DR" here */
var request = new RestRequest(Method.POST);
request.Resource = "manualselect";
request.RequestFormat = DataFormat.Xml;
request.AddBody(DR);
RestResponse response = client.Execute(request);
(--EDIT 2--) I have encountered some more XML serializing weirdness that lead me to make this extension (borrowing from here). Might help if you still have trouble. There is also an answer here that implies you need to use public properties to serialize correctly, which I have not tried yet.
public static class RestSharpExtensions
{
public static T GetXmlObject<T>(this IRestResponse response)
{
if (string.IsNullOrEmpty(response.Content))
{
return default(T);
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
XmlReaderSettings settings = new XmlReaderSettings();
// No settings need modifying here
using (StringReader textReader = new StringReader(response.Content))
{
using (XmlReader xmlReader = XmlReader.Create(textReader, settings))
{
return (T)serializer.Deserialize(xmlReader);
}
}
}
public static void UseDotNetXml(this IRestRequest request)
{
request.RequestFormat = DataFormat.Xml;
request.XmlSerializer = new RestSharp.Serializers.DotNetXmlSerializer();
}
}
So my RestSharp calls start looking more like this
public SimpleSignUpdateDataSet GetSimpleDataset()
{
var client = new RestClient(SerivceURL);
var request = new RestRequest("simpledataset", Method.GET);
request.UseDotNetXml();
var resp = client.Execute(request);
return resp.GetXmlObject<SimpleSignUpdateDataSet>();
}
This answer is getting long, but I hope it is of some help to someone.
you can use fiddler on the same pc .... no need for a second one. If you install it, solving these types of problems gets really much easier, you see what you do!
Specify proxy like this:
using system.net; // for the WebProxy
RestClient rc = new RestClient(aUrl);
rc.Proxy = new WebProxy("http://127.0.0.1:8888");
I am accessing a web service using WCF. Using WSHttpBinding, Security mode is set Transport (https) and client credential type is Basic. When I try to access the service using the proxy, getting an 401 unauthorized exception.
Here is the Binding
var binding = new WSHttpBinding()
{
UseDefaultWebProxy = true,
Security =
{
Mode = SecurityMode.Transport,
Transport =
{
ClientCredentialType = HttpClientCredentialType.Basic,
},
}
};
Here is the service call
var client = new InternetClient(binding, new EndpointAddress("httpsurl"));
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
client.ProcessMessage("somevalue");
When looked into Http headers using Http Analyzer
CONNECT HEADER
(Request-Line):CONNECT somehost.com:443 HTTP/1.1
Host:somehost.com
Proxy-Connection:Keep-Alive
POST HEADER
(Request-Line):POST /Company/1.0 HTTP/1.1
Content-Type:application/soap+xml; charset=utf-8
VsDebuggerCausalityData:uIDPo+voStemjalOv5LtRotFQ7UAAAAAUKLJpa755k6oRwto14BnuE2PDtYKxr9LhfqXFSOo8pEACQAA
Host:somehost.com
Content-Length:898
Expect:100-continue
Connection:Keep-Alive
If you see the header Authorization header is missing
Now my question is why WCF call missing the Authorization header? Am I missing something? . Please ask if you need more information
This is a common problem, but the situation is different from what you think.
It turns out that initially for the 1st request a WCF client that is configured to use HTTP basic authentication will nevertheless send the request without the necessary Authorization header to the server. This is the default behavior of the HttpWebRequest class used by the WCF client.
Normally, the web service server will then return a HTTP 401 Unauthorized response to the WCF client, upon which the latter will resend the message with the Authorization header. This means under normal conditions for HTTP Basic Authentication there will be a a rather useless round trip to the server.
This also explains why the header was missing in your sniffed message. Some Http sniffs possibly don't pass on the 401 response, so the whole exchange gets messed up.
The server round-trip and dependence on the 401 response can be avoided by manually injecting the required Authorization header into every request. See e.g. how to manually inject Authorization header into WCF request
As a slight modification from a previous answer, to support async / await calls, you can actually create a new OperationContext and pass it around on whatever thread you like (as long as it is not shared across concurrent threads as it isn't a thread-safe object)
var client = new MyClient();
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(client.ClientCredentials.UserName.UserName + ":" + client.ClientCredentials.UserName.Password));
var context = new OperationContext(ormClient.InnerChannel);
using (new OperationContextScope(context))
{
context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
return await client.SomeMethod();
}
I had the exact same issues. I was able to manually inject the authorization headers by using the following code:
var callcontext = new CAdxCallContext();
callcontext.codeLang = "ENG";
callcontext.poolAlias = "BGRTEST";
var proxy = new CAdxWebServiceXmlCCClient();
proxy.Endpoint.EndpointBehaviors.Add(new CustomEndpoint());
proxy.ClientCredentials.UserName.UserName = "USERNAME"; // Might not benecessary
proxy.ClientCredentials.UserName.Password = "PASSWORD"; // Might not benecessary
string inputXml = "<PARAM>" +
"<GRP ID= \"GRP1\">" +
"<FLD NAME = \"ITMREF\">" + "100001" + "</FLD>" +
"</GRP>" +
"</PARAM>";
CAdxResultXml response;
try
{
response = proxy.run(callcontext, "BGR_SIEPRO", inputXml);
}
catch (TimeoutException timeout)
{
Console.WriteLine(timeout.Message);
// handle the timeout exception.
proxy.Abort();
}
catch (CommunicationException commexception)
{
Console.WriteLine(commexception.Message);
// handle the communication exception.
proxy.Abort();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
proxy.Close();
}
}
public class ClientMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Nothing Here
Console.Write(reply.ToString());
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes("USERNAME" + ":" +
"PASSWORD"));
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
return null;
}
}
public class CustomEndpoint : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Nothing here
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Nothing here
}
public void Validate(ServiceEndpoint endpoint)
{
// Nothing here
}
}
Notice the Expect:100-continue in the header. That's the reason for the round trip.
Put this in your web.config and try again:
<system.net>
<settings>
<servicePointManager expect100Continue="false"/>
</settings>
</system.net>
Actually, I was wrong about this question. I did see different behaviour when running HTTP analyzer. While Http anaylzer running, my application crashed after receiving 401 response. When Http analyzer application closed, the above code worked as expected.