Подтвердить что ты не робот

Как получить ссылку на службу, чтобы правильно генерировать сообщения с контрактами на основе сторонних WSDL или не принуждать никаких сообщений к контрактам в проекте WF Service

У меня проблема, связанная с WSDL сторонней стороны. Я могу использовать консольное приложение, чтобы легко создать прокси-сервер службы, который работает, но из WF4 WF-сервиса я не являюсь. Сгенерированный прокси-сервер в последнем случае явно ошибочен, включая, в частности, 2 проблемы: a) Контракты сообщений всегда генерируются, когда они не запрашиваются или не нужны b) Неправильные ответные сообщения и используемые имена обертки xml, в результате чего возникают объекты с нулевым ответом и неудачная десериализация

Проблема, с которой я столкнулась, заключается в фактическом генерации класса Reference.cs на основе стороннего WSDL. В WSDL есть много операций, и по внешнему виду 2 из них такие же:

 <operation name="pu013">
      <documentation>
        <description>Check-response service</description>
        <help>The service handles (cut out)</help>
      </documentation>
      <input message="tns:pu013Request" />
      <output message="tns:SimpleResponse" />
 </operation>

...
 <operation name="mi102">
      <documentation>
        <description>Instruction insert to Matching System</description>
        <help>This service (cut out)</help>
      </documentation>
      <input message="tns:mi102Request" />
      <output message="tns:SimpleResponse" />
    </operation> 

В результате этого в Reference.cs приведен следующий С#:

WorkflowService1.PSE.pu013Response pu013(WorkflowService1.PSE.pu013Request request);

...

WorkflowService1.PSE.pu013Response mi102(WorkflowService1.PSE.mi102Request request); 

