Ramblings of General Geekery

From Archos to Amazon

As you probably already know, Amazon’s tablet, the Kindle Fire, was announced a few days ago. Priced at a pretty amazing $199, its purpose is more focused than your general usage tablet like the iPad or the Xoom: it’s specifically designed for consuming content like books, music and video (preferably through Amazon’s own services, of course). Although it will probably be possible to install some other apps (through Amazon’s AppStore) to do some email and chatting and gaming and what have you, it will probably be more limited than on those bigger tablets, and will likely be only advertised as the last bullet point on the list of specs, if at all.

Now you know what else was priced around $200 and was useful mostly for consuming content like books, music and video? Yep. The Archos 70 I got last year for $249 (I got it on a discount, it was normally at $279).

Oh sure, Archos may not have been advertising their product like that. They may have advertised it along the lines of:

The most awesome Android tablet ever! Do everything you want! Web browsing! Videos and music! Email!! Games!!! Video chat!!!! This is so awesome we’re running out of exclamation marks!!!!!

Being a long time Archos user, I’m used to their bullshit and I know how to read through it. It usually translates to:

It’s rubbish at everything, especially anything web-related, but it can read any fucking video format in the universe, doesn’t come with any bullshit iTunes-like sync program, and is so much cheaper than the competition.

And as I stated last time, that was just perfect: I only wanted a good portable media player, and maybe a nicer way to read my Instapaper clips. But, of course, everytime somebody saw my small cheap looking tablet, there was a good chance the discussion would go like this:

  • “What’s that?”
  • “My Archos 70. Android tablet. I use it mainly as a portable media player, though.”
  • “Why didn’t you get an iPad?”
  • “Too big. Not enough codec support. Too expensive.”
  • “Yeah but you can do so much more with it!”
  • “Don’t care.”
  • “Come on! Apple is so awesome! The iPad is so shiny!”
  • “Yeah, whatever.”

Well it looks like Amazon found a lot of people like me out there because the Kindle Fire’s product story is exactly what I was looking for a year ago. Well… apart for that one small detail about data freedom, because I’m not sure exactly how easy it will be to get your own content onto the device, without necessarily going through Amazon (especially since most of their content is not even available outside of the U.S. anyway).

At least I’m very happy to see someone try to move the market into a slightly new direction instead of spitting out confusing all-purpose Android tablets like everyone else.


MongoDB for Lucene and ASP.NET

My latest home project (yet to be announced) is using MongoDB for storage, which led me to write a couple of libraries you may be interested in if you use that kind of database. Keep reading for the details.

MongoDB directory for Lucene

MongoDB.Lucene is what you would expected given the name: a MongoDB backend for Lucene indexes.

It’s a simple port of the standard FSDirectory using Mongo’s GridFS storage. I’m sure there’s a lot of room for improvement (performance, storage efficiency, lock reliability), but it’s working well enough for small applications.

It’s available as a NuGet package.

MongoDB providers for ASP.NET

MongoDB.Web is a set of providers for ASP.NET:

  • Membership provider
  • Role provider
  • Profile provider (not fully-featured yet)

It also contains a TraceListener that stores log events in a MongoDB collection.

This one’s got a funny story behind it. I wrote it quickly to get going with the rest of my project, and came back to it later thinking I could clean it up and publish it on the NuGet Gallery… but I got a package ID already exists error because of this similar package also called MongoDB.Web. I guess I should have checked first…

Why did I keep my own version then? Well:

  • My providers use a bit less storage in the database.
  • Provider initialization (like getting the right connection string) has more options (useful when you want to run on AppHarbor for instance, where they have pretty limited configuration customization).

The other version is however fully-featured, and probably better tested.


The extension method trick

If you’ve been writing C# code lately you should be pretty familiar with extension methods: it’s a nice compiler feature added to C# 3.0 which lets you “attach” methods to an existing type.

So for instance, if you define the following extension method:

    static class ConsoleExtensions
    {
        public static void WriteReversed(this TextWriter writer, string message)
        {
            writer.WriteLine(new string(message.Reverse().ToArray()));
        }
    }

You can write:

    Console.Out.WriteReversed("hello world!");

You effectively “extended” the TextWriter type with a new WriteReversed method in which you get a pseudo-this reference. Before extension methods, these types of methods would have been in a ConsoleHelper class of some kind – a lot less discoverable for the clients of your library.

Now, the (not so-)peculiar thing about it is that the pseudo-this variable can be null. Indeed, since this whole feature is only syntactic sugar, as they say, it’s just a more elegant way to write the following, which is a plain old function call:

    ConsoleExtensions.WriteReversed(Console.Out, "hello world!");

But it means it would look like you’re calling a method on a null object without getting slapped in the face with the infamous NullReferenceException.

