web-dev-qa-db-fra.com

Modifier le type de retour d'une fonction dans WCF sans changer le type de retour d'interface

Je travaille sur un ancien service WCF avec de nombreuses interfaces et services pour un nouveau système. Je souhaite modifier le type de fonction renvoyé sans modifier toutes les interfaces de service et les implémentations comme suit:

interface OperationResult
{
    ErrorInfo Error { get; set; }
}
interface OperationResult<TResult> : OperationResult
{
    TResult Result { get; set; }
}

// old service
interface IService
{
    int TestMethod1(TestMethod1Input input);
    void TestMethod2(TestMethod2Input input);
}
// Interface that client should see
interface IService
{
    OperationResult<int> TestMethod1(TestMethod1Input input);
    OperationResult TestMethod2(TestMethod2Input input);
}

Je pense que je peux gérer les exceptions avec IOperationInvoker mais je ne sais pas comment changer la valeur de retour du service réel et je voulais changer le type de retour de la fonction dans le WSDL en utilisant IWsdlExportExtension. Mais je n'ai pas pu trouver de documentation ni d'échantillon pour aucun d'entre eux.

Quelqu'un peut-il suggérer un échantillon, une documentation ou tout autre moyen pouvant m'éviter d'avoir à modifier trop de services existants?

NOTE: J'ai un autre moyen de le faire en créant une ServiceHost personnalisée qui crée un wrapper dynamique pour le service réel et la passe en tant que type de service au constructeur de ServiceHost. Mais cela devrait être la dernière solution, car cela va générer beaucoup de types dynamiques.

11
BigBoss

Peut-être pourriez-vous envisager d'utiliser IDataContractSurrogate.

Il a trois méthodes relatives à la sérialisation.

  • GetDataContractType est utilisé pour que le type soit sérialisé ou désérialisé ou que le contrat de données soit exporté et importé.
  • GetObjectToSerialize est utilisé pour que l'objet soit sérialisé avant d'être sérialisé.
  • GetDeserializedObject est utilisé pour obtenir l'objet qui a été sérialisé.

https://docs.Microsoft.com/en-us/dotnet/framework/wcf/extending/data-contract-surrogates

6
Ackelry Xu

