Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change return type of a function in WCF without changing interface return type

Tags:

c#

wcf

I'm working on an old WCF service with many interfaces and services for a new system. I want to change return type of functions without changing all service interfaces and implementations as follow:

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

I think I can handle exceptions with IOperationInvoker but I don't know how change return value of actual service, and I wanted to change the return type of the function in the WSDL using IWsdlExportExtension. But I couldn't find any well documentation or sample for either of them.

Can anyone suggest any sample or documentation or any other way that can save me the trouble of changing too many already existing services?

NOTE: I have another way of doing this by creating a custom ServiceHost that create a dynamic wrapper for actual service and pass it as service type to constructor of ServiceHost. But this should be last solution since it will generate many dynamic types.

like image 939
BigBoss Avatar asked Dec 24 '18 16:12

BigBoss


2 Answers

Maybe you could consider using IDataContractSurrogate.

It has three methods relating to serializing.

  • GetDataContractType is used to get the type to serialize or deserialize or get the datacontract to export and import.
  • GetObjectToSerialize is used to get the object to serialize before it is serialized.
  • GetDeserializedObject is used to get the object which has been serialized.

https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/data-contract-surrogates

like image 172
Ackelry Xu Avatar answered Nov 13 '22 13:11

Ackelry Xu


First of all, if your return types are primitive then I think you can’t change the type dynamically. My approximation is above:

  • My client class

        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;
        }
    
  • My 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 to change de success responses:

      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 to change de error responses:

      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
       }
    

I hope my solution help you.

like image 1
Jose M. Avatar answered Nov 13 '22 12:11

Jose M.