Parsing SOAP messages from a web service I'm consuming an ASMX service with SOAP messages. Since Xamarin doesn't support SOAP 1.2, I'm writing the Web requests manually. I'm sending a user/password to retrieve the user ID.

A successful response may look like this:

&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ns="link-to-namespace"&gt;
  &lt;soap:Body&gt;
    &lt;ns:User&gt;
      &lt;ns:UserId&gt;123&lt;/ns:UserId&gt;
    &lt;/ns:User&gt;
  &lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;


An error response may look like this:

&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"&gt;
  &lt;soap:Body&gt;
    &lt;soap:Fault&gt;
      &lt;soap:Code&gt;
        &lt;soap:Value&gt;InvalidUsernameAndPassword&lt;/soap:Value&gt;
      &lt;/soap:Code&gt;
    &lt;/soap:Fault&gt;
  &lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;


What's the best way to parse the response? And how to check if there is an error?

Currently I just look for the presense of the &lt;Fault&gt; element.

WebService class handles the request and returns the SOAP envelope as an XDocument:

class WebService
{
    // Returns the SOAP envelope as an XDocument.
    public XDocument Invoke(string soapAction, string method, Dictionary&lt;string, string&gt; parameters)
    {


UserApi class acts as a proxy towards the service.

class UserApi
{
    // Sends user/pwd to server to retrieve user id.
    public string GetUserId(string username, string password)
    {
        var soapAction = "link-to-soap-action";
        var method = "GetUserId";
        var parameters = new Dictionary&lt;string, string&gt;
        {
            { "Username", username },
            { "Password", password}
        };
        var service = new WebService();
        var result = service.Invoke(soapAction, method, parameters);

        XNamespace nsSoap = "http://www.w3.org/2003/05/soap-envelope";
        XNamespace ns = "link-to-namespace";

        // Look for the "Fault" element in the response. If present, there was an error.
        var fault = result.Root.Element("{" + nsSoap + "}Body").Element("{" + ns_soap + "}Fault");
        if (error != null)
        {
            var codeWithNs = error.Element("{" + nsSoap + "}Code").Element("{" + ns_soap + "}Value").Value;
            var codeSplit = codeWithNs.Split(':');
            var code = codeSplit.Length == 2 ? codeSplit[1] : codeSplit[0];
            if (code == "InvalidUsernameOrPassword")
            {
                throw new InvalidUsernameOrPasswordException();
            }
            else
            {
                throw new Exception("Something went wrong.");
            }
        }

        return userId = result.Descendants(ns + "UserId").First().Value;
    }

    public async Task&lt;string&gt; GetUserIdAsync(string username, string password)
    {
        return await Task.Run(() =&gt;
        {
            return GetUserId(username, password);
        });
    }
}


Currently it works, at least under optimal conditions. But my solution looks dense and fragile. If I should go with deserialization, here's some pseudo code of what I'm thinking:

try
{
    var serializer = new XmlSerializer(typeof(UserEnvelope));
    var user = serializer.Deserialize(response);
}
catch (InvalidOperationException)
{
    var serializer = new XmlSerializer(typeof(ErrorEnvelope));
    var error = serializer.Deserialize(response);
    if (error.Code == "InvalidUsernameOrPassword")
    ...etc
}
Spot the inconsistency:


        var fault = result.Root.Element("{" + nsSoap + "}Body").Element("{" + ns_soap + "}Fault");
...
            var codeWithNs = error.Element("{" + nsSoap + "}Code").Element("{" + ns_soap + "}Value").Value;
...
        return userId = result.Descendants(ns + "UserId").First().Value;



Ok, there are two inconsistencies. Firstly, nsSoap vs ns_soap. I suspect that that difference is due to refactoring the code before posting in, in which case please don't do that again: we want to review code which compiles and works.

Secondly, "{" + nsSoap + "}Body" vs ns + "UserId". The simple concatenation is far more readable.




        var service = new WebService();
        var result = service.Invoke(soapAction, method, parameters);

        XNamespace nsSoap = "http://www.w3.org/2003/05/soap-envelope";
        XNamespace ns = "link-to-namespace";

        // Look for the "Fault" element in the response. If present, there was an error.
        var fault = result.Root.Element("{" + nsSoap + "}Body").Element("{" + ns_soap + "}Fault");
        if (error != null)
        {
            var codeWithNs = error.Element("{" + nsSoap + "}Code").Element("{" + ns_soap + "}Value").Value;
            var codeSplit = codeWithNs.Split(':');
            var code = codeSplit.Length == 2 ? codeSplit[1] : codeSplit[0];
            if (code == "InvalidUsernameOrPassword")
            {
                throw new InvalidUsernameOrPasswordException();
            }
            else
            {
                throw new Exception("Something went wrong.");
            }
        }



There's a strong asymmetry here which I don't grok. WebService does the job of assembling some simple strings into the XML request document: why does it not also do the job of disassembling the XML response document? I understand that it can't fully do that, because the response might be a complex datastructure, but IMO WebService should be detecting &lt;soap:Fault&gt; and returning some kind of "option" type.

As it happens, there is an option type in .Net: Task&lt;TResult&gt;. So I think that WebService.Invoke should return a Task&lt;XElement&gt; which has Status Faulted and a placeholder exception, or Status RanToCompletion and the contents of &lt;soap:Body&gt;. Then GetUser would look like

public string GetUserId(string username, string password)
{
    var soapAction = "link-to-soap-action";
    var method = "GetUserId";
    var parameters = new Dictionary&lt;string, string&gt;
    {
        { "Username", username },
        { "Password", password}
    };

    var service = new WebService();
    var result = service.Invoke(soapAction, method, parameters);

    if (result.Status == TaskStatus.Faulted)
    {
        // Handle specific errors
        if (result.Exception.Message == "InvalidUsernameOrPassword")
        {
            throw new InvalidUsernameOrPasswordException();
        }
        else
        {
            throw new Exception("Something went wrong.", result.Exception);
        }
    }

    XNamespace ns = "link-to-namespace";
    return userId = result.Value.Descendants(ns + "UserId").First().Value;
}