Tout d’abord, si vos types de retour sont primitifs, j’estime que vous ne pouvez pas modifier le type de manière dynamique. Mon approximation est ci-dessus:

  • Ma classe client 

        var client = new ServiceReference1.Service1Client();
    
        WcfService1.OperationResult<int> resultOk1 = client.TestMethod1(new WcfService1.TestMethod1Input { Throws = false });
        WcfService1.OperationResult<string> resultOk2 = client.TestMethod2(new WcfService1.TestMethod2Input { Throws = false });
    
        WcfService1.IOperationResult resultKo1;
        WcfService1.OperationResult resultKo2;
        try
        {
            resultOk1 = client.TestMethod1(new WcfService1.TestMethod1Input { Throws = true });
        }
        catch (FaultException<WcfService1.OperationResult<int>> ex)
        {
            resultKo1 = ex.Detail;
        }
    
        try
        {
            resultOk2 = client.TestMethod2(new WcfService1.TestMethod2Input { Throws = true });
        }
        catch (FaultException<WcfService1.OperationResult<string>> ex)
        {
            resultKo2 = ex.Detail;
        }
    
  • Mon service

        [ErrorHandlerBehavior]
        public class Service1 : IService1
        {
           public TestMethod1Ouput TestMethod1(TestMethod1Input input)
           {
               if (input.Throws)
               {
                   throw new Exception("a error message 1");
               }
               return new TestMethod1Ouput { OrginalResult = 123 };
           }
    
           public TestMethod2Ouput TestMethod2(TestMethod2Input input)
           {
               if (input.Throws)
               {
                   throw new Exception("a error message 2");
               }
               return new TestMethod2Ouput { OrginalResult = "?"};
           }
       }
    
       [ServiceContract]
       [DataContractOperationResult]
       public interface IService1
       {
           [OperationContract]
           [FaultContract(typeof(OperationResult<int>))]
           TestMethod1Ouput TestMethod1(TestMethod1Input input);
    
           [OperationContract]
           [FaultContract(typeof(OperationResult<string>))]
           TestMethod2Ouput TestMethod2(TestMethod2Input input);
       }
    
       public interface IOperationResult
       {
           string Error { get; set; }
       }
    
       public interface IOperationResult<TResult> : IOperationResult
      {
           TResult Result { get; set; }
       }
    
       [DataContract]
       public class OperationResult : IOperationResult
       {
           [DataMember(Name = "Error")]
           public string Error { get; set; }
       }
    
       [DataContract]
       public class OperationResult<TResult> : OperationResult, IOperationResult<TResult>, IOperationResult
       {
           [DataMember(Name = "Result")]
           public TResult Result { get; set; }
       }
    
       public class TestMethod1Ouput
       {
           public int OrginalResult { get; set; }
       }
    
       public class TestMethod1Input
       {
           public bool Throws { get; set; }
       }
    
       public class TestMethod2Ouput
       {
           public string OrginalResult { get; set; }
       }
    
       public class TestMethod2Input
       {
           public bool Throws { get; set; }
       }
    
  • Classes pour changer les réponses de succès:

      public sealed class DataContractOperationResultAttribute : Attribute, IContractBehavior, IOperationBehavior, IWsdlExportExtension, IDataContractSurrogate
       {
           #region IContractBehavior Members
    
           public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, BindingParameterCollection parameters)
           {
           }
    
           public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime proxy)
           {
               foreach (OperationDescription opDesc in description.Operations)
               {
            ApplyDataContractSurrogate(opDesc);
               }
           }
    
    public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatch)
    {
        foreach (OperationDescription opDesc in description.Operations)
        {
            ApplyDataContractSurrogate(opDesc);
        }
    }
    
    public void Validate(ContractDescription description, ServiceEndpoint endpoint)
    {
    }
    
    #endregion
    
    #region IWsdlExportExtension Members
    
    public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
    {
        if (exporter == null)
            throw new ArgumentNullException("exporter");
    
        object dataContractExporter;
        XsdDataContractExporter xsdDCExporter;
        if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter), out dataContractExporter))
        {
            xsdDCExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
            exporter.State.Add(typeof(XsdDataContractExporter), xsdDCExporter);
        }
        else
        {
            xsdDCExporter = (XsdDataContractExporter)dataContractExporter;
        }
        if (xsdDCExporter.Options == null)
            xsdDCExporter.Options = new ExportOptions();
    
        if (xsdDCExporter.Options.DataContractSurrogate == null)
            xsdDCExporter.Options.DataContractSurrogate = new DataContractOperationResultAttribute();
    }
    
    public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
    {
    }
    
    #endregion
    
    #region IOperationBehavior Members
    
    public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
    {
    }
    
    public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
    {
        ApplyDataContractSurrogate(description);
    }
    
    public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
    {
        ApplyDataContractSurrogate(description);
    }
    
    public void Validate(OperationDescription description)
    {
    }
    
    #endregion
    
    private static void ApplyDataContractSurrogate(OperationDescription description)
    {
        DataContractSerializerOperationBehavior dcsOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (dcsOperationBehavior != null)
        {
            if (dcsOperationBehavior.DataContractSurrogate == null)
                dcsOperationBehavior.DataContractSurrogate = new DataContractOperationResultAttribute();
        }
    }
    
    #region IDataContractSurrogate Members
    
    public Type GetDataContractType(Type type)
    {
        // This method is called during serialization and schema export
        System.Diagnostics.Debug.WriteLine("GetDataContractType " + type.FullName);
        if (typeof(TestMethod1Ouput).IsAssignableFrom(type))
        {
            return typeof(OperationResult<int>);
        }
        if (typeof(TestMethod2Ouput).IsAssignableFrom(type))
        {
            return typeof(OperationResult<string>);
        }
    
        return type;
    }
    
    public object GetObjectToSerialize(object obj, Type targetType)
    {
        //This method is called on serialization.
        System.Diagnostics.Debug.WriteLine("GetObjectToSerialize " + targetType.FullName);
        if (obj is TestMethod1Ouput)
        {
            return new OperationResult<int> { Result = ((TestMethod1Ouput)obj).OrginalResult, Error = string.Empty };
        }
        if (obj is TestMethod2Ouput)
        {
            return new OperationResult<string> { Result = ((TestMethod2Ouput)obj).OrginalResult, Error = string.Empty };
        }
        return obj;
    }
    
    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }
    
    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }
    
    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
    
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }
    
    public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }
    
    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    
    }
    
    #endregion
       }
    
  • Classes pour changer les réponses d'erreur:

      public class ErrorHandlerBehavior : Attribute, IErrorHandler, IServiceBehavior
       {
           #region Implementation of IErrorHandler
    
           public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
           {
               ServiceEndpoint endpoint =  OperationContext.Current.Host.Description.Endpoints.Find(OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri);
        DispatchOperation dispatchOperation = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations.Where(op => op.Action == OperationContext.Current.IncomingMessageHeaders.Action).First();
        OperationDescription operationDesc = endpoint.Contract.Operations.Find(dispatchOperation.Name);
        var attributes = operationDesc.SyncMethod.GetCustomAttributes(typeof(FaultContractAttribute), true);
    
        if (attributes.Any())
        {
            FaultContractAttribute attribute = (FaultContractAttribute)attributes[0];
            var type = attribute.DetailType;
            object faultDetail = Activator.CreateInstance(type);
            Type faultExceptionType = typeof(FaultException<>).MakeGenericType(type);
            FaultException faultException = (FaultException)Activator.CreateInstance(faultExceptionType, faultDetail, error.Message);
            MessageFault faultMessage = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, faultMessage, faultException.Action);
        }
    }
    
    public bool HandleError(Exception error)
    {
        return true;
    }
    
    #endregion
    
    #region Implementation of IServiceBehavior
    
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
    
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }
    
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
    
            if (channelDispatcher != null)
            {
                channelDispatcher.ErrorHandlers.Add(this);
            }
        }
    }
    
    #endregion
       }
    

J'espère que ma solution vous aidera.

1
Jose M.

Effectuez un numéro de version différent avec les modifications de méthode attendues. Normalement, nous ne sommes pas censés arrêter celui qui est livré. Les clients/interfaces doivent être mis à jour le service avec les nouvelles modifications si nécessaire. 

1
A. Sakthivel

C'est un peu un bidouillage, mais pourriez-vous créer une classe de base contenant les anciennes implémentations de méthodes appelant les nouvelles méthodes surchargées? De cette façon, il vous suffira d'hériter de la classe de base et elle ne devrait pas générer d'erreur.

0
Edward Gahan