WCF, XmlRoot и дополнительные параметры
У меня есть служба WCF, и я не могу использовать DataContracts, поскольку мне нужно больше контроля над XML, полученным и отправленным этой службе. Поэтому я использую XmlRoot и XmlElement... проблема, с которой я сейчас сталкиваюсь, заключается в том, что мой класс, в который десериализуется принимающий xml, и сериализованный ответ должны иметь одинаковое корневое имя, и когда я пытаюсь установить оба этих класса с:
[XmlRoot(ElementName = "myRoot")]
Я получаю сообщение о том, что корневое имя уже было использовано. Есть ли простой обходной путь для этого? Я попытался поместить свой класс ответа в отдельное пространство имен, но это не сработало.
Если некоторые переменные не заданы в моем классе ответа, который сериализуется, то я не могу их сериализовать и вернуть в ответе... есть ли вариант, который мне не хватает, чтобы сделать это... Я смог сделать это с помощью DataContract, но не могу понять это с помощью XmlElements
1 ответ
Один из способов добиться этого - перехватить ответ XML и изменить имя корневого элемента на что-то уникальное перед его десериализацией. Это можно сделать довольно легко с помощью пользовательского IClientMessageFormatter и связанного с ним атрибута операции.
Я только что написал это, так что "мокрой краской" и все, но вот как это выглядит:
/// <summary>
/// An operation attribute that changes the XML root name in responses
/// </summary>
/// <example>
///
/// [ServiceContract]
/// [XmlSerializerFormat]
/// public interface IRedBlueServiceApi
/// {
/// [OperationContract]
/// [WebGet(...)]
/// [XmlChangeRoot("RedResponse")]
/// RedResponse GetRed();
///
/// [OperationContract]
/// [WebGet(...)]
/// [XmlChangeRoot("BlueResponse")]
/// BlueResponse GetBlue();
/// }
///
/// [XmlRoot("RedResponse")]
/// public class RedResponse
/// {...}
///
/// [XmlRoot("BlueResponse")]
/// public class BlueResponse
/// {...}
/// </example>
[DefaultProperty("NewRootElementName")]
public class XmlChangeRootAttribute : Attribute, IOperationBehavior
{
public XmlChangeRootAttribute(string newRootElementName)
{
NewRootElementName = newRootElementName;
}
public string NewRootElementName { get; set; }
#region IOperationBehavior Members
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
// Inject our xml root changer into the client request/response pipeline
clientOperation.Formatter = new XmlRootChangeFormatter(clientOperation.Formatter, NewRootElementName);
}
#endregion
#region Unrelated Overrides
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription,
BindingParameterCollection bindingParameters)
{
}
public void Validate(OperationDescription operationDescription)
{
}
#endregion
}
/// <summary>
/// A simple wrapper around an existing IClientMessageFormatter
/// that alters the XML root name before passing it along
/// </summary>
public class XmlRootChangeFormatter : IClientMessageFormatter
{
private readonly IClientMessageFormatter _innerFormatter;
private readonly string _newRootElementName;
public XmlRootChangeFormatter(IClientMessageFormatter innerFormatter, string newRootElementName)
{
if (innerFormatter == null)
throw new ArgumentNullException("innerFormatter");
if (String.IsNullOrEmpty(newRootElementName))
throw new ArgumentException("newRootElementName is null or empty");
_innerFormatter = innerFormatter;
_newRootElementName = newRootElementName;
}
#region IClientMessageFormatter Members
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
return _innerFormatter.SerializeRequest(messageVersion, parameters);
}
public object DeserializeReply(Message message, object[] parameters)
{
if (!message.IsEmpty)
{
var doc = XDocument.Load(message.GetReaderAtBodyContents());
if (doc.Root == null)
throw new SerializationException("Could not find root in WCF messasge " + message);
// Change the root element name
doc.Root.Name = _newRootElementName;
// Create a new 'duplicate' message with the modified XML
var modifiedReply = Message.CreateMessage(message.Version, null, doc.CreateReader());
modifiedReply.Headers.CopyHeadersFrom(message.Headers);
modifiedReply.Properties.CopyProperties(message.Properties);
message = modifiedReply;
}
return _innerFormatter.DeserializeReply(message, parameters);
}
#endregion
}