Where do I set the CookieContainer on a Service Reference? - c#

When adding WebService Reference to an ASMX Service on a .NET 2.0 project for example,
var objService = new NameSpace.groupservices();
there exists,
objService.CookieContainer = new System.Net.CookieContainer();
When adding ServiceReference to an ASMX Service on a .NET 4.0 project for example,
var objService = new NameSpace.groupservicesSoapClient();
there isn't any CookieContainer property for objService
A similar question was asked here with no positive solution.
Could someone please guide where to find the property?

In contrast to ASMX Web Services that are tied to HTTP transport, WCF allows for various transport protocols to be used. Therefore, not all protocol-specific options (such as Cookies for HTTP transport) are available in a WCF service reference.
You can, however, add a message inspector that inspects the messages that are sent between client and server. This article describes a way to send cookies to the server.
I've extended the sample to use a CookieContainer. Also, the following code shows how to evaluate the Set-Cookie header sent by the server to add the new cookies to the container. Please note that the sample shows a basic outline, but might need extension or some more validation. However, in a simple scenario it worked.
The following snippet shows a test method of a WCF service that is hosted on IIS and integrated in the ASP.NET framework. It basically echoes the cookies sent to the server in a string and adds two new ones:
public string GetData(int value)
{
var reply = string.Join(", ",
from x in HttpContext.Current.Request.Cookies.AllKeys
select x + "=" + HttpContext.Current.Request.Cookies[x].Value);
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Test", "Test123"));
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Test2", "Test1234"));
return reply;
}
The following test program creates a CookieContainer for the cookies, adds a demo cookie and registers a new behavior for the endpoint of the service:
class Program
{
static void Main(string[] args)
{
var cookieCont = new CookieContainer();
using(var svc = new TestServiceReference.TestServiceClient())
{
cookieCont.Add(svc.Endpoint.Address.Uri, new Cookie("TestClientCookie", "Cookie Value 123"));
var behave = new CookieBehavior(cookieCont);
svc.Endpoint.EndpointBehaviors.Add(behave);
var data = svc.GetData(123);
Console.WriteLine(data);
Console.WriteLine("---");
foreach (Cookie x in cookieCont.GetCookies(svc.Endpoint.Address.Uri))
Console.WriteLine(x.Name + "=" + x.Value);
}
Console.ReadLine();
}
}
The behavior serves the purpose of adding a custom message inspector and handing over the CookieContainer:
public class CookieBehavior : IEndpointBehavior
{
private CookieContainer cookieCont;
public CookieBehavior(CookieContainer cookieCont)
{
this.cookieCont = cookieCont;
}
public void AddBindingParameters(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Channels
.BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Dispatcher.ClientRuntime behavior)
{
behavior.MessageInspectors.Add(new CookieMessageInspector(cookieCont));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Dispatcher
.EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint serviceEndpoint) { }
}
The message inspector both adds cookies when a request is sent to the server in the BeforeSendRequest method and retrieves the cookies that should be updated in the AfterReceiveReply method. Note that the correlationState returned by BeforeSendRequest is used to retrieve the Uri in the AfterReceiveReply:
public class CookieMessageInspector : IClientMessageInspector
{
private CookieContainer cookieCont;
public CookieMessageInspector(CookieContainer cookieCont)
{
this.cookieCont = cookieCont;
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
object obj;
if (reply.Properties.TryGetValue(HttpResponseMessageProperty.Name, out obj))
{
HttpResponseMessageProperty httpResponseMsg = obj as HttpResponseMessageProperty;
if (!string.IsNullOrEmpty(httpResponseMsg.Headers["Set-Cookie"]))
{
cookieCont.SetCookies((Uri)correlationState, httpResponseMsg.Headers["Set-Cookie"]);
}
}
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
object obj;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out obj))
{
HttpRequestMessageProperty httpRequestMsg = obj as HttpRequestMessageProperty;
SetRequestCookies(channel, httpRequestMsg);
}
else
{
var httpRequestMsg = new HttpRequestMessageProperty();
SetRequestCookies(channel, httpRequestMsg);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMsg);
}
return channel.RemoteAddress.Uri;
}
private void SetRequestCookies(System.ServiceModel.IClientChannel channel, HttpRequestMessageProperty httpRequestMessage)
{
httpRequestMessage.Headers["Cookie"] = cookieCont.GetCookieHeader(channel.RemoteAddress.Uri);
}
}

Open your app.config file and add allowCookies="true" to the binding.
Something like this:
<binding allowCookies="true" />

Found the solution in here:
http://msdn.microsoft.com/en-us/library/bb628649.aspx
It turns out I need a web reference instead of a service reference