You just need to add some simple parameter validation code to your extension method:

    static class ConsoleExtensions
    {
        public static void WriteReversed(this TextWriter writer, string message)
        {
            if (writer != null)
                writer.WriteLine(new string(message.Reverse().ToArray()));
        }
    }
    TextWriter writer = null;             // woah, are you crazy!?
    writer.WriteReversed("hello world!"); // woah, this is crazy!!

This trick can be pretty useful if you have an API that aims to be as minimalist as possible for the user and null is a valid case for one of the objects exposed publicly. You can already handle this with various well known techniques like wrapping or the NullObject pattern, but the extension method is a nice new alternative that’s enough in some cases and requires a minimum of code.

I’ve seen that trick used only once so far, in the excellent MvcMiniProfiler:

    using (MiniProfiler.Current.Step("Getting the answer to the ultimate question"))
    {
        var result = DeepThought.Compute();
        ViewBag.UltimateAnswer = result;
    }

Here, it’s totally OK if you disabled profiling in your web application, which means MiniProfiler.Current would return null. That’s because the Step() method is an extension method that checks for a null value provided as “this”, and doesn’t do much in that case.

Sure, they could have abstracted the profiler behind an IProfiler interface and have the MiniProfiler.Current return a NullProfiler when profiling is disabled, or something like that, but that would have been a lot more code than just using an extension method.


Formatting mongo output

If you’re working with MongoDB’s mongo command line tool and you often see yourself reading through this:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")})

{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcded"), "source" :ObjectId("4e39da93
058cfe28a039b546"), "sourceId" : "754731", "sourceUserName" : "loudej", "ful
lName" : "Louis DeJardin", "description" : "A software guy on the ASP.NET te
am and author of Spark", "avatarUrl" : "http://a2.twimg.com/profile_images/1
161371928/DSC01367_normal.jpg", "websiteUrl" : "http://louis.dejardin.org",
"location" : "Kenmore, WA", "friendCount" : 97, "followerCount" : 1428 }
{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf3"), "source" : ObjectId("4e39da9
3058cfe28a039b546"), "sourceId" : "16925866", "sourceUserName" : "sebastienr
os", "fullName" : "Sébastien Ros", "description" : "", "avatarUrl" : "http:/
/a1.twimg.com/profile_images/1074060902/f71ee9e786d8340a5eb08dd6154c2095_1__
normal.jpeg", "websiteUrl" : "http://about.me/sebastienros", "location" : "S
eattle", "friendCount" : 18, "followerCount" : 142 }
{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf4"), "source" : ObjectId("4e39da9
3058cfe28a039b546"), "sourceId" : "768197", "sourceUserName" : "haacked", "f
ullName" : "Phil Haack", "description" : "This bio is not true.", "avatarUrl
" : "http://a2.twimg.com/profile_images/1315393964/image_normal.jpg", "websi
teUrl" : "http://haacked.com/", "location" : "Bellevue", "friendCount" : 225
, "followerCount" : 15947 }

…then you may want to add toArray() at the end of your line:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")}).toArray()

[
    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcded"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "754731",
        "sourceUserName" : "loudej",
        "fullName" : "Louis DeJardin",
        "description" : "A software guy on the ASP.NET team and author of Spark",
        "avatarUrl" : "http://a2.twimg.com/profile_images/1161371928/DSC01367_normal.jpg",
        "websiteUrl" : "http://louis.dejardin.org",
        "location" : "Kenmore, WA",
        "friendCount" : 97,
        "followerCount" : 1428
    },

    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf3"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "16925866",
        "sourceUserName" : "sebastienros",
        "fullName" : "Sébastien Ros",
        "description" : "",
        "avatarUrl" : "http://a1.twimg.com/profile_images/1074060902/f71ee9e786d8340a5eb08dd6154c2095_1__normal.jpeg",
        "websiteUrl" : "http://about.me/sebastienros",
        "location" : "Seattle",
        "friendCount" : 18,
        "followerCount" : 142
    },
    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf4"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "768197",
        "sourceUserName" : "haacked",
        "fullName" : "Phil Haack",
        "description" : "This bio is not true.",
        "avatarUrl" : "http://a2.twimg.com/profile_images/1315393964/image_normal.jpg",
        "websiteUrl" : "http://haacked.com/",
        "location" : "Bellevue",
        "friendCount" : 225,
        "followerCount" : 15947
    }
]

Much more readable, isn’t it? The only problem is that mongo will, by default, return only the first 20 hits of a query (it will write “has more” at the bottom to indicate that, well, there’s more). The toArray() method, however, will get everything and print it out. This could mean printing a lot of stuff, so you may want to stick a limit(20) in between if you’re not sure:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")}).limit(20).toArray()