views:

316

answers:

7

Hello All,

I'm adding functionality to one of our existing (but broken) systems. It grabs an XML file from a web service, parses it and then does some stuff before packing it back into our database.

the previous developer (who has now left) left me this little gem:

http://dl.getdropbox.com/u/109069/wut.GIF

and I wonder if there's a way round this?

Can I loop through each node and assign to the wo object by its name?

something like this (pseudo code):

   foreach XmlNode xn in WorkorderNodeTree
        {
            //find out the property name of the current node
            //match to the property in the workorder class
            //set the value equal

            wo.<xn.name> = xn.innertext

        }

Now the only thing I found which gets close is this (from the interweb):

 foreach (XmlNode xl in myXML)
    {

        object o = Assembly.GetExecutingAssembly().CreateInstance("Workorder", true);
        Type t = xl.Name.GetType();
        PropertyInfo pi = t.GetProperty(xl.Name);
        pi.SetValue(o, xl.InnerText, null);


    }

but it returns a null reference exception on o. I am a little confused, any tips?

I presume to do this, I need to use reflection or generics, but I've never hit upon these things before - can anyone advise anything which might point me in the right direction or at least try to explain reflection?

Many thanks all, apologies for the hideously long post!

EDIT:

Thanks, Very deep and sincere thanks go to Fredrik and Rytmis - both of you are white knights in my drab office environment. Rytmis' code edits have solved the issue but I have learned much in this hour or so - Thanks guys, really appreciate it.

A: 

Try the following code to create the Workorder instance instead:

Workorder o = Activator.CreateInstance<Workorder>();
Fredrik Mörk
I don't see how that would help, it would simply use reflection to call the constructor, but wouldn't in any way alleviate the pain of manually setting each property.
Rytmis
Rytmis: I believe it would solve the problem with the null reference. Given that the XML nodes have the same names as the properties in the class (as I understood it) the loop should do the trick. Not the prettiest solution perhaps, but that is a rare luxury when you get the legacy code and limited time on your hands ;o)
Fredrik Mörk
Ah, I see, my bad. :)
Rytmis
+1  A: 

Try AutoMapper or Custom Mapping in BLToolkit.

Anton Gogolev
A: 

As Fredrik said,

Workorder o = Activator.CreateInstance<Workorder>();

will fix the null reference. The rest of code for copying the properties is right.

Davide Vosti
Just added this, still getting a null reference onpi.SetValue(o, xl.InnerText, null);For some reason the PropertyInfo pi = t.GetProperty(xl.Name); doesnt fill pi with anything, even though xl.name resolves the name properly. Any suggestions? it feels like we're really close to cracking this.
Gareth
@Gareth: are the things you're setting actually properties, or is it possible that they are in fact public instance fields? If that's the case, then you'd need GetField instead.
Rytmis
Hi Rytmis,Just tried GetField as well and still getting a null reference.The class properties are just standard gets and sets:public string worktype { get { return this.worktypeField; } set { this.worktypeField = value; } }
Gareth
A: 

I think your code may need a bit of adjustment.

foreach (XmlNode xl in myXML)
{
    object o = Assembly.GetExecutingAssembly().CreateInstance("Workorder", true);
    Type t = xl.Name.GetType();
    PropertyInfo pi = t.GetProperty(xl.Name);
    pi.SetValue(o, xl.InnerText, null);
}

This creates a new instance of WorkOrder for every property you're setting, and also tries to reflect the PropertyInfo from Name.GetType() which is actually typeof(String), and not typeof(WorkOrder) like you'd want it to be. Instead:

WorkOrder w = new WorkOrder();
Type t = typeof(WorkOrder);
foreach (XmlNode xl in myXML)
{
    PropertyInfo pi = t.GetProperty(xl.Name);
    pi.SetValue(w, xl.InnerText, null);
}

[edit] You may also want to specify some binding flags:

    PropertyInfo pi = t.GetProperty(xl.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);

That may or may not be required. I can never remember what the defaults are. :)

Rytmis
Rytmis, I dont know how to thank you enough - this works perfectly, thank you for your time.
Gareth
You may also want to check XmlSerializer, like Mehmet Aras suggested. More declarative goodness, less reflection voodoo.
Rytmis
agreed, XML serialisation is going to be the "final fix" but I need to look into the input XML and see if there's a way of getting it to work with different documents and strange elements. Research is underway but I dont trust myself enough to code it properly yet :)
Gareth
+1  A: 

How about letting the xml serialization (System.Xml.Serialization.XmlSerializer) do the work for you? Depending on the xml, you may simply use Deserialize method that returns a WorkOrder object initialized from the xml data. If the xml you get does not directly map to WorkOrder, you can try to use various Xml attributes on WorkOrder to class to have more control over the way WorkOrder serializes. You can also take a look at DataContractSerializer which is faster and more flexible but you don't have as much control over the serialization as you do with XmlSerialization.

You could also consider adding a static method to WorkOrder class, FromXml, that takes xml and return WorkOrder object. Internally, you can use deserialization or you could even simply initialize properrties in a switch without messing with reflection.

Mehmet Aras
This would be my recommendation too, actually, I just can't remember enough details to actually write it up. :)
Rytmis
+1  A: 

Call me Mr Silly, but why don't you change the WorkOrder constructor to take an XmlNode parameter, shovel all the ugly assignments into it, and just invoke it like this:

WorkOrder wo = new WorkOrder(xmlnode);
Peter Wone
A: 

You shouldn't use reflection, use existing .Net serialization or leave (ugly but working) static code.

What about other types than string? What if the xml format doesn't match?

François