Parsing SOAP messages from a web service
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;
}