Using json.net serializer in webmethod - json

I'll start from my problem:
I have a webmethod that I'm calling via AJAX/JSON.
Let's call it "GlobalMethod", that's used to manage a "container" object, that has a list of items derived from the same "baseclass".
This is a code sample of my situation:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class MyService : System.Web.Services.WebService
{
[WebMethod]
public string GlobalMethod(Container data)
{
string result = "";
foreach (var item in data.Items)
{
result += item.BaseProperty;
if (item is FirstDerivedClass)
result += ((FirstDerivedClass)item).FirstDerivedProperty;
else if (item is SecondDerivedClass)
result += ((SecondDerivedClass)item).SecondDerivedProperty;
}
return result;
}
}
public class Container
{
public List<BaseClass> Items;
}
public abstract class BaseClass
{
public string BaseProperty;
}
public class FirstDerivedClass : BaseClass
{
public string FirstDerivedProperty;
}
public class SecondDerivedClass : BaseClass
{
public string SecondDerivedProperty;
}
This method simply doesn't work out. I won't be able to call this method using the default JavascriptSerializer, as the serializer isn't able to resolve what kind of objects the container will have: they could be of type FirstDerivedClass or SecondDerivedClass.
So, browsing the web for solutions to my problem, I've come across Json.NET, whose method JsonConvert.DeserializeObject is able to retrieve the original type of object that was serialized using the JsonConvert.SerializeObject, since it adds a property called "$type".
My problem now is: how can I make the webmethod using this serializer instead of the default one used by the ASMX?
If the method signature remains
[WebMethod]
public string GlobalMethod(Container data)
then I'll get a totally empty Container object, as the framework is doing the deserialization job and doesn't know which items to instantiate, and I have no way of telling the framework that it should use Json.NET for the job, which would be able to fully deserialize the data.
If I try modifying the method this way
[WebMethod]
public string GlobalMethod(string data)
{
string result = "";
Container container = JsonConvert.DeserializeObject<Container>(data,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
});
foreach (var item in container.Items) {
...
then I'll get another server error 500 because "no parameterless constructor defined for type of u0027system.string u0027"
Ok, hopefully my problem is clear... I've been spending the entire day on it and I don't seem to find a solution. Revisiting the architecture of my method in order to avoid usage of derived classes isn't quite an option (beware the actual code is much more complex, I've just simplified it to get to the point!).
By the way, I'll add that the ajax method calling the webmethod is done this way:
$.ajax({
type: "POST",
url: wsUrl + method,
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
dataType: "json",
processData: false,
success: function(msg)
{
try{
success(JSON.parse(msg.d));
} finally {
}
},
error: error
});
Thanks to anybody who will share his knowledge with me!
I'd like to share how I actually solved my problem. As I was already saying, I modified my method this way:
[WebMethod]
public string GlobalMethod(string data)
{
string result = "";
Container container = JsonConvert.DeserializeObject<Container>(data,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
});
foreach (var item in container.Items) {
Initially, I started receiving the error 500 "no parameterless constructor defined for type of u0027system.string u0027"
But eventually I found out the reason: the json string that was travelling from client to server was being deserialized by the .NET framework into the contained object, which wasn't a string, but an actual JSON object!
So the trick was the following, in my ajax method:
$.ajax({
type: "POST",
url: wsUrl + method,
**data: JSON.stringify(JSON.stringify(data)),**
contentType: "application/json; charset=utf-8",
dataType: "json",
processData: false,
success: function(msg)
{
try{
success(JSON.parse(msg.d));
} finally {
}
},
error: error
});
which means that the json object is encapsulated inside another string, which will be manually deserialized by my server-side GlobalMethod(string), through Json.NET.
Obviously I won't be including this double "stringify" in the ajax routine, but I'll pay attention to pass a JSON.stringified data in input to the ajax routine itself!

This is just an hack,
it's just an idea, doesn't sound elegant but it should work
I'm just curious
change the container in this way:
public class Container
{
public List<MyUnionBaseClass> Items;
}
and define (pseudocode):
public class MyUnionBaseClass{
public void MyUnionBaseClass(BaseClass b){
this.setValue(b);
};
final public BaseClass getValue(){
if(first!=null) return (BaseClass) first;
if(second!=null)return (BaseClass) second;
return null;
}
final public setValue(BaseClass b){
if(b instanceOf firstClass){
first = (FirstClass) b;
}
if(b instanceOf secondClass){
second = (SecondClass) b;
}
}
private FirstDerivedClass first=null;
private SecondDerivedClass second=null;
}
P.s: this is very rought and should be improved.
Does this make sense?

Change the webmethod to accept an object
[WebMethod]
public string GlobalMethod(object data)
This should resolve the issue you are having.

Related

ASP.NET MVC DataBinder not deserializing simple types from JSON

Input JSON:
{ "name": "gerry" }
Action method:
{ public ActionResult GenerateQrCode([FromBody] string name }
Problem:
The simple-type args are null
ModelState: Invalid
The built-in json deserializer can't handle the input in this form
I've tried:
ConfigureServices() -> services.AddControllersWithViews().AddNewtonsoftJson(); to switch to NewtonSoft, which I know/love
I've set a break-point into the non-NewtonSoft built-in MS SystemTextJsonInputFormatter.ctor() just to check, if it's still used: yes, it is, I'm not sure why, when I'm calling the above .AddNewtonsoftJson()
The situation:
The client POSTs all the input params as one JSON string document, which is UTF8 w/out BOM
The string comes in at the server-side and is nicely readable with new System.IO.StreamReader(Request.Body).ReadToEnd() from inside the immediate window
I need a way ASP.NET Core deserializes this, as it was able under the .NET4.X for many years w/out any issue
I wouldn't like to add [FromBody] and similar opt-in signatures all over the server actions/args
You pass the name as json but accept as a string so it will be null, you can use an InputFormatter like:
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
In startup.cs:
services
.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
And then you can get the row string
To deserilize it, you can check this, use Newtonsoft and make the string to a Model
[HttpPost]
public IActionResult GenerateQrCode([FromBody] string name)
{
object o = JsonConvert.DeserializeObject(name);
MyModel my = JsonConvert.DeserializeObject<MyModel>(o.ToString());
return View();
}

returning json object from controller to jsp

I want to send all the data from the DB as a json array to jsp to be fetched by ajax.
EmployeeController
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
#RequestMapping(value = "/index", method = RequestMethod.GET)
public #ResponseBody List<Employee> listAllUsers() {
return employeeService.listEmployeess();
}
and the jsp
ajaxCall = function() {
$.ajax({
url : 'EmployeeController',
type : 'GET',
dataType : 'json',
error : function(that, e) {
alert(e);
},
success : function(data) {
alert(data);
}
});
}
so how to make this?
By Default your REST Controller converts java objects into JSON object out-of-the Box. But you can also use #Produces("application/json") above the controller method.
Please try to run the ajax response data in a loop data[i], if it doesn't work then use the dot invocation method to reach the data.

MVC service that returns JSON for use with GET (JQuery ajax). Doesn't work

I have a WCF web service (.svc) like this:
Web Service
Interface
namespace Interfaces
{
[ServiceContract]
public interface IGeneral
{
[OperationContract]
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json)]
List<Person> GetPerson(long personID, DateTime startDate);
}
}
Class
[ScriptService]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class GeneralService : Interfaces.IGeneral
{
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = true)]
[WebMethod]
public List<Person> GetPerson(long personID, DateTime startDate)
{
List<Person> people= new List<Person>();
people.Add(new Person()
{
PersonID= 2,
StartDate= new DateTime(),
});
return people;
}
}
Person
namespace Models
{
[DataContract]
public class Person
{
[DataMember]
public long PersonID{ get; set; }
[DataMember]
public DateTime StartDate{ get; set; }
}
}
I'm expecting this to come back with Json but it just gives an error. I should be able to test this from the browser being a GET just like this (as you can see I donøt use the parameters in input so there should be no problem not passing them in):
http://localhost:2880/GeneralService/GetPerson
the way I call it is this:
Client Call
var request = {
personID: 3,
startDate: new Date(),
};
ajaxService = (function () {
var ajaxGetJson = function (method, request, callback, service) {
$.ajax({
url: getSvcUrl(service, method),
type: "GET",
data: request,
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (result, statusMsg, status)
{
callback(result, statusMsg, status, request)
},
error: ServiceFailed // When Service call fails
})
}
return {
ajaxGetJson: ajaxGetJson,
};
})();
ajaxService.ajaxGetJson("GetPerson", request, ModelMetricsMapper, "http://localhost:2880/GeneralService/");
UPDATE
I have a bit more info, it seems that it works if I change the class that I'm returning. I explain: if my class is just a simple class with primitives inside it does NOT work, BUT if I add a wrapper class around each primitive... it suddenly works.
It seems that the response Class needs to be 2 levels nested at least.
Here is the new class that works:
Works
[DataContract]
public class Person {
[DataMember]
public IdClass PersonID{ get; set; }
[DataMember]
public DateClass StartDate{ get; set; }
}
WHY?
I will answer this question with a way I found that works using javascriptSerializer.Serialize and retuning a Strem back to the client, but I would really like a way to do it returng a Person Object like Dave Ward says in this post: http://encosia.com/asp-net-web-services-mistake-manual-json-serialization/ but I just canøt make it work.
What am I doing wrong?
Thanks for any help.
It looks like adding the wrapper forces your data to come back as key value pairs rather than just values. Key value pairs are the format that JSON needs in order to read it properly.
I finally found what the problem was.
There were 2 DateTime fields on the response from the webservice that were not set. They were therefore trying to retunr their MinValue.
But how I learned from this post: http://benpowell.org/wcf-json-serialization-error-with-datetime-minval-and-utc/
that is not working.
so I just set the DateTime fields to Nullable and now everything works perfectly.
This is a WCF service returning Json using the GET method.
Hope this will help someone else.

