views:

136

answers:

2

In my application, I have three collection objects which store data. The data which populates these collections is downloaded from an XML file on the web.

The three data classes are very simple, the following is a typical example:

[SerializableAttribute()]
[XmlTypeAttribute(AnonymousType = true)]
[XmlRootAttribute(Namespace = "", IsNullable = false, ElementName = "companies")]
public partial class CompanyList
{      
    private List<Company> itemsField = new List<Company>();

    [XmlElement("company", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public List<Company> Items
    {
        get { return this.itemsField; }
    }
}

[SerializableAttribute()]
[XmlTypeAttribute(AnonymousType = true)]
public partial class Company
{
    private int companyIdField;
    private string companyNameField;

    [XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, ElementName = "companyid")]
    public int CompanyID
    {
        get { return this.companyIdField; }
        set { this.companyIdField = value; }
    }

    [XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, ElementName = "companyname")]
    public string CompanyName
    {
        get { return this.companyNameField; }
        set { this.companyNameField = value; }
    }
}

In order to download the data for these objects from the web, I have written an asynchronous web client which will take a URI, downloads the data, then fires an event handler with the downloaded data passed as a string within DownloadCompleteEventArgs. When calling the constructor for this web client, I pass the one of the empty objects for the data to be de-serialized into as a object parameter - this is passed between the async methods via a custom class.

This is where I'm running into difficultly. In the event handler, I want to take the string and de-serialize it into the appropriate object. However, although the de-serialization works fine, the original object isn't modified - presumably because I am working on a copy of the object.

I've tried passing refs to the original constructor and between the custom classes, but the compiler won't let me cast my type to "ref object", and I'd like to keep the web download/de-serialization code type-agnostic. In addition, I'm getting a 'feeling' that I'm going way off track, and it's actually my choice of design pattern that's at fault.

So, in summary, what is the best approach for creating a 'helper' class which can populate any one of a variety of objects of different types from a suitable asynchronously downloaded string of xml data?

EDIT: Adding some further context: I need to deserialize an XML string into an object from an asynchronous callback. For example, I might make three calls to DownloadXMLAsync(), which is a method which calls DownloadCompleted(DownloadCompletedEventArgs) when it is done. Each of the three calls return data to populate three different objects. How can I reliably pass a reference to the object through the asynchronous calls so that the DownloadCompleted() method can correctly populate the right object in each case?

I tried defining DownloadXMLAsync(ref object objectToPopulate), then passing the objectToPopulate in the state object within the call to HttpWebRequest.BeginGetResponse(), but I get "cannot convert from 'ref TicketSpaceSiteServer.CompanyList' to 'ref object'".

+2  A: 

I use the following code to construct a new object from an XML string:

public class Util
{
    static private T Load<T>(string xml)
    {
        T t;

        XmlSerializer serializer = new XmlSerializer(typeof(T));
        try
        {
            System.Text.ASCIIEncoding  encoding=new System.Text.ASCIIEncoding();
            Byte[] bytes = encoding.GetBytes(xml);
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                t = (T)serializer.Deserialize(ms);
            }
        }
        catch (Exception ex) 
        {
            throw ex; // This part is for debugging
        }

        return t;
    }
}

Use it like:

MyType my = Util.Load<MyType>(myXmlString);
Eric J.
The deserialization part of the app is working - my issue is passing "my" through the async code so that once the async download is complete, the object can be populated.
Ian Gregory
Why do you want to populate an existing instance? All information contained in that instance would be entirely replaced after deserialization. If you already hold a reference to an existing object, can you update that reference to the new object instead of updating the existing object in place? I'm not aware of a generic pattern to update an already-instantiated object with the serialized information.
Eric J.
I'm populating an existing instance to update it in order to bring it in sync with the online data.
Ian Gregory
I have not seen a situation yet where you can't have your original code that uses this object just reference the new one. I'm not sure what makes your situation different. Maybe post Pseudocode that shows the big picture? Class using this object, object being updated, how the object us used after the update...
Eric J.
A: 

In the end I went for the simple option. The DownloadCompleted async callback is called whenever an XML file has been downloaded. I don't pass a reference to an object to populate, but instead parse the XML to work out which object's data is contained within it and then use this knowledge to correctly return an object of the correct type i.e.

        if (xml.StartsWith("<?xml version=\"1.0\"?>\n<companies>"))
        {
            return LoadFromXmlString<CompanyList>(xml);
        }
        else if (xml.StartsWith("<?xml version=\"1.0\"?>\n<shows>"))
        {
            return LoadFromXmlString<ShowList>(xml);
        }
        else if (xml.StartsWith("<?xml version=\"1.0\"?>\n<performances>"))
        {
            return LoadFromXmlString<PerformanceList>(xml);
        }

Then, in the parent class:

    void wd_DownloadComplete(object sender, DownloadCompletedEventArgs e)
    {
        object foo = SerializationHelper.LoadFromXmlString(e.DownloadedString);
        switch (foo.GetType().ToString())
        {
            case "TicketSpaceSiteServer.CompanyList":
                companies = foo as CompanyList;
                break;
            case "TicketSpaceSiteServer.PerformanceList":
                performances = foo as PerformanceList;
                break;
            case "TicketSpaceSiteServer.ShowList":
                shows = foo as ShowList;
                break;
        }
    }
Ian Gregory