asp.net mvc json serialization attributes - c#

How to change the name of the field names in Json output
I want the Class name cook and field "time1" to be something else please. I am using asp.net mvc controller class to return the json result.
public class Cook
{
public string time1;
public string time2;
public string time3;
}

What about something like this...
public ActionResult GetCook()
{
var cook = new Cook();
return Json(new
{
atime = cook.time1,
anothertime = cook.time2,
yetanothertime = cook.time3
});
}

You can write your custom Attribute and access that value through ViewData.ModelMetadata (make sure that your attribute class implements IMetadataAware). Then create your own JSON class which puts all your properties and its values inside a dictionary. The key for each property will be the name that you specify in your custom attribute.
This sounds a little cryptical maybe, but if you think you like this approuch I can give you a code example. Good luck!

Related

What is the proper way to handle a generic object in a WEBAPI method?

The scenario is something like this:
I have this web-api which will handle a variety of payment gateways, and I want to have the same endpoint for all of those.
So, what I have in mind is to get some json data like this:
{
"OperationId":"N0004",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Sale_id":1000,
"CodUser":"1000040",
"Email":"teste#teste.com"
}
}
Or this, for some other payment gateway
{
"OperationId":"N044444",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Token":1000,
"UserSettings":{
id: "4563345",
name: "Average Joe"
}
}
}
What I want to do is to transform this "Generic_Object_That_Will_Change_According_ToThe_GateWay" in the specific object for each payment gateway (paypal, or some other), becase each one is completely different, but I don't want that to affect the way the client will call this API - I want it to be as flexible as possible, in a way that you just have to pass whatever data in this Generic_Object_That_Will_Change_According_ToThe_GateWay, and I will then cast it to the proper object and then call another endpoint(like an aggragate microservice design) passing this newly created object.
My idea so far, was creating some class with a generic property like this
public class Payment<Gateway>
{
public int OperationId{ get; set; }
public Gateway paymentGateWay{ get; set; }
}
And this property paymentGateWay could be typed according the available payment Gateways.
And then maybe I could get this data in the API method as Object, and do the necessary casts
[Route("api/payment")]
[HttpPost]
public string Compra(Object payment) {
But, to be honest, I don't know if I'm in the right way.
I already know that I can't have a generic method in a web-api endpoint - so what would be the correct way to get this data in my endpoint considering that a part of this json data is flexible/generic and may be cast to a few different objects.
To summarize, I want to handle json data that can be deserialized to a few different known objects, but I don't want to have a different method in my API to handle each one this possible data scenarios.
if you want a generic method in webapi you have to use JObject
something like the following
public void Post([FromBody] JObject testJObject)
{
//here you have to do some additional work in order to parse and get it working for generic entity
}
in addition to this, you can use the Schema validator against any received request and use the factory pattern in order to create the correct object
here an example
var json =
" {\"OperationId\":\"N0004\",\"Generic_Object_That_Will_Change_According_ToThe_GateWay\":{\"Sale_id\":1000,\"CodUser\":\"1000040\"}}";
JsonSchema paypalschema = new JsonSchema();
paypalschema.Type = JsonSchemaType.Object;
paypalschema.Properties = new Dictionary<string, JsonSchema>
{
{"OperationId", new JsonSchema {Type = JsonSchemaType.String}},
{
"Generic_Object_That_Will_Change_According_ToThe_GateWay",
new JsonSchema {Type = JsonSchemaType.Object,Properties = new Dictionary<string, JsonSchema>
{
{"Sale_id", new JsonSchema {Type = JsonSchemaType.Integer}},
{"CodUser", new JsonSchema {Type = JsonSchemaType.String}},
}}
}
};
JObject requestObject = JObject.Parse( json);
bool valid = requestObject.IsValid(paypalschema);
if (valid)
{
//create your GatewayObject here
}
//else check another gateway object
Consider using JObject or String as your input (And then converting to JObject.) Then you can do some type or data checking before casting. Here's an example shows how they use a pre-defined 'type' value provided, but in lieu of that, you can instead look in the JObject for the 'parts' of each unique provider's payload to determine which type to use.
You can have a generic controller to implement the method and instance-controllers, which inherit of the generic controller:
// I'll rename Gateway to TGateway according to the fact, that it is a generic Type parameter.
public class Payment<TGateway>
{
public int OperationId{ get; set; }
public TGateway paymentGateWay{ get; set; }
}
GenericController:
// Don't add a RouteAttribute to this Controller.
public class GenericController<TGateway>: ApiController
{
// The implementation of the method. No RouteAttribute.
[HttpPost]
public string Compra(Payment<TGateway> payment) {...}
}
InstanceController:
// No need to override the method. RouteAttribute.
[Route("api/payment/"+typeof(AGateway).Name)]
public class AGatewayController : GenericController<AGateway>
{}

Use nameof() to get a attributepath for ModelState.AddModelError()

I like to use nameof() in my ASP.NET Core application when setting mvc errors using ModelState.AddModelError(). This reduces hidden errors since I pass the name of an attribute here. Compared to a string, this has the benefit that a compiler error is thrown when the attribute was renamed.
Example
class MyModel{
public string Name{get;set;}
}
public class MyController:Controller{
public IActionResult Test(MyModel model) {
// Will hiddenly fail when "Name" is renamed to something else
ModelState.AddModelError("Name", "The Name field is required!");
// Better: Using the auto renaming feature of VS, this will be updated if "Name" was renamed
ModelState.AddModelError(nameof(model.Name), "The Name field is required!");
}
}
This works, until I have another entity:
class MyModel{
public string Name{get;set;}
public MyOtherModel OtherModel{get;set;}
}
class MyOtherModel{
public string OtherName{get;set;}
}
Now I want to get the name of OtherModel. The problem: ASP.NET requires BaseClass.ChildClass pattern here, so OtherModel.OtherName in this example. Using the same logic like above:
public class MyController:Controller{
public IActionResult Test(MyModel model) {
ModelState.AddModelError(nameof(model.MyOtherModel.OtherName), "The Name field is required!");
}
}
will just give me the name of the child attribute, so OtherName in this example. But I need OtherModel.OtherName. Is there a clean way to get this without building the string itself?
This would be possible doing something like this:
string actionName = nameof(model.MyOtherModel) + "." + nameof(model.MyOtherModel.OtherName);
But not a very clean and intuitive way imho.
As others have suggested, you will probably have to write your own method to do this. This is what I came up with using Linq.Expressions:
string NameOfMember(Expression<Func<Model, object>> accessor)
{
string str = accessor.Body.ToString();
return str.Substring(str.IndexOf('.') + 1);
}
You would then use it like this: NameOfMember(model => model.MyOtherModel.OtherName)
I'm not familiar with ASP.NET but you can change Model to any type you want, including object.
As a side note, you shouldn't repeat "The Name field is required!" in your code. Maybe you can include that in your helper method as well.

Dynamically deserializing to a property in RestSharp

I am playing with the Harvest API and I'm trying to automatically map the entities as easy as possible, unfortunately when I do a request like GET /projects it generates a result like so:
[{
project: {
name: "Test"
}
},
{
project: {
name: "Test 2"
}]
In RestSharp, I can't directly do this:
client.Execute<List<Project>>(request)
Because it is going to look for a property called Project. So I have to make another class that has that property, and call it like this:
client.Execute<List<ProjectContainer>>(request)
I don't want to make a 'container' class for every entity, so I thought I found a clever solution to make one class I can use on all:
public class ListContainer<T> where T : IHarvestEntity
{
public T Item { get; set; }
}
But, of course, the deserializer has no idea it needs to map the entity name (or "Project") to the property Item. In the restsharp documentation I found that I could use [DeserializeAs(Name = "CustomProperty")] to tell the deserializer which field to map to this property. However, attributes do only allow constants, which means I can't do:
[DeserializeAs(Name = typeof(T).FullName)]
public T Item { get; set; }
Does anyone know a clever solution to this? So i don't have to create 10 different container classes?
I suggest you use the XPath equivalent for Json. With Json.NET you can parse the string and create a dynamic object.
With SelectToken you can query values, or using Linq.
The code looks something like this (I did not test it):
// execute the request
RestResponse response = client.Execute(request);
var content = response.Content; // raw content as string
JObject o = JObject.Parse(content);
IList<string> projectNames = o.SelectToken("project").Select(s => (string)s.name).ToList();
You can code the paths or configure the paths anyway you like.
--- Edit ---
Here's an example that I tested, converting the json string to a list of projects.
var projects = JArray.Parse(response.Content).Select(r => new Project(r["project"]["name"].Value<string>())).ToList();
To keep it really simple, you can use List<dynamic> and access the property/properties by name with a one-liner.
var names = client.Execute<List<dynamic>>(request).Data.Select(
item => item["project"]["name"]).ToList(); // list of names
If this is not sufficient, then you could improvise your own mapper and extract a collection of e.g. Project instances:
var projects = client.Execute<List<dynamic>>(request).Data.Select(
item => Map<Project>(item)).ToList(); // list of Project instances
where Map method could be something like
public T Map<T>(dynamic item) where T : class
{
// inline for clarity
var mappings = new Dictionary<Type,Func<dynamic,object>>
{
{ typeof(Project), map => new Project(map["project"]["name"]) }
};
return (T)mappings[typeof(T)].Invoke(item);
}
given Project is defined as
public class Project
{
public Project(string name)
{
Name = name;
}
public string Name { get; set; }
}

Decoration on ViewModel property to use a different name for binding

On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.

MVC 4 ApiController not serializing System.Json.JsonObject properly

So I am new to working with MVC4 and the serialization of objects on the back end seems pretty "magical" to me so if I am doing this the wrong way please let me know.
My goal however is to build a simple rest API and return JSON out. I figured that I would use System.Json and just return JsonObject. I have simplified this down for the sake of this question but the objects are much more complicated in my real issue.
Here is my controller....
....
public class ActionsController : ApiController
{
// GET api/actions
public JsonObject Get()
{
JsonObject testObjet = new JsonObject();
testObjet.Add("Name", "Test name");
testObjet.Add("Description", "Test Description");
return testObjet;
}
....
I would expect to see:
{"Name":"Test name","Description":"Test Description"}
Instead I see:
{"Name":[],"Description":[]}
I actually seem to get better results when I return a string of the JsonObject or heck even just return the object itself with the exception it has enums and I want to return the names not the number values, which is what led me to JsonObject for customization.
Does anyone know why it is dropping off the values?
EDIT:
So because of Dan's comments below I tried just for giggles to see what the XML serializer spit out with the JSON object and I get the below exception...
"Type 'System.Json.JsonPrimitive' with data contract name 'JsonPrimitive:http://schemas.datacontract.org/2004/07/System.Json' is not expected."
So it appears that you can not serialize the System.Json.JsonObject object, because it uses a type that it does not expect.
That is shocking. Does anyone have a workaround? If not I am off to find out how to show enum names when serializing instead of values.
So the answer is apparently... You Can't!
It appears that the type JsonPrimitive is not supported to serialize objects. The answers provided below by Obi and Dan helped me to poke around a bit more and find out that the XML serializer actually throws an exception while the JSON serializer simply eats it and puts out an empty array which is what you see above.
There are any number of correct answers here.
Make your own custom serializer
Output JSON as a string
Return custom objects and then work around things like the Enum
values
I am sure there are others.
But whatever you do don't try to use System.Json as a return in the ApiController because you will get the results above.
You should not force your WebApi call to use a particular format like JSON. One of the features of WebApi is that you can specify the format as part of the request. Return an object from your Get call, and let the WebApi engine do the serialization and deserialization:
public class DataObject
{
public string Name { get; set; }
public string Description { get; set; }
}
public class ActionsController : ApiController
{
// GET api/actions
public DataObject Get()
{
var testObject = new DataObject
{
Name = "Test name",
Description = "Test Description"
};
return testObject;
}
}
You can specify the format by setting the Accept header to application/xml, application/json, etc.
The default JSON serializer has no problem serializing simple string properties like Name and Description.
I would suggest you did this instead
// GET api/actions
public object Get()
{
//New up a strongly typed object if you want to return a specific type
//and change Action return type accordingly
var testObjet = new (){
Name= "Test name",
Description= "Test Description"
};
return testObjet;
}
Dan has posted a similar answer below so let me try to address your other problem. To serialize the enum, I would suggest you hide it in a public string property which would return the string value of the enum,
public class DataObject{
public MyEnum SomeEnumValue;
public string EnumValue{
get {
//..return SomeEnumValue string value
}
}
}
You can then read the value from EnumValue which should be properly serialized as you want.

Categories

Resources