Изменить тип возврата функции в WCF без изменения типа возврата интерфейса

Я работаю над старым сервисом WCF со многими интерфейсами и сервисами для новой системы. Я хочу изменить тип возвращаемых функций без изменения всех сервисных интерфейсов и реализаций следующим образом:

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

Я думаю, что могу обрабатывать исключения с IOperationInvoker но я не знаю, как изменить возвращаемое значение фактического сервиса, и я хотел изменить тип возврата функции в WSDL, используя IWsdlExportExtension. Но я не смог найти ни одной документации или образца скважины ни для одного из них.

Может кто-нибудь предложить какой-либо образец или документацию или любой другой способ, который может избавить меня от необходимости менять слишком много уже существующих служб?

ПРИМЕЧАНИЕ: у меня есть другой способ сделать это, создав пользовательский ServiceHost который создает динамическую оболочку для фактического сервиса и передает его как тип сервиса в конструктор ServiceHost. Но это должно быть последним решением, поскольку оно будет генерировать много динамических типов.

Ответы

Ответ 1

Может быть, вы могли бы рассмотреть возможность использования IDataContractSurrogate.

У него есть три метода, относящихся к сериализации.

  • GetDataContractType используется для получения типа для сериализации или десериализации или для получения и обработки данных для экспорта и импорта.
  • GetObjectToSerialize используется для сериализации объекта до его сериализации.
  • GetDeserializedObject используется для получения объекта, который был сериализован.

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

Ответ 2

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

  • Мой клиентский класс

        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;
        }
    
  • Мой сервис

        [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; }
       }
    
  • Классы для изменения успешных ответов:

      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
       }
    
  • Классы для изменения ответов об ошибках:

      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
       }
    

Я надеюсь, что мое решение поможет вам.

Ответ 3

Сделайте другой номер выпуска с ожидаемыми изменениями метода. Обычно мы не должны останавливать доставленный. Клиенты/Интерфейсы должны обновлять сервис новыми изменениями, если это необходимо.

Ответ 4

Это что-то вроде хака, но не могли бы вы создать базовый класс со старыми реализациями методов, которые вызывают новые перегруженные методы? Таким образом, вам просто нужно наследовать от базового класса, и он не должен выдавать никаких ошибок.