How to accept JSON into MVC2 action method as parameter and have it deserialize automatically?

Background:
I have a MVC2 project and am using jQuery 1.4.2.
I have a .NET class that I put into a view using a JsonResult.
The page then uses jQuery to do stuff which will result in properties of the object put on the page by item (2) above.
When a certain page element is clicked jQuery $.post's the JSON.stringify(myObj) back to action method.
What I'd like to be able to have is something like:
[HttpPost]
public ViewResult MyAction(MyClass minime)
{
}
... MyClass would be the .NET class to deserialize the JSON into which should be fine given it was the type MyClass that got "return Json(MyClass);"
At the present time what I'm seeing is that either the object parameter is null or has no values as set by the JS/jQuery code. If I try:
MyClass foo = new MyClass();
UpdateModel(foo);
It doesn't throw an exception but similar doesn't populate the class either.
Anybody have any ideas on how to solve this? Or how to deal with JSON sent back to the action method and get it into a class.
If you have an Action
[HttpPost]
public JsonResult MyAction(MyClass minime)
where for example
public class MyClass {
public string Name { get; set; }
public int Age { get; set; }
}
you should fill all properties of the class MyClass as a parameters. The input data must not be in JSON format:
$.ajax({
url: '/MyController/MyAction',
data: { name: "Peter", age: 33 },
dataType: 'json',
traditional: true,
type: 'POST',
success: function (data) {
// get data which are already deserialized from JSON
}
});
If you have DateTime properties read JSON Date parameter passed to MVC Action is always null.

How can I customize the deserialization of JSON in ASP.NET MVC?

I have a controller action that is receiving a complex object via JSON. The object has a property on it that is declared as an abstract type. Currently, the action method never gets executed because when the JSON object is deserialized it chokes on the abstract type.
How can I customize the deserialization phase so that I can supply the correct concrete types to the deserializer?
public class ComplexType
{
public AbstractClass AbstractObject { get; set; }
}
public abstract class AbstractClass
{
}
public class ConcreteClass1 : AbstractClass
{
}
public class ConcreteClass2 : AbstractClass
{
}
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult MyAction(ComplexType complexObject)
{
// code ...
return Json(myResult);
}
Called with:
$.ajax({
url: myUrl,
data: JSON.stringify(instanceOfComplexType),
cache: false,
contentType: "application/json",
complete: function (data, textStatus) {
// handle response
},
dataType: "json",
type: "POST",
processData: false
});
In the end I wrote an ActionFilter that processes the incoming JSON using JSON.NET and a custom Converter. The custom Converter is smart enough to decide using the JSON data which of my derived classes it should instantiate.
An Recieving class has allways to have concrete implementations.
otherwise, the deserializer cannot instantiate the objects.
and there is no other out there that solves you that.
you have 2 possibilities.
either remove the abstract from the base class,
or make a implementation of the complexType that implements a concrete AbstractObject property (maybe through contravariance or generics)