views:

86

answers:

5

Here is the entire code for my class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml.Linq;

namespace SharpDIC.Entities
{
    /// <summary>
    /// Represents a forum on the Dream.In.Code website.
    /// </summary>
    public class Forum
    {
        //Forum information is given by this XML call:
        //http://www.dreamincode.net/forums/xml.php?showforum=NUMBER_GOES_HERE

        public string Name { get; set; }
        public string ID { get; set; }
        public string URL { get; set; }
        public List<Subforum> Subforums { get; set; }

        /// <summary>
        /// Load a forum by providing an ID.
        /// </summary>
        /// <param name="id">A forum's individual ID number.</param>
        public Forum(string ID)
        {
            WebClient webClient = new WebClient();
            string htmlSource = webClient.DownloadString(new Uri(String.Format("http://www.dreamincode.net/forums/xml.php?showforum={0}", ID)));
            XDocument xml = XDocument.Parse(htmlSource);

            var forumXML = xml.Element("ipb").Element("forum");

            //Load general profile information.
            this.Name = forumXML.Element("name").Value;
            this.ID = forumXML.Element("id").Value;
            this.URL= forumXML.Element("url").Value;

            //Load subforums.
            var subforumsXML = xml.Element("ipb").Element("forum").Element("subforums");
            this.Subforums = (from forum in subforumsXML.Descendants("forum")
                              select new Subforum()
                              {
                                  ID = forum.Element("id").Value,
                                  Name = forum.Element("name").Value,
                                  URL = forum.Element("url").Value,
                                  Description = forum.Element("description").Value,
                                  Type = forum.Element("type").Value,
                                  TopicCount = forum.Element("topics").Value,
                                  ReplyCount = forum.Element("replies").Value,
                                  LastPost = new LastPost()
                                  {
                                      Date = forum.Element("lastpost").Element("date").Value,
                                      Name = forum.Element("lastpost").Element("name").Value,
                                      ID = forum.Element("lastpost").Element("id").Value,
                                      URL = forum.Element("lastpost").Element("url").Value,
                                      UserWhoPosted = new Friend()
                                      {
                                          ID = forum.Element("lastpost").Element("user").Element("id").Value,
                                          Name = forum.Element("lastpost").Element("user").Element("name").Value,
                                          Url = forum.Element("lastpost").Element("user").Element("url").Value,
                                          Photo = forum.Element("lastpost").Element("user").Element("photo").Value,
                                      }
                                  }
                              }).ToList();
        }
    }
}

Basically what it does is parse the information from the returned xml of this address:

http://www.dreamincode.net/forums/xml.php?showforum=1

Here's how I'm running this code:

SharpDIC.Entities.Forum forum = new SharpDIC.Entities.Forum("1");
Console.WriteLine(forum.Name);
Console.WriteLine(forum.URL);
Console.WriteLine(forum.ID);
Console.WriteLine(forum.Subforums[0].LastPost.UserWhoPosted.Name);

Console.ReadLine();

I'm getting an exception on the entire load subforums block; any suggestions?

NullReferenceException was not handled. Object reference not set to an instance of an object.
+1  A: 

It's almost certainly that one of the elements you're expecting is missing.

If you run it in the debugger you may find that when it throws the exception, it indicates which actual operation it's on, which would make it much easier.

Alternatively, break out the parsing of individual components into separate methods:

this.Subforums = subforumsXML.Descendants("forum")
                             .Select(ParseForum)
                             .ToList();

...

static SubForum ParseForum(XElement forum)
{
    return new Subforum
    {
        ID = forum.Element("id").Value,
        ...
        LastPost = ParseLastPost(forum.Element("lastpost"))
    };
}

static LastPost ParseLastPost(XElement lastPost)
{
    ...
}

I've found this can keep the query expressions more manageable.

One way of avoiding NREs when there's a missing element that you're just trying to get the value of is to use a conversion instead of the Value property, e.g.

ID = (string) forum.Element("id")

That will set ID to null if the id element is missing. Obviously if it's actually a required property, throwing the exception is better, but for optional properties it's really useful.

Jon Skeet
By running it in the debugger do you mean setting a breakpoint and then running the application? I've done that but I don't get a specific "hey this element here is missing." It seems if a single element is missing the entire block is caught. I can't pinpoint where the element is tripping things up.
Serg
@Sergio: No breakpoint needed, just run until it blows up. I'm *slightly* surprised that it doesn't indicate where it's going wrong... but that's what you get for a single big select, unfortunately. See my edited answer for a way of breaking things up.
Jon Skeet
From the stacktrace You should be able to do that. You may laso craete a factory for that Subforum, LastPost, UserWhoPosted classes.
Vash
Jon that suggestion of using casting instead of value, and using methods to divide the work is just GREAT! My code looks much more succinct. Thanks again for the nuggets of knowledge.
Serg
Casting makes the code no longer break and returns null when it doesn't find anything. This entire project is a library that'll help other devs create DreamInCode based applications, do you think returning null is a good thing and expected?
Serg
@Sergio: That's a very open-ended question. It entirely depends on the scenario. If you want to return "" instead, you can do: `(string) x.Element("name") ?? ""`
Jon Skeet
A: 

you should probably check to make sure that subforumsXML != null before using your linq on it.

you could break this operation into a couple of lines....

    var subforumsXML = xml.Element("ipb").Element("forum").Element("subforums");

// goes to

if ( forumXML != null )    // breakpoint here
{
   // ml.Element("ipb").Element("forum") == forumXML
   var subforumsXML = forumXML.Element("subforums");
   if ( subforumsXML != null )
   {
   // do stuff here
   }
}
Muad'Dib
A: 

You never check whether an element exists. For example for the UserWhoPosted every item must have a <lastpost> element, which must have a <user> element, which must have <id>, <name>, <url> and <photo> elements. If any of those are missing, that Element("whatever") will return null so a .Value will fail.

Hans Kesting
A: 

In your link first users have no photo, this might be it

GôTô
A: 

To find the problem, try this extension and then call GetElement rather than Element...

public static System.Xml.Linq.XElement GetElement(this System.Xml.Linq.XContainer doc, string name)
    {
        var element = doc.Element(name);
        if (element == null)
            throw new ApplicationException("Missing element: " + name);
        return element;
    }
Pedro