6

I am trying to retireve all the documents in my MongoDB collection via a REST api built in ASP.NET MVC 4 and I have encoutered an error when i enter localhost:50491/api/document:

An error occurred while deserializing the Id property of class Acord_Rest_API.Models.Document: Cannot deserialize string from BsonType ObjectId.

My controller looks like:

public class DocumentController : ApiController
{
    public readonly MongoConnectionHelper<Document> docs;

    public DocumentController()
    {
        docs = new MongoConnectionHelper<Document>();
    }

    public IList<Document> getAllDocs()
    {
        var alldocs = docs.collection.FindAll();
        return alldocs.ToList();
    }
}

My MongoDB document looks like this? enter image description here

What am I missing here?

My class that connects to the DB:

public class MongoConnectionHelper<T> where T: class
{
    public MongoCollection<T> collection { get; private set; }

    public MongoConnectionHelper()
    {
        string connectionString = "mongodb://127.0.0.1";
        var server = MongoServer.Create(connectionString);
        if (server.State == MongoServerState.Disconnected)
        {
            server.Connect();
        }
        var conn = server.GetDatabase("Acord");
        collection = conn.GetCollection<T>("Mappings");
    }
}

EDIT here is my solution: enter image description here

MongoConnectionHelper does the connection to the DB, DocumentController has the methods to retrive all the documents and Document contains what you have suggested in your answer.

EDIT here is the Document class:

[DataContract]
public class Document
{
    public ObjectId _id { get; set; }

    [DataMember]
    public string MongoId
    {
        get { return _id.ToString(); }
        set { _id = ObjectId.Parse(value); }
    }
    public IList<string> alldocs { get; set; }
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
Mike Barnes
  • 4,217
  • 18
  • 40
  • 64

3 Answers3

8

A more simple approach is to tell MongoDB to treat string field as ObjectId. You can do it easily by using BsonType attribute.

[BsonRepresentation(BsonType.ObjectId)]
public string _id { get; set; }
James Conkling
  • 3,235
  • 2
  • 25
  • 37
Ofir
  • 5,049
  • 5
  • 36
  • 61
6

The Mongo Id is not a "string" and the Mongo.Bson library will not serialize it to a string automatically. If you are using the Mongo.Bson library, you need to set the id property on your class to be a ObjectId type which is available in that library.

The problem here is that the .net serializer doesn't know how to serialize the custom type ObjectId that's the mongo built in Id (not sure why). It is not marked as [Serializable] so you have to get ASP.NET to bypass it or create another class that doesn't have one and convert it to a string.

If you need a string to use in your app, you should disable the serialization of the MongoId to xml (if that's what you're trying to do), then you can add a property like this to your class:

[XmlIgnore]
public ObjectId _id { get; set; }

public string MongoId
{
    get { return _id.ToString(); }
    set { _id = ObjectId.Parse(value); }
}

The alternative it to create a seperate mapped class to manage the return data.

EDIT In that case you should use the "opt-in" approach. This involves decorating your class to look like the following:

[DataContract]
public class Document
{
    public ObjectId _id { get; set; }

    [DataMember]
    public string MongoId
    {
        get { return _id.ToString(); }
        set { _id = ObjectId.Parse(value); }
    }

...

DataMember will flag only those properties for serialization. Are you using POCO classes for your "Document" object? If so the above should work fine.

I would however recommend creating a mapped view of the "Document" object for passing out publically. In these situations youalmost always end up wanting a slightly different "contract" to your actual database entity

Gats
  • 3,452
  • 19
  • 20
  • I have edited the question and added my connection. I would like to return the result of the FindAll to Json. – Mike Barnes Jan 11 '13 at 11:16
  • How would you suggest I create a mapped view of the Document? with an interface? or is that a stupid question? – Mike Barnes Jan 11 '13 at 11:47
  • Not at all, there's loads of ways to do it, and hard to advise without seeing the structure of your solution in VS. A contracts folder with classes you map to is always a good one. A contract is it's own class and you can map it with linq ...Select(x => new DocumentContract() { MongoId = ObjectId.ToString(), A = x.A, B = x.B ... etc or a tool like Automapper if you have a lot of different classes. – Gats Jan 11 '13 at 11:52
  • Thanks man, I have edited the question and added a screem shot of the solution – Mike Barnes Jan 11 '13 at 12:04
  • I now seem to get an Element 'ClaimSynchronizationProcess' does not match any field or property of class Acord_Rest_API.Models.Document error? do you have any suggestions? – Mike Barnes Jan 11 '13 at 12:06
  • Can you show me where you're mapping your entity from the one in the Mongo collection? – Gats Jan 11 '13 at 12:23
  • How are you populating your document contract? Are you using EF? – Gats Jan 11 '13 at 23:03
  • Im not to sure to be completely honest with you? I am very new to Restful Web API and ASP.NET? how do I check that? you have been very helpful so far!!! – Mike Barnes Jan 13 '13 at 17:34
  • 1
    The problem's not in the restful part by the looks of it, but in the dataaccess part. – Gats Jan 14 '13 at 02:21
  • Lets say I wanted to return just a list of all the documents from this would I have to all something like `public IList allDocs {get; set;};`? because I think my document class is all wrong> – Mike Barnes Jan 14 '13 at 07:55
1

i think that you are missing the [BsonId] convention so your class should look like this:

[DataContract]
public class Document
{
    [BsonId]
    public ObjectId _id { get; set; }

    [DataMember]
    public string MongoId
    {
        get { return _id.ToString(); }
        set { _id = ObjectId.Parse(value); }
    }