33 votes

Comment obtenir une référence de service pour générer correctement avec des contrats de message basés sur un WSDL tiers, ou forcer l'absence de contrats de message dans le projet de service WF

J'ai un problème avec le WSDL tiers donné. Je suis capable de créer facilement un proxy de service à partir d'une application console qui fonctionne, mais pas à partir d'un service WF4 WF. Le proxy généré dans ce dernier cas est clairement défectueux, impliquant spécifiquement 2 problèmes : a) Contrats de message toujours générés sans demande ou nécessaire b) Messages de réponse incorrects et noms de wrapper xml utilisés, entraînant des objets de réponse nuls et une désérialisation échouée

Le problème auquel je suis confronté réside dans la génération effective de la classe Reference.cs sur la base du WSDL tiers. Dans le WSDL, il y a de nombreuses opérations, et dans l'ordre d'apparition, 2 d'entre elles sont comme suit :

        Service de réponse de vérification
        Le service gère (coupé)

...

        Insertion d'instruction dans le système de correspondance
        Ce service (coupé)

Ce que cela entraîne dans le Reference.cs est le C# suivant :

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

...

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

Remarquez que pour une raison inconnue, l'opération mi102 est générée avec le mauvais message de réponse pu013Response, qui est déclaré comme suit :

 [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 { 

Remarquez le WrapperName qui empêche le sérialiseur XML de reconnaître la réponse, qui est mi102Response, donc pour toutes les opérations qui ne sont pas pu013, j'obtiens toujours une réponse nulle.

Cela ne se produit PAS non plus si j'ajoute une référence à partir d'une application console. Cela ne génère pas de contrats de message, et dans ce cas, l'invocation et la réponse fonctionnent.

Qu'est-ce qui est différent ? Est-ce que svcutil est invoqué en arrière-plan ? Si c'est le cas, qu'est-ce qui est différent au niveau des paramètres utilisés ? Est-ce que svcutil peut être utilisé pour générer également les activités xamlx, afin que je puisse trouver une solution de contournement en ligne de commande ?

Il semble s'agir d'un bug de VS / Ajout de référence de service. J'ai besoin de conseils urgents à ce sujet, car l'alternative est de corriger manuellement de nombreuses opérations dans le Reference.cs.

Idéalement, je cherche un moyen d'exécuter facilement et automatiquement svcutil ou Ajouter une référence de service de sorte que la classe Reference soit correcte et que les activités xamlx soient générées. Il serait utile d'avoir une explication sur la différence et ce qui se passe en coulisses.

MISE À JOUR: Les contrats de message générés dans l'application console entraînent le même problème - des déclarations de réponse incorrectes. Le problème disparaît si des paramètres sont utilisés à la place des messages, qui ne sont pas disponibles à partir d'une application de service WF.

2voto

James Fleming Points 1150

Je suis loin d'être une autorité en la matière, et bien que la réponse ci-dessous ne corresponde pas exactement à votre problème, ma récente expérience de connexion sans proxylème à un service pourrait vous apporter quelques éclaircissements, à vous ou à la prochaine personne confrontée à un problème similaire.

Je commencerais par voir si vous pouvez lancer manuellement la requête SOAP à l'aide de fiddler, et voir si vous êtes en mesure de créer le bon message et de l'envoyer. Puisque vous décrivez les outils d'automatisation comme étant bogués (ou peut-être qu'il y a un problème de configuration que vous n'arrivez pas à résoudre). Quoi qu'il en soit, une bonne compréhension de la forme du contrat et la possibilité d'effectuer un test fiable avec fiddler peuvent apporter des éclaircissements.

Vous n'avez pas nécessairement besoin d'utiliser un proxy. Vous pouvez créer votre propre message de savon et l'envoyer de deux façons. La première consiste à utiliser ChannelFactory.

  1. Créez le corps de votre message (si nécessaire, la classe de message peut fonctionner sans corps).
  2. Créez votre message
  3. Envoyez votre message par l'intermédiaire de ChannelFactory

Pour l'étape 1, vous pouvez construire votre message en créant un simple POCO pour refléter ce qui est attendu dans votre contrat. Vous devriez être en mesure de dériver ce qu'est cette classe via le WSDL.

Supposons que le service se présente sous la forme suivante :

[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);
    }
}

Vous auriez besoin d'une classe de ce type :

[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; }
}

et vous l'appelleriez ainsi :

    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;
    }

Ce que vous recevrez en retour sera un message, que vous devrez désérialiser, et il y a quelques façons OOTB de le faire, (Message.GetReaderAtBodyContents, Message.GetBody) en accord avec le thème "hand-rolled" :

    /// <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;
    }
}

OU vous pouvez construire votre enveloppe de savon à la main et utiliser 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);
}

qui est appelé avec :

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

Le tester dans Fiddler ressemblerait à quelque chose comme ceci :

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

Le corps :

<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>

Encore une fois, cette réponse n'essaie pas de résoudre comment invoquer svcUtil différemment, mais d'éviter de l'appeler complètement, donc j'espère que les dieux de l'édition ne me puniront pas pour cela ;-)

Mon code ci-dessus a été inspiré par de meilleurs développeurs que moi, mais j'espère qu'il sera utile.

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

0voto

Je vous suggère de générer un proxy WSDL à l'aide de l'utilitaire en ligne de commande et d'ajouter le fichier proxy généré à votre projet. Il fonctionnera depuis n'importe quel projet et vous pourrez trouver les configurations requises dans output.config qui sera généré depuis l'utilitaire en ligne de commande.

Si vous avez besoin de la commande et des options WSDL, je peux vous les fournir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X