drupal hit counter
Jerry Huang | Xamarin

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:


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:


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]);
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));
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);
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.