Custom Serialization in ASP.NET MVC API - serialization

I've got a class that looks something like this:
public class Position
{
public int X { get; set; }
public int Y { get; set; }
}
Assuming a data contract similar to this one:
[DataContract]
public class MyModel
{
[DataMember(name="position")]
public Position Position { get; set; }
}
I want to send this position value both to and from the client using a value like this:
JSON: { "position": "1,2" }
XML: <position>1,2</position>
I'll just add that I'm not trying to do this:
JSON: { "position": { "data": "1,2" } }
XML: <position><data>1,2</data></position>
I'd rather not inject this into the whole serialization pipeline if possible. I had it working on a web controller, but the API controller is proving a little tricky. What is the best way to do this?
Thanks
UPDATE
Decided in this case to use the position fields as is (e.g. { "position": { "X": 1, "Y": 2 } }), but I'll leave this open and update it if I come across a solution.

You could use a view model:
[DataContract]
public class PositionViewModel
{
[DataMember(name = "position")]
public string Position { get; set; }
}
and then map between your Position domain model and your view model in the controller action if needed.

Related

Better Way of Dealing With Circular References? - EF Core 3.1 and Web API

I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.
Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.
User:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
DtoItems:
namespace TestWebApplication.Database.DTOs
{
public class DtoItems
{
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public DtoUser User { get; set; }
}
}
DtoUser:
namespace TestWebApplication.Database.DTOs
{
public class DtoUser
{
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
}
}
TestController:
[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
List<Items> items = _myContext.Items.Include(i => i.User).ToList();
// DTOs
List<DtoItems> dtoItems = new List<DtoItems>();
foreach (var i in items)
{
var dtoItem = new DtoItems
{
Item = i.Item,
Category = i.Category,
Type = i.Type,
User = new DtoUser
{
UserName = i.User.UserName,
Dob = i.User.Dob,
Location = i.User.Location
}
};
dtoItems.Add(dtoItem);
}
return Ok(dtoItems);
}
The output from endpoint:
[
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xx"
}
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xxx"
}
}
]
In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.
I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).
There is really only two other ways of handling this.
You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line
Like so :
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820
You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...
So your best bet, stick with the DTO.
More info :
https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
https://github.com/dotnet/runtime/issues/30820
https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

.NetCore 3 Web API: exclude model objects from controller response

I'm learning the basics of .NetCore 3 Web API and creating a basic app with it, and I'm stuck with this question.
I have a model with a foreign key that looks like this:
public class Rule
{
public long Id { get; set; }
public long CategoryId { get; set; }
public Category Category { get; set; }
[Required]
[StringLength(50)]
public string Keyword { get; set; }
}
I can POST new Rules by just passing a CategoryId identifier. But when I do a GET request I get both the FK attribute (CategoryId) and the Category object that is referenced just for navigation purposes.
Request response example:
{
"id": 1,
"categoryType": "Expense",
"categoryId": 2,
"category": null,
"keyword": "TEST"
}
Is there an easy/quick way of excluding model elements from being exposed by the controller? Using DataAnnotations, Fluent API or just a service configuration?
By the way, I'm suing EFCore as well.
Thank you.
A way is to create a manual JSON object using JSON Convert
return JsonConvert.SerializeObject(new
{
id = rule.id,
//more fields as needed...
});

C# type serialization not as a collection of key value pairs

I have a class that when serialized, I want to return as a single string without key value pairs. A string becomes a string. An int becomes an int. How do I make my class become a string?
Looking at DataContract and Serializable, it doesn't look that this is possible. The SerializationInfo.AddValue(name, value) setup forces your whole object into a key-value approach. I just want to return "A and B".
[DataContract]
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
When serialized using the DataContractJsonSerializer, for example, I want it to be:
4 and 2
Not
{
"A": 4,
"B": 2
}
So let's say I have a parent class that uses both of these custom types:
[DataContract]
public class Parent
{
[DataMember]
public MyObject One { get; set; }
[DataMember]
public MyObject Two { get; set; }
}
I want to it serialize like this:
{
"One": "4 and 2",
"Two": "6 and 8"
}
Instead, the only thing I seem to be able to make it do is:
{
"One": {
"A": 4,
"B": 2
},
"Two": {
"A": 6,
"B": 8
}
}
The only solution that would work, is to add the custom serialization on Parent, setting One and Two accordingly, but I don't want to do that, I want my new class to get serialized as a string everywhere.
Can I serialize a type as a single return value? Or do all types besides built-in ones like int and string have to serialize to an object?
You can simply add a get attribute that returns the attributes as a string in any way that you like...
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
public string MyNewStringAttribute {
get{
return A + " and " + B;
}
}
}
Then, when you can serialize from the controller using the new attribute from an array, linq, etc.
My answer: You can't.
The parent class, MyObject has to implement ISerializable to provide the custom serialization of the class itself. This places the responsibility of correctly serializing the class the way it is intended to each class that wishes to use it, which is an undesirable experience, and highlights a severe design flaw in the supported .NET custom serializer strategy.
[Serializable]
public class Parent : ISerializable
{
public MyObject One { get; set; }
public MyObject Two { get; set; }
public (SerializerInfo info, SerializerContext context)
{
string oneString = info.GetString("One");
One = new MyObject(oneString);
string twoString = info.GetString("Two");
Two = new MyObject(twoString);
}
public override void GetObjectData(SerializerInfo info, SerializerContext context)
{
info.AddValue("One", One.ToString());
info.AddValue("Two", Two.ToString());
}
}
Grumble grumble... add this to the long list of things that .NET has gotten wrong. Static / non interfaced classes for everything making the framework untestable by default without creating plug-n-chug wrappers for everything. Compilers with lossful MSIL/symbols. I could go on and on.

