20

I am trying to write a general purpose Web Api controller that will allow me to save a JSON document to a collection WITHOUT specifying a C# type. I've tried to condense the code down to the essentials:

public class PassThroughController : ApiController
{
    [Route("api/mongodb/{collection}")]
    public void Post(string collection, dynamic document)
    {
        const string connectionString = "mongodb://localhost";

        var client = new MongoClient(connectionString);
        var db = client.GetServer().GetDatabase("SampleDb");
        var mongoCollection = db.GetCollection(collection);

        mongoCollection.Save(document,
            new MongoInsertOptions
            {
                WriteConcern = WriteConcern.Acknowledged
            });
        }
}

I'm trying to post a simple document:

{ id: "2112", name: "Rush" }

But no matter what I send to the method, I get an error similar to this: "Save can only be used with documents that have an Id."

We've attempted a number of different properties for Id (Id, id, _id) but they all result in a similar issue.

Any ideas?

Thanks

3 Answers 3

25

With the help of a co-worker, I figured out a solution:

public class PassThroughController : ApiController
{
    [Route("api/mongodb/{collection}")]
    public void Post(string collection, HttpRequestMessage message)
    {
        const string connectionString = "mongodb://localhost";

        var client = new MongoClient(connectionString);
        var db = client.GetServer().GetDatabase("SampleDb");
        var mongoCollection = db.GetCollection(collection);

        var json = message.Content.ReadAsStringAsync().Result;
        var document = BsonSerializer.Deserialize<BsonDocument>(json);

        mongoCollection.Save(document,
            new MongoInsertOptions
            {
                WriteConcern = WriteConcern.Acknowledged
            });
        }
}
Sign up to request clarification or add additional context in comments.

Comments

8

I was trying to save some dynamic object associated to a ProcessID, the dynamic object it's totally "dynamic", sometimes it can be one property with some arbitrary data and name, sometimes it can be a 100 properties with totally arbitrary names for each property. In fact, it's an object coming from an Angular 5 app that generates a dynamic form from (surprise) some JSON object. My "base" class, the one that get inserted into mongo it's like this:

public class MongoSave {

    [BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
    public string Id { get; set; }        
    public int ProcessID { get; set; }
    public BsonDocument DynamicData { get; set; }

}

On my first attempt the DynamicData property was type object, the controller method received everything ok, assign the incoming object to the class but when saving the object to mongo the values for DynamicData were lost. I was getting only the property names. Tried to use the BsonSerializeannotation in my class but with no success.

Researching a little bit i discovered that BsonDocument has a Parse method similar to javascript that parse a string containing an object description, so... i posted an object like this to my controller:

{
    "ProcessID": 987,
    "DynamicData": {
        "name": "Daniel",
        "lastName": "Díaz",
        "employeeID": 654
    }
{

A few things here, i'm sending via Insomnia (a Postman alternative) this JSON to the API. In real world i'll be sending this object via Angular so i have to be sure to apply JSON.stringify(obj) to be sure that the JSON object it's ok (don't know for sure if the Angular HttpClient post objects as stringified JSON or as JS objects (i'll test that). Changed my controller action to use the Parse method and the DynamicData C# generic object to string like this:

[HttpPost("~/api/mongotest/save")]
public async Task<IActionResult> PostObjectToMongo (MongoSaveDTO document) {

    MongoSave mongoItem = new MongoSave() {
        ProcessID = document.ProcessID,
        DynamicData = BsonDocument.Parse(document.DynamicData.ToString())
    };

    await _mongoRepo.AddMongoSave(mongoItem);

    return Ok();
}

The MongoSaveDTO it's the same as MongoSave without the BsonIdproperty and instead of BsonDocument i'm using C# generic object type and everything saves like a charm.

2 Comments

That is very interesting. Thank you for sharing. I tried using ExandoObject and had all sorts of hell with serialistion.. but I like you can just use the object like obj.DynamicVar = "test" as that makes assigning values easier. But saving it to Mongo didnt go well :( May try your solution
ExpandoObject cannot be saved "as is" in mongodb due to the fact it does not have structure, although it's possible to use reflection on it (if creating a dynamid type using ExpandoObject). Converting from/to json and bsondocument appears cleaner and easier to me.
0

It is the easiest to register custom json serializer:

public class BsonDocumentJsonConverter : JsonConverter<BsonDocument>
{
    public override BsonDocument Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options) =>
        BsonDocument.Parse(reader.GetString());

    public override void Write(
        Utf8JsonWriter writer,
        BsonDocument value,
        JsonSerializerOptions options) =>
        writer.WriteStringValue(value.ToString());
}

after use it in entity:

public class ContentEntity: EntityBase
{
    public string Repository { get; set; }
    public string Branch { get; set; }
    public string FilePath { get; set; }
    public string ContentId { get; set; }
    public string Title { get; set; }

    [JsonConverter(typeof(BsonDocumentJsonConverter))]
    public BsonDocument Content { get; set; }

    [JsonConverter(typeof(BsonDocumentJsonConverter))]
    public BsonDocument Meta { get; set; }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.