Solution : How to handle null values (especially date time) in a c# web service

Here is another programming blog entry. Sorry for the normal friends :)

I recently ran into a problem with a web service I was trying to call from c# where the web service returned null dates (and other null values on elements that end up de-serialized as value types)

I didn't find any good solutions online (other than wait for nullable types in c# 2.0 or change the service, neither of which was applicable in my situation)

I ended up working out a solution with microsoft, and here it is. The following class will loop through a returned soap call, and set any datetime fields that are null to be DateTime.MinDate

This can easily be extended to enable any other fields that need default values (enums, etc)

To use this class, edit the reference.cs file that is generated by .net, and add this decoration to each webMethod call that you want to apply the fix to.

[SoapFixer.SoapFixer()]

for example, one of my functions looks like this :

[System.Web.Services.Protocols.SoapHeaderAttribute("AuthenticationInfoValue")]
 [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:HPD_Help_Desk/OpGetList", RequestNamespace="urn:HPD_Help_Desk", ResponseNamespace="urn:HPD_Help_Desk", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlElementAttribute("getListValues")]
[SoapFixer.SoapFixer()]
        public GetListOutputMapGetListValues[] OpGetList(string Qualification) {
            object[] results = this.Invoke("OpGetList", new object[] {
                        Qualification});
            return ((GetListOutputMapGetListValues[])(results[0]));
        }


The SoapFixer class decends from SoapExtensionAttribute, which makes all the magic happen. The class loops through each node in the returned XML, and uses reflection to find the appropriate field in the destination class. If the destination field is a datetime, and the value is null, the value is converted to MinDate (1/1/0001)

The field lookup starts out by assuming that the node name in the xml is identical to the class field name. However, this is not always true.  In some cases (notably where the XML element name is an invalid field name, like it has a - in it)   they are not the same. In this case, the field is decorated with a XmlElementAttribute attribute that specifies the real name. The GetFieldInfo function(s) try to grab the field name assuming an exact match, and if that fails tries to grab the attributes and get a match that way. Any fields that are not found are written out to the console.

using System;
using System.IO;
using System.Web;
using System.Web.Services.Protocols;
using System.Web.Services.Description;
using System.Xml;
using Rockwell.Library.RemedyWebService;
using System.Reflection;
using System.Xml.Serialization;

namespace Rockwell.Library.DataAccessor.Reusable
{
    ///


    /// Summary description for CaptureSoap.
    ///

    [AttributeUsage(AttributeTargets.Method)]
    public class RemedySoapFixer : SoapExtensionAttribute
    {
        private int m_Priority = 0;

        public override Type ExtensionType
        {
            get
            {
                return typeof(DeleteDate);
            }
        }

        public override int Priority
        {
            get
            {
                return m_Priority;
            }
            set
            {
                m_Priority = value;
            }
        }

    }

    public class DeleteDate : SoapExtension
    {
        private Stream soapStream;
        private Stream customStream;
//Switch this to be your destination field type. (IE, the return value of your web method)
        static GetListOutputMapGetListValues exampleItem = new GetListOutputMapGetListValues();

        static FieldInfo[] fields = ((object)exampleItem).GetType().GetFields();

        public override object GetInitializer(Type serviceType)
        {  
            return GetType();
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {

        }

        public override Stream ChainStream(Stream stream)
        {
            soapStream = stream;
            customStream = new MemoryStream();
            return customStream;
        }
      
        public override void ProcessMessage(SoapMessage message)
        {
            switch(message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:                  
                    break;
                case SoapMessageStage.AfterSerialize:
                    ChangeOutgoingSoap(message);
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    ChangeIncomingSoap(message);
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
                default:
                    break;
            }
        }
          
  
        public void ChangeOutgoingSoap(SoapMessage message)
        {
            //reads the content of the customStream
            //modify it, log it, whatever
            //and write it to soapStream

            Stream tempstream = new MemoryStream();
            customStream.Position = 0;
            Copy (customStream,tempstream);
            customStream.Position = 0;
            tempstream.Position = 0;
            XmlDocument doc = new XmlDocument();
            doc.Load(tempstream);
            //modify outgoing XML here
            //doc.GetElementsByTagName("elementname").Item(0).InnerText = "whatever";
            doc.Save(customStream);  
  
            customStream.Position = 0;
            Copy(customStream, soapStream);
        }

        public void ChangeIncomingSoap(SoapMessage message)
        {
            //reads the content of the soapStream
            //modify it, log it, whatever
            //and write it to customStream
  
            Copy(soapStream, customStream);
            customStream.Position = 0;

            Stream tempstream = new MemoryStream();
            customStream.Position = 0;
            Copy (customStream,tempstream);
            customStream.Position = 0;
            tempstream.Position = 0;
            XmlDocument doc = new XmlDocument();
            doc.Load(tempstream);
 
            FixNodes(doc);
          
        
            doc.Save(customStream);
            customStream.Position = 0;
        }

 

        public FieldInfo GetFieldInfo(object target, string fieldName)
        {
            if (target == null)
                throw new ArgumentNullException("target", "target must be an instantiated object.");

            if (fieldName == null || fieldName.Trim().Length==0)
                throw new ArgumentOutOfRangeException("fieldName", "Fieldname must be a non empty string.");

          
      
            Type objectType = target.GetType();

          
          

            FieldInfo privateField = objectType.GetField(fieldName,
                BindingFlags.Instance
                | BindingFlags.NonPublic
                | BindingFlags.Public
                | BindingFlags.IgnoreCase
                | BindingFlags.FlattenHierarchy
              
                );

          
            if (privateField == null)
                throw new ArgumentOutOfRangeException("FieldName", objectType.FullName + " does not have a field : " + fieldName + ".");

            return privateField;
        }

        private FieldInfo GetFieldInfo(XmlNode node)
        {
            if (node.Name =="xml"
                || node.Name =="#text")
                return null;

            FieldInfo fieldInfo = null;
            try
            {
                fieldInfo = GetFieldInfo(exampleItem, node.Name);
            }
            catch(ArgumentOutOfRangeException)
            {
                foreach (FieldInfo currentFieldInfo in fields)
                {
                    if (currentFieldInfo.Name == node.Name)
                        fieldInfo = currentFieldInfo;
                  
                    if (fieldInfo==null)
                    {
                       object[] attributes = currentFieldInfo.GetCustomAttributes(false);
                        foreach (object currentAttribute in attributes)
                        {
                         
                            XmlElementAttribute currentElementAttribute;
                            currentElementAttribute = currentAttribute as XmlElementAttribute;
                            if (currentElementAttribute!=null)
                            {
                                if (currentElementAttribute.ElementName == node.Name)
                                   fieldInfo = currentFieldInfo;
                            }
                        }
                    }

                }
      
            }
            return fieldInfo;
        }

        private void FixNodes (XmlNode node)
        {
            foreach (XmlNode childNode in node.ChildNodes)
            {
                FixNodes (childNode);
            }

            FieldInfo fieldInfo = GetFieldInfo(node);

            if (fieldInfo!=null)
            {
                if (fieldInfo.FieldType ==typeof(DateTime))
                  
                {
                    if (node.InnerText.Length==0)
                        node.InnerText = "0001-01-01T00:00:00-06:00";
                    else
                    {
                        DateTime dt;
                        try
                        {
                            dt = DateTime.Parse(node.InnerText);
                        }

                        catch (FormatException)
                        {
                            Console.WriteLine ("{0}, {1}", node.Name, node.InnerText);
                            node.InnerText = "0001-01-01T00:00:00-06:00";

                        }
                    }
                }

            
      
      
          
            }
            else
            {
            if (node.Name!="#text")
                Console.WriteLine("field not found for {0}", node.Name);
            }
        }

        private void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
      
    }
}

Print | posted on Tuesday, October 18, 2005 2:15 PM

Feedback

# re: Solution : How to handle null values (especially date time) in a c# web service

left by Jose Manuel at 4/9/2008 10:56 AM Gravatar
Hello, I'm Jose Manuel, from Barcelona.
In c#, I have a WebService with a WebMethod

public ActualitzacioResposta Actualitzacio
(
long ID,
int IDIOMA,
int TIPUS,
DateTime CONSULTA_DATA_RESOLUCIO
}

the input CONSULTA_DATA_RESOLUCIO can be null, how i handle this?

Please helpme. if you can sendme a email please.
Thanks
Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: