C# driver issue when serializing BsonObjectId to JSON in MVC

8,498 views
Skip to first unread message

Jorin Slaybaugh

unread,
Dec 8, 2010, 12:37:06 PM12/8/10
to mongod...@googlegroups.com
I'm noticing an issue with version 0.9.0.3992 of the C# driver.  I have a sample collection that looks like this:

> db.Lists.find()
{ "_id" : ObjectId("4cfec14ed457000000005914"), "Name" : "Christmas List", "Users" : [ "jorin", "cassie" ] }
{ "_id" : ObjectId("4cfec15bd457000000005915"), "Name" : "Shopping List", "Users" : [ "jorin" ] }
>

In one of my MVC actions, I run the following to return this as JSON:

var myLists = _Lists.Find(Query.EQ("Users", "john"));
return Json(myLists, JsonRequestBehavior.AllowGet);

and I get the following error (full stack trace below): 

Unable to cast object of type 'MongoDB.Bson.BsonObjectId' to type 'MongoDB.Bson.BsonBoolean'.

However, if i do the following, it works fine:

var myLists = _Lists.Find(Query.EQ("Users", "jorin")).SetFields(Fields.Exclude("_id"));
return Json(myLists, JsonRequestBehavior.AllowGet);

result: [{"_id":null,"Name":"Christmas List","Users":["jorin","cassie"]},{"_id":null,"Name":"Shopping List","Users":["jorin"]}]

However, I need the value of the _id field, so that won't exactly work for me.  I'm going to try looping through and parsing out just the string portion for the time being, but it definitely appears to be something specific to the BsonObjectId or perhaps in the Microsoft Javascript Serializer implementation?

Thanks for any help,

Jorin

-------------------

Full stack trace:

[InvalidCastException: Unable to cast object of type 'MongoDB.Bson.BsonObjectId' to type 'MongoDB.Bson.BsonBoolean'.]
   MongoDB.Bson.BsonValue.get_AsBoolean() +65

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +0
   System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +72
   System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) +335
   System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +28
   System.Web.SecurityUtils.MethodInfoInvoke(MethodInfo method, Object target, Object[] args) +147
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeCustomObject(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +482
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValueInternal(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +1355
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValue(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +194
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeCustomObject(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +502
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValueInternal(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +1355
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValue(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +194
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeEnumerable(IEnumerable enumerable, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +126
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValueInternal(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +1311
   System.Web.Script.Serialization.JavaScriptSerializer.SerializeValue(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat) +194
   System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Object obj, StringBuilder output, SerializationFormat serializationFormat) +26
   System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Object obj, SerializationFormat serializationFormat) +74
   System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Object obj) +6
   System.Web.Mvc.JsonResult.ExecuteResult(ControllerContext context) +216
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +13
   System.Web.Mvc.<>c__DisplayClass1c.<InvokeActionResultWithFilters>b__19() +23
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +260
   System.Web.Mvc.<>c__DisplayClass1e.<InvokeActionResultWithFilters>b__1b() +19
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +177
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
   System.Web.Mvc.Controller.ExecuteCore() +116
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8841105
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

Robert Stam

unread,
Dec 8, 2010, 12:48:09 PM12/8/10
to mongodb-user
I'm perplexed by your stack trace. It's all MVC and then there seems
to be a whole section missing between
System.RuntimeMethodHandle._InvokeMethodFast and
MongoDB.Bson.BsonValue.get_AsBoolean. So I can't even tell what method
of the driver was first called.

Other questions:

1. What is the default document type of the _Lists collection?
2. What does the Json function do and what are its parameter types?
3. Does it make sense to be passing a cursor to the Json function? Or
should it be a list?

If you can provide additional information that will help me answer
your questions.

Jorin Slaybaugh

unread,
Dec 8, 2010, 3:19:01 PM12/8/10
to mongod...@googlegroups.com
Sorry the stack trace isn't much help. The Json function just serializes the object passed to it into Json using the Microsoft Javascript Serializer.  Here is the source of the Json object's ExecuteResult function from the MVC source.  As you can see, all it really does is call the Microsoft Serializer and serialize the data object that is passed to it.

        public override void ExecuteResult(ControllerContext context) {
            if (context == null) {
                throw new ArgumentNullException("context");
            }
            if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
                String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) {
                throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);
            }

            HttpResponseBase response = context.HttpContext.Response;

            if (!String.IsNullOrEmpty(ContentType)) {
                response.ContentType = ContentType;
            }
            else {
                response.ContentType = "application/json";
            }
            if (ContentEncoding != null) {
                response.ContentEncoding = ContentEncoding;
            }
            if (Data != null) {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                response.Write(serializer.Serialize(Data));
            }
        }


I'm not sure I understand the question about the default document type.  And lastly, I'm not sure it makes the most sense to send the cursor, but the cursor works if I tell it to exclude the _id field, so it certainly seems like it should work.  Also, if i call .ToList() on my MongoCursor object, it turns it into an IEnumerable<ApiListDetails> and that still doesnt serialize properly because of the BsonObjectId field, so it certainly appears to be somehow related to that specific field.

Thanks,

Robert Stam

unread,
Dec 8, 2010, 3:37:58 PM12/8/10
to mongodb-user
In the C# Driver a collection has a default document type (so the type
of the collection is MongoCollection<TDefaultDocument>). Based on your
comment that ToList on the cursor returns a
IEnumerable<ApiListDetails> it looks like your default document type
is ApiListDetails. So you probably got your collection object using a
line of code like:

var collection = database.GetCollection<ApiListDetails>("name");

The ExecuteResult code you showed me is not from the C# driver code
and doesn't appear to call the C# driver (although I can't tell what
the type of the Data variable is).

Can you configure Visual Studio to stop when an exception is thrown
and see if you can get a more complete picture of the stack trace
leading up to BsonValue.get_AsBoolean?

Perhaps if you show me the declaration of ApiListDetails that will
help. I'm beginning to think that this failure has nothing to do with
the C# driver but is just a side effect of trying to serialize a
BsonValue through the .NET JSON serializer. If the .NET JSON
serializer is trying to serialize all the public properties of
BsonObjectId that's not going to work.

One thing you could try is to change the declaration of your Id field
from BsonObjectId to just ObjectId.

Jorin Slaybaugh

unread,
Dec 8, 2010, 4:14:51 PM12/8/10
to mongod...@googlegroups.com
Ah, that makes sense.  Sorry about the confusion.  Yes, in my ApiListDetails object, the _id field is of type BsonObjectId.  I had no idea that the ObjectId type even existed.  I swapped that in and it didn't fail, generating the following result:

[{"_id":{"Timestamp":1291764046,"Machine":13915904,"Pid":0,"Increment":22804,"CreationTime":"\/Date(1291764046000)\/"},"Name":"Christmas List","Users":["jorin","cassie"]},{"_id":{"Timestamp":1291764059,"Machine":13915904,"Pid":0,"Increment":22805,"CreationTime":"\/Date(1291764059000)\/"},"Name":"Shopping List","Users":["jorin"]}]

So, obviously, the ObjectId type separates the BsonObjectId value into its component parts. Cool. However, I'd prefer just the string...is there a type to easily grab that? :-)

I also moved some things around to catch the exception in the debugger, but unfortunately there is nothing more in the trace than what I've posted above. I have no idea why it's trying to get the BsonObjectId as a boolean. Perhaps its because its parsing it as a function?

Just in case it helps, here's the declaration of my ApiListDetails type. But if I change the default document type to just be BsonDocument all the way through, it still fails with exactly the same error.

public class ApiListDetails { public BsonObjectId _id { get; set; } public string Name { get; set; } public string[] Users { get; set; } }

Robert Stam

unread,
Dec 8, 2010, 4:22:26 PM12/8/10
to mongodb-user
I think the .NET JSON serializer is serializing all public properties
(and BsonObjectId inherits a property called AsBoolean from its parent
class BsonValue).

If you want more control over your JSON serialization you may need to
have two similar classes, one for interacting with MongoDB and one for
returning values through the .NET JSON serializer. Then you could set
the data type of the _id field to string in one of them. The bad part
is that you'd have to write mapping code to go back and forth. An
ObjectId can be converted to a string by calling ToString().

Jorin Slaybaugh

unread,
Dec 8, 2010, 4:55:15 PM12/8/10
to mongod...@googlegroups.com
Yeah, that's kind of what I was thinking.  I might just make another property that calls the ToString() on it.  Not ideal, but that explanation makes sense.  I'm sure thats what the .NET serializer is doing.  I might look into the JSON.NET serializer also. It appears to have some options where you can specify what properties to serialize and perhaps i can just get it to only serialize the .Value property.

Thanks for your help, Robert.

Robert Stam

unread,
Dec 8, 2010, 5:10:23 PM12/8/10
to mongodb-user
There's also a JSON writer built into the C# driver. You can tell it
you want the ObjectId serialized as a string:

public class ApiListDetails {
[BsonRepresentation(BsonType.String)]
public ObjectId { get; set; }
public string Name { get; set; }
public string[] Users { get; set; }
}

var apiListDetails;
var json = apiListDetails.ToJson();

But there is not yet a JSON reader.

Jorin Slaybaugh

unread,
Dec 8, 2010, 5:25:56 PM12/8/10
to mongod...@googlegroups.com
That would be perfect!  However, I just tried it and it comes up with "No serializer found for type: MongoDB.Driver.IMongoFields" Any idea what that means?

Robert Stam

unread,
Dec 8, 2010, 5:27:28 PM12/8/10
to mongodb-user
Probably a bug. I'll check it out.

Robert Stam

unread,
Dec 8, 2010, 5:31:55 PM12/8/10
to mongodb-user
I tried it and I get this output:

"{ \"_id\" : \"4d0006d1e447ad20cc4b739b\", \"Name\" : \"Name\", \"Users
\" : [\"User1\", \"User2\"] }"

Here's my test program:

http://www.pastie.org/1360405

Perhaps you are calling ToJson on the cursor and not the data?

Jorin Slaybaugh

unread,
Dec 8, 2010, 5:41:30 PM12/8/10
to mongod...@googlegroups.com
Yeah, I was trying to serialize the cursor.  Is there not a way to serialize a collection of objects?  Or mayb some method to convert the cursor to something that can be serialized using ToJson()?

Robert Stam

unread,
Dec 8, 2010, 5:46:42 PM12/8/10
to mongodb-user
Serialize them one at a time in a loop:

foreach (var document in cursor) {
var json = document.ToJson();
// do something with json
}

Or if you know there's only one:

var json = cursor.Single().ToJson();

Robert Stam

unread,
Dec 8, 2010, 5:49:04 PM12/8/10
to mongodb-user
Also, if you know there's only one you don't need a cursor:

var json = collection.FindOne(query).ToJson();
Reply all
Reply to author
Forward
0 new messages