Search code examples
c#xmlwcfdatacontractserializer

need to have child by using DataContractSerializer to populate XML


I have a method that put the error messages in 1 xml and send it to client. In case of error, errors can be several, I am returning the list pf errors that are in XMLErrMessage. I want to show them in comment but each error as 1 xml child:

  <comments>
     <comment>XMLErrMessage1</comment>
     <comment>XMLErrMessage2</comment>
    <comment>XMLErrMessage3</comment>
  </comments>

this is my method:

    public string ProcessXML(CommonLibrary.Model.TransferData dto, bool Authenticated)
    {
        DataContractSerializer dcs = new DataContractSerializer(typeof(CFCConnectResponse));
        MemoryStream ms = new MemoryStream();
        utility.utilities utl = new utility.utilities();
        List<string> XMLErrMessage =null;

        if (Authenticated)
        {
            if (!string.IsNullOrEmpty(dto.xml))
            {
                XMLErrMessage = utl.validateXML(dto.xml, xsdFilePath, currentSchema);

                if (XMLErrMessage.Count==1)
                {
                    dcs.WriteObject(ms, new CFCConnectResponse() { StatusCode = 101, StatusDescription = "Success" });
                    ms.Position = 0;
                }
                else
                {
                    dcs.WriteObject(ms, new CFCConnectResponse() { StatusCode = 201, StatusDescription = "XML Validation Fails", Comments=XMLErrMessage });
                    ms.Position = 0;
                }
            }
        }
        else
        {
            dcs.WriteObject(ms, new CFCConnectResponse() { StatusCode = 401, StatusDescription = "Authentication Fails" });
           // ms.Position = 0;
        }
        string s = new StreamReader(ms).ReadToEnd();  // xml result
        Console.WriteLine(s);
        return s;
    }

and this is contract class:

public class CFCConnectResponse
{
    [DataMember]
    public int StatusCode;
    [DataMember]
    public string StatusDescription;
    [DataMember]
    public List<string> Comments;

Solution

  • The CollectionDataContract attribute allows you to control the collection element names, however since it can only target a class or struct, you must create a custom subclass of List<T> with the desired contract, like so:

    [DataContract(Namespace = "")]
    [KnownType(typeof(CommentList))]
    public class CFCConnectResponse
    {
        [DataMember]
        public int StatusCode;
        [DataMember]
        public string StatusDescription;
        [DataMember(Name="comments")]
        public CommentList Comments;
    }
    
    [CollectionDataContract(ItemName = "comment", Namespace="")]
    public class CommentList : List<string>
    {
        public CommentList()
            : base()
        {
        }
    
        public CommentList(params string[] strings)
            : base(strings)
        {
        }
    
        public CommentList(IEnumerable<string> strings)
            : base(strings)
        {
        }
    }
    

    And then, to test:

    public static class TestCFCConnectResponse
    {
        static CFCConnectResponse CreateTest()
        {
            return new CFCConnectResponse()
            {
                StatusCode = 101,
                StatusDescription = "here is a description",
                Comments = new CommentList("XMLErrMessage1", "XMLErrMessage2", "XMLErrMessage3"),
            };
        }
    
        public static void Test()
        {
            var response = CreateTest();
    
            try
            {
                var xml = DataContractSerializerHelper.GetXml(response);
                Debug.Write(xml);
                var newResponse = DataContractSerializerHelper.GetObject<CFCConnectResponse>(xml);
                Debug.Assert(newResponse != null);
                Debug.Assert(response.StatusCode == newResponse.StatusCode);
                Debug.Assert(response.StatusDescription == newResponse.StatusDescription);
                Debug.Assert(newResponse.Comments.SequenceEqual(response.Comments));
            }
            catch (Exception ex)
            {
                Debug.Assert(false, ex.ToString());
            }
        }
    }
    

    This produces the following output, with no asserts:

    <?xml version="1.0" encoding="utf-16"?>
    <CFCConnectResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <StatusCode>101</StatusCode>
        <StatusDescription>here is a description</StatusDescription>
        <comments>
            <comment>XMLErrMessage1</comment>
            <comment>XMLErrMessage2</comment>
            <comment>XMLErrMessage3</comment>
        </comments>
    </CFCConnectResponse>
    

    Update

    If changing CFCConnectResponse.CommentList to get & set a CommentList requires too many changes to legacy code, you can do the following:

    [DataContract(Namespace = "")]
    [KnownType(typeof(CommentList))]
    public class CFCConnectResponse
    {
        [DataMember]
        public int StatusCode;
    
        [DataMember]
        public string StatusDescription;
    
        [IgnoreDataMember]
        public List<string> Comments { get; set; }
    
        [DataMember(Name = "comments")]
        private CommentList SerializableComments
        {
            get
            {
                return new CommentList(Comments);
            }
            set
            {
                Comments = value.ToList();
            }
        }
    }
    

    This preserves the List<string> Comments property while serializing & deserializing a CommentList.