drupal hit counter
Jerry Huang | Re-using your SOAP in Xamarin .Net Standard

Jerry Huang apps and developing apps

Re-using your SOAP in Xamarin .Net Standard

19. January 2019 22:01 by Jerry in C#, Xamarin

I have an old Xamarin Forms project that created couple years ago, by then, was using PCL as project template. The project initially was only for iOS but recently I need to support Android as well. For some reason, there is an runtime error as soon as the app launched. Long story short, I want to upgrade the PCL to .Net Standard 2.0. The project consumes a SOAP web service (asmx) also written long time ago. Even thought I manage to eliminate all compile time error after re-generating the proxy class. The WS call end up with the same exception as below:

https://github.com/dotnet/wcf/issues/1804

Operation 'methodNameAsync' contains a message with parameters. Strongly-typed or untyped message can be paired only with strongly-typed, untyped or void message.

The issue is currently opened here as well:

https://github.com/dotnet/wcf/issues/1808

After some research, I understand that there is no solution for that while I don't want to re-write my SOAP WS into REST api. I decided to write my own SOAP client by re-using the data/schema classes generated inside proxy class. Here is my soap client looks like:

public class SoapService 
{ 
private static T Deserialize(string xml, string ns= "http://home.jerryhuang.net/")
{
Message m = Message.CreateMessage(XmlReader.Create(new StringReader(xml)), int.MaxValue, MessageVersion.Soap11);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T), ns);
return (T)xmlSerializer.Deserialize(m.GetReaderAtBodyContents()); 
}
class RequestMessageBodyWriter : BodyWriter
{
private MessageBodyDescription bodyDescription;
private object[] parameters;
public RequestMessageBodyWriter(MessageBodyDescription bodyDescription, params object[] parameters)
: base(false)
{
this.bodyDescription = bodyDescription;
this.parameters = parameters;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
// Can't Dispose this xmlWriter, 'cause it will close 'writer'.
var xmlWriter = XmlWriter.Create(writer);
xmlWriter.WriteStartElement(bodyDescription.WrapperName, bodyDescription.WrapperNamespace);
foreach (var part in bodyDescription.Parts)
{
var dcs = new DataContractSerializer(part.Type, part.Name, part.Namespace);
dcs.WriteObject(xmlWriter, parameters[part.Index]);
}
xmlWriter.WriteEndElement();
}
}
private Message GetRequestMessage(string opName, params object[] para)
{
var serviceDescription = ContractDescription.GetContract(typeof(HomeServices.HomeServiceSoapClient));
var operationDescription = serviceDescription.Operations.Single(op => op.Name.Equals(opName));
var inputMessage = operationDescription.Messages.Single(msgDescription => msgDescription.Direction == MessageDirection.Input);
var bodyDescription = inputMessage.Body;
var msg = Message.CreateMessage(MessageVersion.Soap11,
inputMessage.Action, new RequestMessageBodyWriter(bodyDescription, para));
//msg.Headers.Clear();
return msg;
}
private string GetUrl(string api)
{
return string.Format("https://yourWSURL/something.asmx?op={0}", api);
}
public async Task Login(string user, string pass)
{
var msg = GetRequestMessage("LoginAsync", user, pass);
var _httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("SOAPAction", msg.Headers.Action);
msg.Headers.Clear();
var soapString = msg.ToString();
var response = await  _httpClient.PostAsync(GetUrl("Login"),
new StringContent(soapString, Encoding.UTF8, "text/xml"));
var content = await response.Content.ReadAsStringAsync();
var o = Deserialize(content);
return o;
}
}

That's it, I keep re-writing the method similar to Login. The implementation is to serialize the request and manually POST via HttpClient; then deserialize the response back to objects.