I set up a console shell application and framework for a team that collected data from hundreds of sources, but most of the data came from web scrapes and web services.
Setting up clients in VS usually requires just a few clicks, but when the servers are third party, and they are not using Microsoft technologies, this sometimes doesn’t work. The VS tool will just error out with some vague message about not loading the WSDL. Using the command line will give you some hints and sometimes you can download their WSDL to your local, and make a couple of edits, and then SvcUtil your client.
In one case in particular, even this didn’t work for me. I was already resorting to writing custom XML requests and inspecting the responses with Fiddler to get my requests right. I think it was some Java JBoss server, and apparently they are known for not serving a standard SOAP format. I forget the details why... But I knew that I could write my own DataContract and OperationContract classes and even write custom channel parsers if I had to. They were serving lots and lots of datatypes, and methods, though, and I didn’t need but a few of them. I had to dissect their huge wdsl file, pulling out just the Data Objects I needed and writing them by hand, instead of using the svcutil, and then running tests to find what I was missing. I had to use XmlSerializerFormat instead of DataContractSerializer attributes for some obscure reason.
Here was my client, constructed in c# to get the requests just right:
class MPRClient : ClientBase<IMPR>, IMPR
{
public MPRClient()
: base()
{
System.ServiceModel.BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.Realm = "eMPR Authentication";
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
CustomBinding cbinding = new CustomBinding(binding);// need to set keepalive=false or we get 505 after auth, this is one way
foreach (BindingElement be in cbinding.Elements)
{
if (be is HttpsTransportBindingElement) ((HttpsTransportBindingElement)be).KeepAliveEnabled = false;
}
Endpoint.Binding = cbinding;
}
public queryResponse query(queryRequest request)
{
queryResponse result = Channel.query(request);
return result;
}
Here are some of my Data Classes that I figured out from the testing you will see my request and response objects, and take note of how I constructed the child objects, as arrays were the only way to get the serialization to line up just right…
/// <summary>
/// some's empr query web service
/// </summary>
[XmlSerializerFormat]
[ServiceContract(Name = "empr", Namespace = "http://empr.some.com/mpr/xml")]
interface Impr
{
/// <summary>
///
/// </summary>
/// <param name="queryRequest">
/// query takes two parms- CompanyName(LSE) and Day
/// </param>
/// <returns>
/// sample data you can get from this service:
/// <PeakLoadSummary Day="2012-01-23">
/// <LSE>NEV</LSE>
/// <ZoneName>AECO</ZoneName>
/// <AreaName>AECO</AreaName>
/// <UploadedMW>70.4</UploadedMW>
/// <ObligationPeakLoadMW>70.064</ObligationPeakLoadMW>
/// <ScalingFactor>0.99523</ScalingFactor>
/// </PeakLoadSummary>
/// </PeakLoadSummarySet>
/// </returns>
[XmlSerializerFormat]
[OperationContract(Action = "/mpr/xml/query")]
queryResponse query(queryRequest queryRequest);
}
[MessageContract(WrapperName = "QueryRequest", WrapperNamespace = "http://empr.some.com/mpr/xml", IsWrapped = true)]
[XmlSerializerFormat]
public class queryRequest
{
[MessageBodyMember(Namespace = "http://empr.some.com/mpr/xml", Order = 0)]
[System.Xml.Serialization.XmlElement("QueryPeakLoadSummary")]
QueryPeakLoadSummary[] Items;
public queryRequest() { }
public queryRequest(QueryPeakLoadSummary[] items)
{
Items = items;
}
}
[XmlSerializerFormat]
[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://empr.some.com/mpr/xml")]
public class QueryPeakLoadSummary
{
[System.Xml.Serialization.XmlAttribute]
public string CompanyName;
[System.Xml.Serialization.XmlAttribute]
public string Day;
public QueryPeakLoadSummary() { }
}
[MessageContract(WrapperName = "QueryResponse", WrapperNamespace = "http://empr.some.com/mpr/xml", IsWrapped = true)]
[XmlSerializerFormat]
public class queryResponse
{
[MessageBodyMember(Namespace = "http://empr.some.com/mpr/xml", Order = 0)]
[System.Xml.Serialization.XmlElement("PeakLoadSummarySet")]
public PeakLoadSummarySet[] Items;
public queryResponse() { }
public queryResponse(PeakLoadSummarySet[] Items)
{
this.Items = Items;
}
}
[XmlSerializerFormat]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://empr.some.com/mpr/xml")]
public class PeakLoadSummarySet
{
[System.Xml.Serialization.XmlElement("PeakLoadSummary", Order = 0)]
public PeakLoadSummary[] PeakLoadSummary;
}
[XmlSerializerFormat]
[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://empr.some.com/mpr/xml")]
public class PeakLoadSummary
{
[System.Xml.Serialization.XmlElement(Order = 0)]
public string LSE;
[System.Xml.Serialization.XmlElement(Order = 1)]
public string ZoneName;
[System.Xml.Serialization.XmlElement(Order = 2)]
public string AreaName;
[System.Xml.Serialization.XmlElement(Order = 3)]
public string UploadedMW;
[System.Xml.Serialization.XmlElement(Order = 4)]
public string ObligationPeakLoadMW;
[System.Xml.Serialization.XmlElement(Order = 5)]
public double ScalingFactor;
[System.Xml.Serialization.XmlAttribute]
public DateTime Day;
public PeakLoadSummary() { }
}
My client config was just a one line endpoint, since the options to set the keepaliveenabled were not available in the config, and I put it in the c# initialization:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="pooledInstanceNetTcpEP_something else
</netTcpBinding>
<basicHttpBinding>
<binding name="OperatorInterfaceSoap" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="net.tcp://Tosomethingelse
</identity>
</endpoint>
<endpoint address="https://b2bsomething else
</endpoint>
<endpoint address="https://rpm.pjm.com/erpm/services/query"binding="basicHttpBinding"
contract="jobs.IRPM" >
</endpoint>
</client>
</system.serviceModel>
And then I could write business code just like normal:
private List<queryResponse> GetResponses(List<Account> accounts, DateTime date)
{
List<queryResponse> result = new List<queryResponse>();
foreach (Account account in accounts)
{
MPRClient r = new MPRClient();
r.ClientCredentials.UserName.UserName = account.Username;
r.ClientCredentials.UserName.Password = account.Password;
result.Add(r.query(new queryRequest(
new QueryPeakLoadSummary[] {
new QueryPeakLoadSummary{ CompanyName = account.Company, Day = date.ToString("yyyy-MM-dd") }
}
)));// day must be 00 digits
}
return result;
}