Разбор SOAP-сообщений из веб-сервиса


Я потреблял службы asmx с сообщениями SOAP. С помощью Xamarin не поддерживает SOAP 1.2, я пишу веб-запросов вручную. Я посылаю пользователя/пароль для получения идентификатора пользователя.

Успешный ответ может выглядеть так:

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

Ответ об ошибке может выглядеть следующим образом:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <soap:Fault>
      <soap:Code>
        <soap:Value>InvalidUsernameAndPassword</soap:Value>
      </soap:Code>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

Какой самый лучший способ для разбора ответа? И как проверить, если есть ошибка?

В настоящее время я просто смотрю на наличие в <Fault> элемент.

Класса WebService обрабатывает запрос и возвращает конверт SOAP в качестве объекта XDocument:

class WebService
{
    // Returns the SOAP envelope as an XDocument.
    public XDocument Invoke(string soapAction, string method, Dictionary<string, string> parameters)
    {

Класс UserApi работает как прокси к службе.

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<string, string>
        {
            { "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<string> GetUserIdAsync(string username, string password)
    {
        return await Task.Run(() =>
        {
            return GetUserId(username, password);
        });
    }
}

В настоящее время он работает, по крайней мере, при оптимальных условиях. Но мое решение выглядит плотной и хрупкой. Если я пойду с десериализации, вот некоторые псевдо-код, что я думаю:

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
}


2561
1
задан 1 марта 2018 в 07:03 Источник Поделиться
Комментарии
1 ответ

Найди нестыковку:


        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;

ОК, Есть две нестыковки. Во-первых, nsSoap против ns_soap. Я подозреваю, что эта разница из-за рефакторинга кода, прежде чем отправлять в таком случае, пожалуйста, не сделать это снова: мы хотим пересмотреть код, который компилируется и работает.

Во-вторых, "{" + nsSoap + "}Body" против ns + "UserId". Простой конкатенации является гораздо более удобным для чтения.



        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.");
}
}


Существует сильная асимметрия здесь, который я не обращал внимания. WebService выполняет работу по сборке некоторые простые строки в XML-документ с запросом: почему он не выполнял работу по разборке XML в ответ документ? Я понимаю, что она не может в полной мере сделать это, потому что ответ может быть сложная структура данных, но ММО WebService следует обнаруживать <soap:Fault> и возвращение какой-то "вариант" типа.

Как это происходит, есть типа вариант .Объем: Task<TResult>. Поэтому я думаю, что WebService.Invoke должны вернуть Task<XElement> который Status Faulted и заполнитель исключение, или Status RanToCompletion и содержание <soap:Body>. Тогда GetUser будет выглядеть

public string GetUserId(string username, string password)
{
var soapAction = "link-to-soap-action";
var method = "GetUserId";
var parameters = new Dictionary<string, string>
{
{ "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;
}

2
ответ дан 2 марта 2018 в 11:03 Источник Поделиться