serialize mongoDB document with ObjectID id back after JSON conversion - c#

I have this C# object type:
public class MyClass
{
[BsonRepresentation(BsonType.ObjectId)]
public ObjectId id { get; set; }
public string myString { get; set; }
}
My backend (C#) gets a document of this type from MongoDB and returns it to my frontend client (JS) which modifies its property (myString) and then sends it back to backend in the body of an HTTP POST Request, but I can't convert it back to MyClass...
I tried this:
string incomingRequest = await new StreamReader(req.Body).ReadToEndAsync();
var test = Newtonsoft.Json.JsonConvert.DeserializeObject<MyClass>(incomingRequest);
// or "MyClass test = ..."
=> Error converting value \"60b903b87c309bb60f88adc6\" to type 'MongoDB.Bson.ObjectId
and this:
string incomingRequest = await new StreamReader(req.Body).ReadToEndAsync();
var test = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<MyClass>(incomingRequest);
// or "MyClass test = ..."
=> Element 'id' does not match any field or property of class MyProject.Models.Myclass.
The problem is that the frontend (JS) converts id's type from ObjectId to string, but I'm not sure how to change that back to ObjectId in backend. If I change MyClass.id type to string, the problem is solved, but I read that the performance much better when using ObjectId instead of string.
Any idea? :(

Related

Why can't I deserialize string Into a model?

I'm using a HTTP client to get a string and picking out my json from that and converting back to a string to deserialize it into a List of "Spots" but can't get it to to work
I've tried changing the DeserializeObject type to every mix of "List, IList, HardwareUpdateSpot, HardWareModel" and still it didn't work
public async Task<IList<HardwareUpdateSpot>> UpdateSpotHTTP()
{
var client = new HttpClient();
var response = await client.GetAsync(
"https://io.adafruit.com/api/v2/Corey673/feeds/673d855c-9f66-4e49-8b2c-737e829d880c");
var responseHTTP = response.Content.ReadAsStringAsync();
var j = JObject.Parse(responseHTTP.Result);
var b = j.GetValue("last_value");
var h = b.ToString();
var dataObjects = JsonConvert.DeserializeObject<IList<HardwareUpdateSpot>>(h);
return null;
}
public record HardWareModel
{
public int SpotId { get; set; }
public string Occupied { get; set; }
}
public class HardwareUpdateSpot
{
public IList<HardWareModel> Spots { get; set; }
public HardwareUpdateSpot(IList<HardWareModel> spots)
{
Spots = spots;
}
}
While trying to reproduce your problem I have examined the returned value from the API call. This is the json returned:
{"Spot":[
{"SpotId":"1","Occupied":"false",},
{"SpotId":"2","Occupied":"false",},
{"SpotId":"3","Occupied":"false",},
{"SpotId":"4","Occupied":"false"}
]}
So, it easy to see that the returned json requires a root object with a public Spot property (not Spots) and this property should be a collection.
Instead the code above expects a json that has at the root level a collection of HardwareUpdateSpot and of course it cannot work.
To fix the problem you need to change the deserialization to:
JsonConvert.DeserializeObject<HardwareUpdateSpot>(h);
Now, you need to make some changes to the HardwareUpdateSpot class to make it compatible with the json.
First you need to add a parameterless constructor required by jsonconvert, then you need to fix the difference between the name for the property (Spots) and the name returned (Spot).
So you can change the property name to match the json or add the attribute that make Spots=Spot
[JsonProperty("Spot")]
public IList<HardWareModel> Spots { get; set; }

C# getting MongoDB BsonDocument to return as JSON

I asked a question a couple of days ago to collect data from MongoDB as a tree.
MongoDB create an array within an array
I am a newbie to MongoDB, but have used JSON quite substantially. I thought using a MongoDB to store my JSON would be a great benefit, but I am just experiencing immense frustration.
I am using .NET 4.5.2
I have tried a number of ways to return the output from my aggregate query to my page.
public JsonResult GetFolders()
{
IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>("DataStore");
PipelineDefinition<BsonDocument, BsonDocument> treeDocs = new BsonDocument[]
{
// my query, which is a series of new BsonDocument
}
var documentGroup = collection.Aggregate(treeDocs).ToList();
// Here, I have tried to add it to a JsonResult Data,
// as both documentGroup alone and documentGroup.ToJson()
// Also, loop through and add it to a List and return as a JsonResult
// Also, attempted to serialise, and even change the JsonWriterSettings.
}
When I look in the Immediate Window at documentGroup, it looks exactly like Json, but when I send to browser, it is an escaped string, with \" surrounding all my keys and values.
I have attempted to create a model...
public class FolderTree
{
public string id { get; set; }
public string text { get; set; }
public List<FolderTree> children { get; set; }
}
then loop through the documentGroup
foreach(var docItem in documentGroup)
{
myDocs.Add(BsonSerializer.Deserialize<FolderTree>(docItem));
}
but Bson complains that it cannot convert int to string. (I have to have text and id as a string, as some of the items are strings)
How do I get my MongoDB data output as Json, and delivered to my browser as Json?
Thanks for your assistance.
========= EDIT ===========
I have attempted to follow this answer as suggested by Yong Shun below, https://stackoverflow.com/a/43220477/4541217 but this failed.
I had issues, that the "id" was not all the way through the tree, so I changed the folder tree to be...
public class FolderTree
{
//[BsonSerializer(typeof(FolderTreeObjectTypeSerializer))]
//public string id { get; set; }
[BsonSerializer(typeof(FolderTreeObjectTypeSerializer))]
public string text { get; set; }
public List<FolderTreeChildren> children { get; set; }
}
public class FolderTreeChildren
{
[BsonSerializer(typeof(FolderTreeObjectTypeSerializer))]
public string text { get; set; }
public List<FolderTreeChildren> children { get; set; }
}
Now, when I look at documentGroup, I see...
[0]: {Plugins.Models.FolderTree}
[1]: {Plugins.Models.FolderTree}
To be fair to sbc in the comments, I have made so many changes to get this to work, that I can't remember the code I had that generated it.
Because I could not send direct, my json result was handled as...
JsonResult json = new JsonResult();
json.Data = documentGroup;
//json.Data = JsonConvert.SerializeObject(documentGroup);
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return json;
Note, that I also tried to send it as...
json.Data = documentGroup.ToJson();
json.Data = documentGroup.ToList();
json.Data = documentGroup.ToString();
all with varying failures.
If I leave as documentGroup, I get {Current: null, WasFirstBatchEmpty: false, PostBatchResumeToken: null}
If I do .ToJson(), I get "{ \"_t\" : \"AsyncCursor`1\" }"
If I do .ToList(), I get what looks like Json in json.Data, but get an error of Unable to cast object of type 'MongoDB.Bson.BsonInt32' to type 'MongoDB.Bson.BsonBoolean'.
If I do .ToString(), I get "MongoDB.Driver.Core.Operations.AsyncCursor`1[MongoDB.Bson.BsonDocument]"
=========== EDIT 2 =================
As this way of extracting the data from MongoDB doesn't want to work, how else can I make it work?
I am using C# MVC4. (.NET 4.5.2)
I need to deliver json to the browser, hence why I am using a JsonResult return type.
I need to use an aggregate to collect from MongoDB in the format I need it.
My Newtonsoft.Json version is 11.0.2
My MongoDB.Driver is version 2.11.1
My method is the simplest it can be.
What am I missing?

How to post a dynamic JSON property to a C# ASP.Net Core Web API using MongoDB?

How to post a dynamic JSON property to a C# ASP.Net Core Web API using MongoDB?
It seems like one of the advantages of MongoDB is being able to store anything you need to in an Object type property but it doesn't seem clear how you can get the dynamic property info from the client ajax post through a C# Web API to MongoDB.
We want to allow an administrator to create an Event with Title and Start Date/Time but we also want to allow the user to add custom form fields using Reactive Forms for whatever they want such as t-shirt size or meal preference... Whatever the user may come up with in the future. Then when someone registers for the event, they post EventID and the custom fields to the Web API.
We can have an Event MongoDB collection with _id, event_id, reg_time, and form_fields where form_fields is an Object type where the dynamic data is stored.
So we want to POST variations of this JSON with custom FormsFields:
Variation 1:
{
"EventId": "595106234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"FirstName": "John",
"LastName": "Public",
"TShirtSize": "XL"
}
}
Variation 2:
{
"EventId": "d34f46234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"Email": "John.Public#email.com",
"MealPref": "Vegan"
}
}
I would like to have an EventController with Post action that takes a custom C# EventReg object that maps to the JSON above.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new BsonDocument();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("EventId")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime
{
set; get;
}
[BsonElement("form_fields")]
public MongoDB.Bson.BsonDocument FormFields { get; set; }
}
EventService
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
Right now, if I post to the controller, my EventReg is null because it must not know how to map my JSON FormFields properties to a BsonDocument.
What type can I use for FormFields?
Can I have the FormFields property be a BsonDocument and is there an easy way to map the Web API parameter to that?
Is there an example of how some custom serializer might work in this case?
We could maybe use a dynamic type and loop through the posted properties but that seems ugly. I have also seen the JToken solution from a post here but that looks ugly also.
If MongoDB is meant to be used dynamically like this, shouldn't there be a clean solution to pass dynamic data to MongoDB? Any ideas out there?
In ASP.NET Core 3.0+ Newtonsoft.Json is not the default JSON serializer anymore. Therefore I would use JsonElement:
[HttpPost("general")]
public IActionResult Post([FromBody] JsonElement elem)
{
var title = elem.GetProperty("title").GetString();
...
The JToken example works to get data in but upon retrieval it causes browsers and Postman to throw an error and show a warning indicating that content was read as a Document but it was in application/json format. I saw the FormFields property being returned as {{"TShirtSize":"XL"}} so maybe double braces was a problem during serialization.
I ended up using the .NET ExpandoObject in the System.Dynamic namespace. ExpandoObject is compatible with the MongoDB BsonDocument so the serialization is done automatically like you would expect. So no need for weird code to manually handle the properties like the JToken example in the question.
I still believe that a more strongly typed C# representation should be used if at all possible but if you must allow any JSON content to be sent to MongoDB through a Web API with a custom C# class as input, then the ExpandoObject should do the trick.
See how the FormFields property of EventReg class below is now ExpandoObject and there is no code to manually handle the property of the object being saved to MongoDB.
Here is the original problematic and overly complex JToken code to manually populate an object with standard type properties and a dynamic FormFields property:
[HttpPost]
public void Post([FromBody]JToken token)
{
if (token != null)
{
EventReg eventReg = new EventReg();
if (token.Type == Newtonsoft.Json.Linq.JTokenType.Object)
{
eventReg.RegTime = DateTime.Now;
foreach (var pair in token as Newtonsoft.Json.Linq.JObject)
{
if (pair.Key == "EventID")
{
eventReg.EventId = pair.Value.ToString();
}
else if (pair.Key == "UserEmail")
{
eventReg.UserEmail = pair.Value.ToString();
}
else
{
eventReg.FormFields.Add(new BsonElement(pair.Key.ToString(), pair.Value.ToString()));
}
}
}
//Add Registration:
eventService.AddEventRegistration(eventReg);
}
}
Using ExpandoObject removes the need for all of this code. See the final code below. The Web API controller is now 1 line instead of 30 lines of code. This code now can insert and return the JSON from the Question above without issue.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new ExpandoObject();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("event_id")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("user_email")]
public string UserEmail { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime{ get; set; }
[BsonElement("form_fields")]
public ExpandoObject FormFields { get; set; }
}
EventService:
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
If you are not sure about the type of Json you are sending i.e. if you are dealing with dynamic json. then the below approach will work.
Api:
[HttpPost]
[Route("demoPath")]
public void DemoReportData([FromBody] JObject Jsondata)
Http client call from .net core app:
using (var httpClient = new HttpClient())
{
return await httpClient.PostAsJsonAsync(serviceUrl,
new demoClass()
{
id= "demoid",
name= "demo name",
place= "demo place"
};).ConfigureAwait(false);
}
You can define FormFields as a string and send data to the API in string format after converting JSON to string:
"{\"FirstName\":"John\",\"LastName\":\"Public\",\"TShirtSize\":\"XL\"}"
Then in your controller parse the string to BsonDocument
BsonDocument.Parse(FormFields);
I would use AutoMapper to automate the conversion between the dto and the document