Related

SSL errors using Service Fabric ServicePartitionClient

We have a singleton service fabric service that needs to communicate to a partitioned service, both of which are running in a secure cluster with certificate-based authentication. We are using ServicePartitionClient to do the talking. What follows is a simplified version of our implementations of ICommunicationClient and ICommunicationClientFactory as required by the ServicePartitionClient.
The client:
public class HttpCommunicationClient : ICommunicationClient
{
// Lots of fields omitted for simplicity.
public HttpCommunicationClient(HttpClientWrapper client, Uri baseAddress)
{
this.HttpClient = client;
this.BaseAddress = baseAddress;
}
public Uri BaseAddress { get; set; }
// Wraps System.Net.Http.HttpClient to do stuff like add default headers
public HttpClientWrapper HttpClient { get; }
public ResolvedServicePartition ResolvedServicePartition { get; set; }
public string ListenerName { get; set; }
public ResolvedServiceEndpoint Endpoint { get; set; }
public Uri GetUri(string relativeUri)
{
return new Uri(this.BaseAddress, relativeUri);
}
}
The factory:
// Note that this base class is under the
// Microsoft.ServiceFabric.Services.Communication.Client namespace,
// it's not something we wrote
public class HttpCommunicationClientFactory :
CommunicationClientFactoryBase<HttpCommunicationClient>
{
// Lots of fields omitted for simplicity.
private readonly HttpClientWrapper client;
public HttpCommunicationClientFactory(
ServicePartitionResolver resolver,
IEnumerable<IExceptionHandler> exceptionHandlers)
: base(resolver, exceptionHandlers, null)
{
// There's a bunch of other args that are omitted for clarity.
this.client = new HttpClientWrapper();
}
protected override Task<HttpCommunicationClient> CreateClientAsync(
string endpoint,
CancellationToken cancellationToken)
{
HttpCommunicationClient client = new HttpCommunicationClient(
this.client,
new Uri(endpoint));
if (this.ValidateClient(endpoint, client))
{
return Task.FromResult(client);
}
else
{
throw new ArgumentException();
}
}
protected override bool ValidateClient(HttpCommunicationClient client)
{
// Not much to validate on httpclient
return true;
}
protected override bool ValidateClient(
string endpoint,
HttpCommunicationClient client)
{
return !string.IsNullOrWhiteSpace(endpoint);
}
protected override void AbortClient(HttpCommunicationClient client)
{
}
}
And here's an example of how everything is being constructed and used:
internal class FooFabricClient : IDisposable
{
// Lots of fields omitted for simplicity.
private readonly HttpCommunicationClientFactory clientFactory;
private readonly ServicePartitionClient<HttpCommunicationClient> partitionClient;
public FooFabricClient(Uri fabricUri, ServicePartitionKey partitionKey = null)
{
this.clientFactory = new HttpCommunicationClientFactory(
ServicePartitionResolver.GetDefault());
this.partitionClient = new ServicePartitionClient<HttpCommunicationClient>(
this.clientFactory,
fabricUri,
partitionKey,
retrySettings: new OperationRetrySettings());
}
public async Task<Foo> GetFooAsync()
{
return await this.partitionClient.InvokeWithRetryAsync(async (client) =>
{
// Note: See above for actual GetUri() implementation and context,
// but this will give us a Uri composed of the client's BaseAddress
// (passed in during HttpCommunicationClientFactory.CreateClientAsync())
// followed by "/foo"
Uri requestUri = client.GetUri("foo");
return await client.HttpClient.GetAsync<Foo>(requestUri);
});
}
Now, the issue is that when I call GetFooAsync(), it throws an exception saying this:
Could not establish trust relationship for the SSL/TLS secure channel. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
After some debugging, we found that this is most likely due to the fact that we get the internal service fabric IP address (e.g., 10.0.0.4) as HttpCommunicationClient.BaseAddress, so when we make our API call the server cert doesn't validate against the domain in the request. As a temporary fix we've done the following in the calling service:
ServicePointManager.ServerCertificateValidationCallback += (a, b, c, d) => true;
Of course, we'd rather not just blindly say "yup, looks good" when validating server certs, so how should we go about resolving this issue? Is there another way to set up the client(s) so that they can properly validate the server cert on the request without needing our own callback? Or do we just need to put acceptable thumbprints in our config and compare against those in the ServicePointManager callback or whatever?

How to override an invalid XSD property type in a SOAP response to allow parsing?

I am trying to consume a SOAP service using Visual Studio's "Add Service Reference" tool.
One of the types defined in the service WSDL has the following property:
<xsd:element name="paymentDate" type="xsd:dateTime" />
However, when requesting data for which this property value is null, it will be returned in the raw XML as:
<paymentDate xsi:type="xsd:dateTime"/>
Which causes the following exception when the XML is deserialized: FormatException: The string '' is not a valid AllXsd value.
I cannot fix the bug in the SOAP service to change the XML response (which should have xsd:nil as the type if I am correct).
What would be a good workaround so that it doesn't throw an exception and allows me to access the property value, at least as a string?
Is there any way I could override the deserialization of the XML response? Changing the corresponding property type from DateTime to string in the Reference.cs file will not help (it seems the deserializer uses the type information present in the XML reponse).
If I remove the property from Reference.cs the exception will not be thrown, but then I don't have access to the property value which I need.
I was able to come up with the following workaround to inspect the raw XML response and modify it before it is parsed by WCF.
It basically replaces the incorrect <paymentDate xsi:type="xsd:dateTime"></paymentDate> with the expected <paymentDate xsi:nil="true"></paymentDate>.
public class MyRoutine {
public static void Main() {
var client = new MyServiceClient();
client.Endpoint.Behaviors.Add(new InspectorBehavior());
}
}
public class InspectorBehavior : IEndpointBehavior {
public void Validate(ServiceEndpoint endpoint) {
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
clientRuntime.MessageInspectors.Add(new MyMessageInspector());
}
}
public class MyMessageInspector : IClientMessageInspector {
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState) {
reply = ChangeString(reply, from: "<paymentDate xsi:type=\"xsd:dateTime\"></paymentDate>", to: "<paymentDate xsi:nil=\"true\"></paymentDate>");
}
public static Message ChangeString(Message oldMessage, string from, string to) {
var ms = new MemoryStream();
var xw = XmlWriter.Create(ms);
oldMessage.WriteMessage(xw);
xw.Flush();
var body = Encoding.UTF8.GetString(ms.ToArray());
xw.Close();
body = body.Replace(from, to);
ms = new MemoryStream(Encoding.UTF8.GetBytes(body));
var xdr = XmlDictionaryReader.CreateTextReader(ms, new XmlDictionaryReaderQuotas());
var newMessage = Message.CreateMessage(xdr, int.MaxValue, oldMessage.Version);
newMessage.Properties.CopyProperties(oldMessage.Properties);
return newMessage;
}
}

