У меня есть следующий контракт в веб-службе на основе WCF:
public List<string> GetAllPossibleQueryEngineHostNames(Instance instance);
Я называю это от клиента так:
string instance = "value";
svc.GetAllPossibleQueryEngineHostNames(instance);
instance
Note - это объект String
, а не Instance
. Я получаю исключение:
При попытке десериализовать сообщение форматировщик исключил исключение: при попытке десериализации параметра http://tempuri.org/:instance произошла ошибка. Сообщение InnerException было "Ошибка в строке 1 позиция 152. Ожидание состояния" Элемент ".. Обнаружен" Текст "с именем" ", пространство имен". ". Дополнительную информацию см. В InnerException.
Я ищу способ исправить эту проблему, не меняя код клиента, так как мы должны иметь возможность поддерживать старых клиентов, подключающихся к этой службе.
Обратите внимание, что класс Instance
на клиенте и сервере имеет неявный оператор преобразования:
public static implicit operator Instance(string value)
{
// Converts string to an Instance
}
Однако WCF, похоже, не учитывает это при десериализации. Есть ли способ точно контролировать, как WCF будет десериализовать Instance
экземпляра, позволяя ему быть либо строкой, либо Instance
объекта?
Получил эту работу. Здесь мой код, если кто-то заинтересован в решении. В принципе, я создал новый файл Legacy.cs со следующими классами:
public class InstanceSerializer : XmlObjectSerializer
{
const string localName = "instance";
public override bool IsStartObject(XmlDictionaryReader reader)
{
return String.Equals(reader.LocalName, localName, StringComparison.OrdinalIgnoreCase);
}
public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
{
string xml = reader.ReadOuterXml();
XDocument doc = XDocument.Parse(xml);
string shortCode = doc.Descendants()
.Where(e => e.Name.LocalName == "ShortCode")
.Select(e => e.Value)
.FirstOrDefault();
string connStr = doc.Descendants()
.Where(e => e.Name.LocalName == "ConnectionString")
.Select(e => e.Value)
.FirstOrDefault();
if (connStr != null || shortCode != null) // Instance passed as Instance object
{
return new Instance(shortCode, connStr);
}
// Instance passed as String
Instance instance = ((XElement) doc.FirstNode).Value;
return instance;
}
public override void WriteEndObject(XmlDictionaryWriter writer)
{
writer.WriteEndElement();
}
public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
{
}
public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
{
writer.WriteStartElement(localName);
}
}
public class InstanceBehavior : DataContractSerializerOperationBehavior
{
public InstanceBehavior(OperationDescription operation) : base(operation) { }
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
{
return typeof(Instance) == type
? new InstanceSerializer()
: base.CreateSerializer(type, name, ns, knownTypes);
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
{
return typeof(Instance) == type
? new InstanceSerializer()
: base.CreateSerializer(type, name, ns, knownTypes);
}
}
public class SupportStringInstanceAttribute : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
ReplaceSerializerOperationBehavior(contractDescription);
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
ReplaceSerializerOperationBehavior(contractDescription);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
private static void ReplaceSerializerOperationBehavior(ContractDescription contract)
{
foreach (OperationDescription od in contract.Operations)
{
for (int i = 0; i < od.Behaviors.Count; i++)
{
DataContractSerializerOperationBehavior dcsob = od.Behaviors[i] as DataContractSerializerOperationBehavior;
if (dcsob != null)
{
od.Behaviors[i] = new InstanceBehavior(od);
}
}
}
}
}
Наконец, я добавил [SupportStringInstance]
в начало моей реализации службы (должен либо работать интерфейс, либо класс).
Обратите внимание, что это поддерживает десериализацию объекта Instance
(передается ли он как строка или объект). Вам нужно будет реализовать InstanceSerializer.WriteObjectContent
а также поддерживать сериализацию, к счастью, мне не нужно делать (по крайней мере пока).
Стандартный DataContractSerializer не собирается автоматически конвертировать вашу строку в тип экземпляра. Вы не можете перегрузить этот метод в WCF, поэтому я бы предложил вам просто взять строку в качестве аргумента, а затем выполнить преобразование для экземпляра.
public List<string> GetAllPossibleQueryEngineHostNames(string instanceName)
{
var instance = Instance(instanceName);
// Do everything else
}
Тогда я подумал бы о том, чтобы сохранить его таким образом или добавить другой метод, который фактически примет аргумент экземпляра.
Последняя опция будет принимать в байте [] в качестве аргумента и попытаться обработать его до нужного вам типа и провести проверку, чтобы решить, является ли она строкой или типом.