Restful api to receive an array from request

I have been tasked with building an API, that as a request, will take a product number which will have a quantity and size , zip code, shipping method.
The customer has a cart and in that cart is the product number, quantity and size
so basically he would send a json request that looks like the below
{
"ShoppingCart": {
"Products": [
{
"Sku": "123",
"Size": "S",
"Quantity": "1"
},
{
"Sku": "456",
"Size": "M",
"Quantity": "2"
},
{
"Sku": "789",
"Size": "L",
"Quantity": "3"
}
],
"ShipToZip": "54452",
"ShipMethod": "Ground"
}
}
is it possible to receive an HTTP json request on my .net core rest webapi that im making.
If so, what would the route look like to send json like that? it'sgoing to be pretty long if they have to put the entire json in the url right?
EDIT:
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
Yes. You are correct. For instance, the following controller action would accept a POST body with the JSON from your question and respond with that same JSON.
public class Product
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
public class Cart
{
public List<Product> Product { get; set; }
public string ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class CartBody
{
public Cart Cart { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost]
public ActionResult<CartBody> Post(CartBody cartBody)
{
return cartBody;
}
}
I guess I did not look hard enough :(
Anyway the more questions I feel like the more exposure some newbie problems will have.
How to receive json in ASP.NET web api?
I got my solution from there,
All I had to do was create a "Model" class like below that matches the exact JSON format being sent, no deserielization needed
public class RequestModel
{
public ShoppingCart RequestShoppingCart { get; set; }
}
public class ShoppingCart
{
public Products[] Products { get; set; }
public int ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class Products
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
Then from there in my API Controller i can do the following to see it working
[Produces("application/json")]
[Route("api/ShippingCalculator")]
public class ShippingCalculatorController : Controller
{
// POST: api/ShippingCalculator
[HttpPost]
public string Post([FromBody]RequestModel jsonRequest)
{
// Debug.WriteLine(jsonRequest.RequestShoppingCart.Products);
return jsonRequest.RequestShoppingCart.ShipMethod;
}
}

Nested json deserialized incompletely

I mentioned in title that the json is deserialized incompletely because some of the objects have the right value in the controller (I send the json to an asp.net mvc 4 controller), but the problem occurs with properties of objects stored in arrays (to be exact, the problem appears with Data)
I have the following classes:
public class Node
{
[JsonProperty("id")]
public int? Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
private Data _data = new Data();
[JsonProperty("data")]
public Data Data { get; set; }
private List<Adjacency> _adjacencies = new List<Adjacency>();
[JsonProperty("adjacencies")]
public List<Adjacency> Adjacencies
{
get { return _adjacencies; }
set { _adjacencies = value; }
}
private List<Instance> _dependendStates = new List<Instance>();
public List<Instance> DependentStates
{
get { return _dependendStates; }
set { _dependendStates = value; }
}
}
public class Data
{
[JsonProperty("$posX")]
public decimal PosX { get; set; }
[JsonProperty("$posY")]
public decimal PosY { get; set; }
}
public class Adjacency
{
[JsonProperty(PropertyName = "nodeTo")]
public string NodeTo { get; set; }
[JsonProperty(PropertyName = "data")]
public AdjData Data { get; set; }
//doesn't contain definition for automated transitions
}
public class AdjData
{
[JsonProperty(PropertyName = "$labelid")]
public string LabelId { get; set; }
[JsonProperty(PropertyName = "$labeltext")]
public string Label { get; set; }
[JsonProperty(PropertyName = "$type")]
public string Type { get; set; }
}
I've highlighted fields that don't have the right value.
The null problem http://img19.imageshack.us/img19/530/36976913.png
The json looks like this:
{
"result":[
{
"id":"100",
"name":"Start",
"data":{
"$posX":-100,
"$posY":-100,
"$deployed":true,
"$color":"#000000",
"$selected":"true"
},
"adjacencies":[
{
"nodeTo":"188",
"data":{
"$type":"labeled_arrow",
"$labelid":"Label Name",
"$labeltext":"Label Name"
}
}
]
},
{
"id":"188",
"name":"Second ",
"data":{
"$dim":20,
"$color":"#000000",
"$selected":"true"
},
"adjacencies":[
]
}
],
"smName":"gftrds"
}
I'm having a problem sorting this out as I do not understand or see where the problem is.
Disclaimer: I wasn't able to reproduce this exactly, as in my case Label and LabelId deserialized fine, though Type did not, so my anwser may not address the problem you are having.
In my case, I was only able to deserialize the "$type":"labeled_arrow" property if it was not the first one - if it appeared after "$labelid":"Label Name" or "$labeltext":"Label Name" it worked fine.
Strangely, if the $type property was called type in the Json and referred to as [JsonProperty(PropertyName = "type")] in the C#, it would deserialize fine, regardless of the order.
In your case you can't deserialize Label or LabelId either, so it could be that your problem is caused by something else. To rule out what I have suggested, it might pay to try modifying some of the property names (possibly just take off the $) to see if they are causing the properties to be deserialized incorrectly.
I experienced the same issue, however in my case this was related to how the data was being transmitted from client side. Make sure the AJAX request is using the proper headers and formatting. For example:
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify({
MemberId : '123',
UserName: '456',
Parameters: [
{ Value : 'testing' },
{ Value : 'test2' }
]
}),