Can't get a valid response from a REST web service using System.Net.Http.HttpClient

I am using this test method (and helper class) to verify the response from an external web service:
[TestMethod]
public void WebServiceReturnsSuccessResponse()
{
using (var provider = new Provider(new Info()))
using (var result = provider.GetHttpResponseMessage())
{
Assert.IsTrue(result.IsSuccessStatusCode);
}
}
private class Info : IInfo
{
public string URL { get; set; } =
"https://notreallythe.website.com:99/service/";
public string User { get; set; } = "somename";
public string Password { get; set; } = "password1";
}
I can't get this test to pass; I always get a 500 - Internal Server Error result. I have connected via an external utility (Postman) - so the web service is up and I can connect with the url & credentials that I have.
I think the problem is in my instantiation of the HttpClient class, but I can't determine where. I am using Basic authentication:
public class Provider : IProvider, IDisposable
{
private readonly HttpClient _httpClient;
public Provider(IInfo config){
if (config == null)
throw new ArgumentNullException(nameof(config));
var userInfo = new UTF8Encoding().GetBytes($"{config.User}:{config.Password}");
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.URL),
DefaultRequestHeaders =
{
Accept = { new MediaTypeWithQualityHeaderValue("application/xml")},
Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(userInfo)),
ExpectContinue = false,
},
};
}
public HttpResponseMessage GetHttpResponseMessage()
{
return _httpClient.GetAsync("1234").Result;
}
}
The response I get back appears to go to the correct endpoint; the RequestUri in the response looks exactly like I expect, https://notreallythe.website.com:99/service/1234.
You need to load up Fiddler and do a recording of the HTTP traffic when this operation succeeds (through the browser).
Then, load up your code, stand up another instance (or window) of Fiddler, and do the same thing with your code. Now, compare the two Fiddler windows to see what is different.
You only need to compare those things in Fiddler that are highlighted in blue. You can ignore the other communications.

Consuming a SOAP web service (Kronos SaasHR) and saving response into SQL server

