System.UriBuilder: It's the Little Things
I was debugging some wierd behavior in my trackback ping routines earlier. Specifically, I was checking for an existing trackback entry on a target page for a given post. Issue a GET to the trackback url with __mode=rss appended to the querystring, seemed simple enough the first time through. But it was hit or miss to the urls in my tests.
The problem became obvious real fast (and I would cite this as an example of simple RESTful interfaces being pretty darn easy to debug, I didn't need to look what was over the wire, my elaborate external testbed was the browser).
Ah, the little things. It turns out System.UriBuilder does something I personally think is a little odd when you go to append to its Query or Fragment propertys. I didn't see anyone out there documenting the issue previously, which leads me to naturally think I may just be insane.
What System.Uri.Query's setter does is prepend a ? to the value passed. This is fine and dandy for working with a url without an existing querystring. But when you want to simply add another argument, I started plunking around and ended up with like 5 - 10 lines of code just to deal with the fact that set_Query was automatically doing this for me.
In other words given http://localhost/whatever/whichever.html?first=1, straightforwardly appending a second=2 to it yields http://localhost/whatever/whichever.html??first=1&second=2. Bad url, so any trackback ping with it's own querystring was returning an error. Great.
Decompile of the accessors for System.Uri and you get:
public void set_Query(string value)
{
if (value == null)
{
value = string.Empty; }
if (value.Length > false)
{
value = string.Concat(63, value); }
this.m_query = value;
this.m_fragment = string.Empty;
this.m_changed = true;} public string get_Query()
{
return this.m_query;
}
I couldn't come up with a reason why you wouldn't do the prepending in the get accessor. Maybe I'm insane, but it would make this simple little thing a whole lot easier. The docs indicate that "The query information returned includes the leading question mark". Which is true. A better way to say it would be "A leading question mark is added everytime you set the query, so don't think about appending without trimming the returned leading question mark first."
Sure, it's a design decision whether to do field handling in the set or in the get. Personally I like to do the handling as late as possible in get.
I think it would me more intuitive and easier to work with if it had been written along the lines of...
public void set_Query(string value)
{
if (value == null) value = string.Empty; this.m_query = value;
this.m_fragment = string.Empty;
this.m_changed = true;
} public string get_Query()
{ return (m_query.StartsWith("?") ? "" : "?") + m_query;
}
Even better, lose the question mark. Lost the frag's pound sign. It's putatively a uri building component, add the formatting when I go to get the full uri, not when I get it's constituent parts--do some more of the building for me hehe. I freely admit this is subjective on my part. Just the same, it doesn't let me instantiate it with a bad url, it seems like it shouldn't be able to give me back a bad url either. Yes, I'm dreaming now, I know it.
Anyway, just a quick postscript--as you can see in the underlying code, setting System.Uri.Query to anything clears your fragment if you have one. This is documented and was reportedly a design decision. I'm not sure I see the design merits, but hey, if it's stated explicitly and I don't have to break out Reflector to figure out what it really does, I'm happy. Fragment's accessors do the same thing as Query's with a leading #.
Which brings me to a final issue. I understand the need to hide access to internal fields. But part of me thinks simple derivations to deal with personal preference things like the above would be a heck of a lot easier with more use of protected. Yes this means I could break something, but it also means I could neatly change the code as above myself and not generate another method signature filled with a bunch of after-the-fact corrections.
What I ended up doing was deriving anyway so I can do it exactly the way that seems intuitive to me.
protected string StripLeadingString(string value, string leadingString)
{
if (value.StartsWith(leadingString))
return value.Substring(leadingString.Length, value.Length - leadingString.Length);
else
return value;
}public virtual void QueryAppend(string value)
{
if (value.Length > 0)
{
value = StripLeadingString(value, "?"); if (base.Query.Length > 0)
{
if (!value.StartsWith("&"))
value = value.Insert(0, "&"); value = StripLeadingString(base.Query, "?") + value + base.Fragment;
}
} base.Query = value;
}
But because I can't effectively change the underlying behavior, only counteract it, it seems a lot less efficient, at least in terms of logic/comprehension. Oh well, at least it gave me a chance to create a method that works just how I want it to in the end. Some days it's the little things.