Обратите внимание, что по какой-либо причине операция mi102 генерируется с ответным сообщением INCORRECT pu013Response, которое объявляется следующим образом:

 [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.MessageContractAttribute(WrapperName="pu013Response", WrapperNamespace="http://pse/", IsWrapped=true)]
    public partial class pu013Response { 

Обратите внимание, что WrapperName не позволяет сериализатору XML распознавать ответ, который является mi102Response, поэтому для всех операций, которые не являются pu013, я всегда получаю NULL-ответ.

Кроме того, это НЕ происходит, если я добавляю ссылку из консольного приложения. Это не создает контракты с сообщением, и в этом случае работают вызов и ответ.

Чем отличается? Вызывается ли svcutil за кулисами? Если да, то чем отличаются используемые параметры? Можно ли использовать svcutil для создания действий xamlx, чтобы я мог найти обходной путь командной строки?

Это похоже на ошибку VS/Add Service Reference. Альтернативой является вручную исправить многие операции в Reference.cs.

В идеале, я ищу способ легко, автоматически запускать svcutil или Add Service Reference, чтобы корректный класс Reference и генерируемые действия xamlx. Приятно иметь объяснение, почему есть разница, и за кулисами происходит то, что происходит.

UPDATE: Контракты сообщений, сгенерированные в консольном приложении, приводят к одной и той же проблеме - неправильные объявления Response. Проблема исчезает, если параметры используются вместо сообщений, которые недоступны из приложения службы WF.

4b9b3361

Ответ 1

Я далек от полномочий по этим вопросам, и хотя этот ответ ниже, возможно, не подходит для вашей проблемы, мой недавний опыт подключения к службе без прокси может предложить вам некоторое представление о вас или о следующем человеке с аналогичная проблема.

Начну с того, что вы сможете вручную загрузить запрос SOAP с помощью скрипача и посмотреть, сможете ли вы создать правильное сообщение и отправить его. Поскольку вы описываете инструменты автоматизации как ошибочные (или, возможно, проблема с конфигурацией, которую вы не получаете именно так). В любом случае, имея четкое представление о форме контракта и возможность провести надежный тест у скрипача, может проявляться ясность.

Вам необязательно полагаться на использование прокси. Вы можете создать свое собственное мыльное сообщение и отправить его одним из двух способов. Первый использует ChannelFactory.

  • Создайте тело сообщения (если требуется, класс сообщений может работать без него)
  • Создайте свое сообщение
  • Отправьте свое сообщение через ChannelFactory

Для шага 1 вы можете создать свое сообщение, сделав простой POCO, чтобы отразить то, что ожидается в вашем контракте. Вы должны иметь возможность получить, что этот класс через WSDL.

Предположим, что сервис выглядит примерно так:

[ServiceContract(Namespace = "http://Foo.bar.car")]
public interface IPolicyService
{
    [OperationContract]
    PolicyResponse GetPolicyData(PolicyRequest request);
}

public class PolicyData : IPolicyService
{
   public PolicyResponse GetPolicyData(PolicyRequest request)
   {
            var polNbr = request.REQ_POL_NBR;
            return GetMyData(polNbr);
    }
}

Вам понадобится класс примерно так:

[DataContract(Namespace = "http://Foo.bar.car")]
public class GetPolicyData
{
    [DataMember]
    public request request { get; set; }
}

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy")]
public class request
{
    ///<summary>
    /// Define request parameter for SOAP API to retrieve selective Policy level data
    /// </summary>
    [DataMember]
    public string REQ_POL_NBR { get; set; }
}

а затем вы бы назвали это следующим образом:

    private static Message SendMessage(string id)
    {
        var body = new GetPolicyData {request =  new request{ REQ_POL_NBR = id }}; 
        var message = Message.CreateMessage(MessageVersion.Soap11, "http://Foo.bar.car/IPolicyService/GetPolicyData", body);
// these headers would probably not be required, but added for completeness
        message.Headers.Add(MessageHeader.CreateHeader("Accept-Header", string.Empty, "application/xml+"));
        message.Headers.Add(MessageHeader.CreateHeader("Content-Type", string.Empty, "text/xml"));
        message.Headers.Add(MessageHeader.CreateHeader("FromSender", string.Empty, "DispatchMessage"));
        message.Headers.To = new System.Uri(@"http://localhost:5050/PolicyService.svc");

        var binding = new BasicHttpBinding(BasicHttpSecurityMode.None)
        {
             MessageEncoding = WSMessageEncoding.Text,
            MaxReceivedMessageSize = int.MaxValue,
            SendTimeout = new TimeSpan(1, 0, 0),
            ReaderQuotas = { MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue, MaxDepth = int.MaxValue }
        };
        message.Properties.Add("Content-Type", "text/xml; charset=utf-8");
        message.Properties.Remove("Accept-Encoding");
        message.Properties.Add("Accept-Header", "application/xml+");

        var cf = new ChannelFactory<IRequestChannel>(binding, new EndpointAddress(new Uri("http://localhost:5050/PolicyService.svc")));

        cf.Open();
        var channel = cf.CreateChannel();
        channel.Open();

        var result = channel.Request(message);

        channel.Close();
        cf.Close();
        return result;
    }

То, что вы получите назад, будет Message, которое вам нужно будет десериализовать, и есть несколько способов OOTB сделать это (Message.GetReaderAtBodyContents, Message.GetBody) в соответствии с ручной темой:

    /// <summary>
/// Class MessageTransform.
/// </summary>
public static class MessageTransform
{
    /// <summary>
    /// Gets the envelope.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <returns>XDocument.</returns>
    public static XDocument GetEnvelope(Message message)
    {
        using (var memoryStream = new MemoryStream())
        {
            var messageBuffer = message.CreateBufferedCopy(int.MaxValue);
            var xPathNavigator = messageBuffer.CreateNavigator();

            var xmlWriter = XmlWriter.Create(memoryStream);
            xPathNavigator.WriteSubtree(xmlWriter);
            xmlWriter.Flush();
            xmlWriter.Close();

            memoryStream.Position = 0;
            var xdoc = XDocument.Load(XmlReader.Create(memoryStream));
            return xdoc;
        }           
    }

    /// <summary>
    /// Gets the header.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <returns>XNode.</returns>
    public static XNode GetHeader(Message message)
    {
        var xdoc = GetEnvelope(message);

        var strElms = xdoc.DescendantNodes();
        var header = strElms.ElementAt(1);

        return header;
    }

    /// <summary>
    /// Gets the body.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <param name="localName">Name of the local.</param>
    /// <param name="namespaceName">Name of the namespace.</param>
    /// <returns>IEnumerable&lt;XElement&gt;.</returns>
    public static IEnumerable<XElement> GetBody(Message message, string localName, string namespaceName)
    {
        var xdoc = GetEnvelope(message);

        var elements = xdoc.Descendants(XName.Get(localName, namespaceName));

        return elements;
    }
}

ИЛИ вы могли бы создать свой конверт для мыла вручную и использовать WebClient:

using System.Net; 
using System.Xml.Linq;

public static class ClientHelper
{
    public static string Post(string targetUrl, string action, string method, string key, string value)
    {
        var request = BuildEnvelope(method, key, value);
    using (var webClient = new WebClient())
    {
        webClient.Headers.Add("Accept-Header", "application/xml+");
        webClient.Headers.Add("Content-Type", "text/xml; charset=utf-8");
        webClient.Headers.Add("SOAPAction", action);
        var result = webClient.UploadString(targetUrl, "POST", request);

        return result;
    }
}

public static string BuildEnvelope(string method, string key, string value)
{
    XNamespace s = "http://schemas.xmlsoap.org/soap/envelope/";
    XNamespace d = "d4p1";
    XNamespace tempUri = "http://tempuri.org/";
    XNamespace ns = "http://Foo.bar.car";
    XNamespace requestUri = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy";
    var xDoc = new XDocument(
                        new XElement(
                            s + "Envelope",
                            new XAttribute(XNamespace.Xmlns + "s", s),
                            new XElement(
                                s + "Body",
                                new XElement(
                                    ns + method,
                                    new XElement(requestUri + "request", 
                                        new XElement(tempUri + key, value))
                                )
                            )
                        )
                    );
    // hack - finish XDoc construction later
    return xDoc.ToString().Replace("request xmlns=", "request xmlns:d4p1=").Replace(key, "d4p1:" + key);
}

который вызывается с помощью:

return ClientHelper.Post("http://localhost:5050/PolicyService.svc", "http://Foo.bar.car/IPolicyService/GetPolicyData", "GetPolicyData", "REQ_POL_NBR", id);

Тестирование в Fiddler будет выглядеть примерно так:

Post action:    http://localhost:5050/PolicyService.svc
Header:
User-Agent: Fiddler
SOAPAction: http://Foo.bar.car/IPolicyService/GetPolicyData
Content-type: text/xml
Host: localhost:5050
Content-Length: 381

Тело

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
<GetPolicyData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://Foo.bar.car">
<request xmlns:d4p1="http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy">
<d4p1:REQ_POL_NBR>1</d4p1:REQ_POL_NBR>
</request>
</GetPolicyData>
  </s:Body>
</s:Envelope>

Опять же, этот ответ не пытается решить, как вызывать svcUtil по-разному, но чтобы не называть его вообще, поэтому я надеюсь, что боги редактирования не заставляют меня за это; -)

Мой код выше был вдохновлен лучшими разработчиками, чем я, но я надеюсь, что это поможет.

http://blogs.msdn.com/b/stcheng/archive/2009/02/21/wcf-how-to-inspect-and-modify-wcf-message-via-custom-messageinspector.aspx

Ответ 2

Я бы предложил вам создать прокси-сервер wsdl с помощью утилиты командной строки и добавить сгенерированный прокси файл в ваш проект. Он будет работать из каждого проекта, и вы можете найти необходимые конфигурации из output.config, которые будут генерироваться из командной строки.

Если вам нужна команда wsdl и параметры, я могу предоставить вам.