Geeks With Blogs

News
Jason Coyne Jason Coyne's blog

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();
        }
      
    }
}

 

Posted on Tuesday, October 18, 2005 8:07 PM Programming , c# | Back to top


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

# re: Solution : How to handle null values (especially date time) in a c# web service
Requesting Gravatar...
Jason,

Are you the Jason Coyne ("Gaijin42") who
posted a word-wrap routine in April,
2004? I found it handy and used your
long nested if-statement tests in a
more detailed version that wraps at the
nearest white space when the *pixel*
length would be exceeded, rather than
the letter-count, as you had done.
The post I think you may have ]
written appears in

microsoft.public.dotnet.faqs

and came up in a Google word search
for "word wrap".

I wanted to say thank-you here, since
I see nobody was appreciative enough
to thank you for your post back then.
In essence, you cast a note in a
bottle into the sea and somebody
far away, at least in time, did find
it.

Jim C.
(writing from Bay Area)



Left by j.collier@cross-comp.com on Jan 22, 2006 6:13 AM

# re: Solution : How to handle null values (especially date time) in a c# web service
Requesting Gravatar...
I supose you solve the problem long time ago, but just in case:

In the XSD:

<xsd:element name="someDate" type="xsd:date" nillable="true"/>

And in the XML:

<someDate xsi:nil="true"></someDate>

Voalá

(Sorry for my ugly english)

Left by Joao on Nov 06, 2006 7:17 AM

Your comment:
 (will show your gravatar)
 


Copyright © Jason Coyne | Powered by: GeeksWithBlogs.net | Join free