I have been working on a project to connect to our time reporting vendor's SOAP service and get a report weekly (REST doesn't offer function call for getting the desired report). They have only given us the WSDL which has not been helpful: https://secure.entertimeonline.com/ta/padnos.wsdl and
https://secure.saashr.com/ta/PADNOS.soap
I have added the service reference to my solution and generated the proxy class, but each time I try to fetch the data, I get errors saying "Response is not well-formed XML." and "data at the root level is invalid. Line 1, position 1."
This is my first foray into consuming a web service and I cannot find any helpful material.
private void btnGo_Click(object sender, EventArgs e)
{
// service reference
// runReportByName
TSPHoursWorked.ServiceReference1.runReport_ByNameType rptName = new ServiceReference1.runReport_ByNameType();
rptName.version = 1;
rptName.reportCategory = "Calculated Time";
rptName.reportName = "Calculated Time By Entry";
rptName.reportSavedName = "DailyHoursWorked";
rptName.outputType = TSPHoursWorked.ServiceReference1.runReport_ByNameTypeOutputType.XML;
TSPHoursWorked.ServiceReference1.SaaSHRClient soap = new ServiceReference1.SaaSHRClient();
soap.ClientCredentials.UserName.UserName = "username";
soap.ClientCredentials.UserName.Password = "password";
var requestInterceptor = new InspectorBehavior();
soap.Endpoint.Behaviors.Add(requestInterceptor);
soap.runReport_ByName(rptName);
string requestXML = requestInterceptor.LastRequestXML;
outputText.Text = requestXML;
string responseXML = requestInterceptor.LastResponseXML;
outputText.Text += responseXML;
}
public class InspectorBehavior : IEndpointBehavior
{
public string LastRequestXML
{
get
{
return myMessageInspector.LastRequestXML;
}
}
public string LastResponseXML
{
get
{
return myMessageInspector.LastResponseXML;
}
}
private MyMessageInspector myMessageInspector = new MyMessageInspector();
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(myMessageInspector);
}
}
public class MyMessageInspector : IClientMessageInspector
{
public string LastRequestXML { get; private set; }
public string LastResponseXML { get; private set; }
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
LastResponseXML = reply.ToString();
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
LastRequestXML = request.ToString();
return request;
}
}
I would just like help simply connecting to their web service, and get the report and bulk insert into SQL server. Could anyone set me on the right path?
EDIT:
I've installed SoapUI and there are no URLs listed under the soap operation "Actions" column:
The problem was with our middle-man vendor to Kronos not having any technical knowledge. After a few weeks and finally getting in touch with Kronos, I was informed that:
a new user must be created without single sign-on
copy the groups/reports/etc of the main admin account to the new account
add highest security privilege to new account
Then you will be able to call the RESTful service. I hope this helps someone because our vendor wasted 2 weeks of our time

WCF REST Proxy using ChannelFactory

I'm trying to create a proxy WCF service over a WCF service using ChannelFactory.
I have defined the following interface for the WCF service to call. It has a generic GetResource method that writes directly into HttpContext the response byte[] and sets it's content type based on the type of the resource.
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "getResource/?resourceKey={resourceKey}")]
void GetResource(string resourceKey);
}
Proxy implementation for GetResource:
public void GetResource(string resourceKey)
{
var factory = new ChannelFactory<ITest>(GetCustomBinding(), new EndpointAddress('https://test.com/mytest.svc'));
channel.Endpoint.Behaviors.Add(new WebHttpBehavior());
var channel = factory.CreateChannel();
using (var scope = new OperationContextScope((IContextChannel)channel))
{
channel.GetResource(resourceKey);
}
}
private static Binding GetCustomBinding()
{
var binding = new WebHttpBinding(WebHttpSecurityMode.Transport)
{
MaxReceivedMessageSize = 20000000,
MaxBufferSize = 20000000,
MaxBufferPoolSize = 20000000,
CloseTimeout = TimeSpan.FromMinutes(1)
};
binding.ContentTypeMapper = new JsonMapper();
return binding;
}
private class JsonMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Json;
}
}
The problem that I have is that when I'm calling the proxy at this endpoint, proxy.svc/GetResource?resourceKey="text.css" it returns the right content type but no actual content, ContentLength=0.
How should I handle Response Body forwarding? How do I read the response body? I tried using WebOperationContext.Current.IncomingResponse but it doesn't give me the response body as well.
I think you are attempting to use WCF in a way that it is not intended. A void method will return nothing. In theory, you can intercept this by adding an inspection behaviour, but not even sure that would work.
I have (potentially) similar code which loads file from a database and returns them, but that will return the content.
var file = ServiceUtilities.FileManager.GetFile(id);
if (file != null)
{
var fcr = new FileContentResult(file.Content, file.MimeType);
return fcr;
}
Perhaps you should revisit your intended approach.

Categories