How to use the Serialize method from the ElasticClient class from the NEST client in c#?

I've created a successful connection to ES, and then written my json query. Now, I would like to send that query via the Serialize method.
The Serialize method requires two parameters:
1. object and
2. Stream writableStream
My question is, with the second one. When I create a stream with the following code line:
Stream wstream;
And use it to initialize my json2 variable with the following code:
var json2 = highLevelclient.Serializer.Serialize(query, wstream).Utf8String();
I get the following error on the wstream variable:
Use of unassigned local variable 'wstream'.
Am I missing something? Is it the way I create the wstream variable that is wrong? thank you.
/* \\\ edit: ///// */
There is another issue now, I use Searchblox to index and search my files, which itself calls ES 2.x to do the job. Searchblox uses a "mapping.json" file to initialize a mapping upon the creation of an index. Here's the link to that file.
As "#Russ Cam" suggested, I created my own class content with the following code (just like he did with the "questions" index and "Question" class):
public class Content
{
public string type { get; set; }
public Fields fields { get; set; }
}
public class Fields
{
public Content1 content { get; set; }
public Autocomplete autocomplete { get; set; }
}
public class Content1
{
public string type { get; set; }
public string store { get; set; }
public string index { get; set; }
public string analyzer { get; set; }
public string include_in_all { get; set; }
public string boost { get; set; }
} //got this with paste special->json class
These fields from the content class (type,store etc.) come from the mapping.json file attached above.
Now, when I (just like you showed me) execute the following code:
var searchResponse = highLevelclient.Search<Content>(s => s.Query(q => q
.Match(m => m.Field(f => f.fields.content)
.Query("service")
All I get as a response on the searchResponse variable is:
Valid NEST response built from a successful low level call on POST: /idx014/content/_search
Audit trail of this API call:
-HealthyResponse: Node: http://localhost:9200/ Took: 00:00:00.7180404
Request:
{"query":{"match":{"fields.content":{"query":"service"}}}}
Response:
{"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]}}
And no documents in searchResponse.Documents.
Contradictorily, when I search for the "service" query on Searchblox or make an API call to localhost:9200 with the Sense extension of Google Chrome, I get 2 documents. (the documents that I was looking for)
In brief, all I want is to be able to :
get all the documents (no criteria)
get all the documents within a time range and based upon keywords.. such as "service"
What am I doing wrong? I can provide with more information if needed..
Thank you all for your detailed answers.
It's actually much simpler than this with NEST :) The client will serialize your requests for you and send them to Elasticsearch, you don't need to take the step to serialize them yourself and then pass them to the client to send to Elasticsearch.
Take a search request for example. Given the following POCO
public class Question
{
public string Body { get; set; }
}
We can search for questions that contain the phrase "this should never happen" in the body with
var settings = new ConnectionSettings(new Uri("http://localhost:9200"))
.InferMappingFor<Question>(m => m
.IndexName("questions")
);
var client = new ElasticClient(settings);
var searchResponse = client.Search<Question>(s => s
.Query(q => q
.MatchPhrase(m => m
.Field(f => f.Body)
.Query("this should never happen")
)
)
);
// do something with the response
foreach (var question in searchResponse.Documents)
{
Console.WriteLine(question.Body);
}
this line
My question is, with the second one. When I create a stream with the following code line:
Stream wstream;
does not create na object. It nearly declares it. You need to new-ed it.
Stream wstream = new MemoryStream(); //doesn't have to be MemoryStream here - check what does Serialize expects
Just remember to close it later or use using statement.
using(Stream stream = new MemoryStream())
{
//do operations on wstream
} //closes automatically here
You just declared wstream but never assigned an actual stream to it. Depending on how Serialize method works it could be:
you need to create a stream and pass it to the Serialize method
or you need to pass stream parameter with out prefix

Null value after json deserialization on windows phone

I am using the Json.DeserializeObject method in windows phone, inorder to deserialize json, the problem I am having is one of the variable names, in the json has a space and I just can't get it to deserialize. it returns a null the whole time, and if I view the raw json it does contain a value
part of raw json:
\"Service Provider\":Test\"
When I try to generate a class for the json into which it needs to be deserialized, the Service Provider section tells me "Invalid Name" and that obviously doesn't work in C# as a variable name, but I believe the variable name can be anything:
public string __invalid_name__Service Provider { get; set; }
current code:
public string Service_Provider { get; set; }
Using Json.Net, Just decorate your property with "JsonProperty" Attribute
string json = #"{""Service Provider"":""Test""}";
var obj = JsonConvert.DeserializeObject<TempObject>(json);
public class TempObject
{
[JsonProperty("Service Provider")]
public string ServiceProvider;
}

Categories