Изменить тип возврата функции в 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
Это что-то вроде хака, но не могли бы вы создать базовый класс со старыми реализациями методов, которые вызывают новые перегруженные методы? Таким образом, вам просто нужно наследовать от базового класса, и он не должен выдавать никаких ошибок.