views:

114

answers:

1

I have a WCF service and cannot use DataContracts since I need more control over the XML received and sent to this service. As such, I use XmlRoot, and XmlElement... the problem I'm running into now is that my class that the receiving xml gets deserialized into and the serialized response both need to have the same root name, and when I try to set both of those classes with:

[XmlRoot(ElementName = "myRoot")] 

I get an error saying that the root name was already used. Is there a simple workaround for this? I tried putting my response class in a separate namespace but that didn't appear to work.

If some of the variables aren't set in my response class that gets serialized then I don't them to get serialized and returned in the response... is there an option I'm missing to do this... I was able to do this with a DataContract, but cant figure it out with XmlElements

A: 

One way to achieve this is to intercept the XML response and change the root element name to something unique before it is deserialized. This can be done pretty easily with a custom IClientMessageFormatter and an associated attribute on the operation.

I just wrote this up, so "wet paint" and all, but here is what that looks like:

/// <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
}