PDA

View Full Version : [OPEN] [#1841] Problem with JSON serialization in direct method



lbrohan
Nov 10, 2020, 9:49 PM
Hello

if I understand correctly, the objects passed through the direct methods are (de)serialized using JSON.GlobalSettings setting.
I can change values in this setting in order to modify (de)serialization behavior for all direct methods

Is there a way how to modify behavior for one concrete direct method only?

Thanks

geoffrey.mcgill
Nov 11, 2020, 4:23 PM
I'm not sure I understand your requirements. Can you demonstrate the scenario? Current result and expected result.

lbrohan
Nov 11, 2020, 7:46 PM
Ok

here is bit wider example


<%@ Page Language="C#" %>

<%@ Import Namespace="System.Threading" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="Newtonsoft.Json" %>

<script>
var globalData = null;
</script>
<ext:ResourceManager runat="server"></ext:ResourceManager>
<script runat="server">

public class ParentClass
{
public int Id { get; set; }

public virtual string Test()
{
return "Hello from parent";
}
}

public class ChildClass : ParentClass
{
public string DataForClient { get; set; }
public override string Test()
{
return "Hello From Child";
}
}


[DirectMethod]
public static ParentClass[] LoadCollectionSimple()
{
return new ParentClass[]
{
new ParentClass
{
Id = 1
},
new ChildClass
{
Id = 2,
DataForClient = "xxx"
}
};
}

[DirectMethod]
public static ParentClass[] LoadGlobalData()
{

return new ParentClass[]
{
new ParentClass
{
Id = 1
},
new ChildClass
{
Id = 2,
DataForClient = "xxx"
}
};
}


[DirectMethod]
public static string ProcessGlobalData(ParentClass[] data)
{
return string.Join(", ", data.Select(item => item.Test()));

}

[DirectMethod]
public static void SetTypeNameHandlingAll()
{
JSON.GlobalSettings.TypeNameHandling = TypeNameHandling.All;

}
[DirectMethod]
public static void SetTypeNameHandlingNone()
{
JSON.GlobalSettings.TypeNameHandling = TypeNameHandling.None;

}
</script>

<ext:Container runat="server">
<Items>

<ext:Button runat="server" Text="Load Collection Simple">
<Listeners>
<Click Handler="
App.direct.LoadCollectionSimple({
success: function (result) {
alert(result[0].Id);
}
});" />
</Listeners>
</ext:Button>
<ext:Button runat="server" Text="Load Global Data">
<Listeners>
<Click Handler="
App.direct.LoadGlobalData({
success: function (result) {
globalData = result
}
});" />
</Listeners>
</ext:Button>
<ext:Button runat="server" Text="Process Global Data">
<Listeners>
<Click Handler="
App.direct.ProcessGlobalData(globalData,{
success: function (result) {
alert(result);
}
});" />
</Listeners>
</ext:Button>

<ext:Button runat="server" Text="Set TypenameHandling to All">
<Listeners>
<Click Handler="
App.direct.SetTypeNameHandlingAll({

});" />
</Listeners>
</ext:Button>

<ext:Button runat="server" Text="Set TypenameHandling to None">
<Listeners>
<Click Handler="
App.direct.SetTypeNameHandlingNone({

});" />
</Listeners>
</ext:Button>

</Items>
</ext:Container>




Now - I want first 3 buttons to work - i.e. I want the third button to display "Hello from parent, Hello from child"

it will do that only as long as the JSON.GlobalSettings.TypeNameHandling is set to All value
but if it is done, then first method will not work - as the right expression would be result.$values[0]


so basically I need to get TypeNameHandling applied to particular method only
(in real app I have 300+ methods like LoadCollectionSimple, and about 5-10 that require the tyep information to be passed)

fabricio.murta
Nov 12, 2020, 1:36 AM
Hello @Ibrohan! Thanks for the test case, I could reproduce the scenario.

You can use JSON.RequestSettings to set a given setting for the length of the request alone.

That said, if you set global TypeNameHandling to All then wherever you don't want it you can just add the code to the direct method's code behind:



JSON.RequestSettings = JSON.CopyCurrentSettings;
JSON.RequestSettings.TypeNameHandling = TypeNameHandling.None;


Notice it may not work in some situations where the serialization happens before it is set. I still have to further investigate the details but in your example, if I keep the initial default of TypeNameHandling.None and use the above code to set the specific requests to TypeNameHandling.All, it simply does not work, as if it ignored the setting.

So albeit the opposite works (set default to all, specify none where it shouldn't place the $values), it may also break depending how you use in your production. Besides, as per your description, you'd prefer to change the second method rather than the first. Again I have to get a bit deeper as to why it doesn't work as it would probably be preferable to you.

You may want to experiment this in your production project in the meanwhile. The difference between JSON.RequestSettings and JSON.GlobalSettings is just the life span of the former being just during current request, and the latter is maintained throughout the life of the web server worker process.

I'll get in-depth and will post a follow-up when we have more to share about this. Ultimately the solution should be with the RequestSettings handle.

fabricio.murta
Nov 13, 2020, 1:06 AM
Hello again, @Ibrohan!

The issue that happens if you have JSON.GlobalSettings.TypeNameHandling = TypeNameHandling.None is, ProcessGlobalData(ParentClass[] data) has already been deserialized with the GlobalSetting before you set the request-specific's TypeNameHandling behavior.

So all that you need to do is to re-submit the raw value to be deserialized into the parameter variable after the JSON.RequestSettings is overridden.

Then, if you want to keep the default global (and you should, as it can potentially break any internal Ext.NET client-server communication), TypeNameHandling.None, then you can ensure the two methods that require TypeNameHandling.All works changing them to something like this:



[DirectMethod]
public static ParentClass[] LoadGlobalData()
{
JSON.RequestSettings = JSON.CopyCurrentSettings;
JSON.RequestSettings.TypeNameHandling = TypeNameHandling.All;

return new ParentClass[]
{
new ParentClass
{
Id = 1
},
new ChildClass
{
Id = 2,
DataForClient = "xxx"
}
};
}

[DirectMethod]
public static string ProcessGlobalData(ParentClass[] data)
{
JSON.RequestSettings = JSON.CopyCurrentSettings;
JSON.RequestSettings.TypeNameHandling = TypeNameHandling.All;

var request = HttpContext.Current.Request;
string rawData = request.RequestType == "POST" ? request.Form["data"] : request["data"];
data = (ParentClass[])JSON.Deserialize(rawData, data.GetType());

return string.Join(", ", data.Select(item => item.Test()));
}


This is probably not very optimal, as data will just get deserialized twice in ProcessGlobalData. It should be okay if the methods you need to work like that are not time-critical in your application.

The parameter like this may still be interesting as you are able to use Ext.NET facilities to address the direct method and encapsulate the parameter.

In the case you don't want the double deserializations, you should then change the data argument to string, then JSON.stringify() it from client-side before passing to the direct method. Then the method would become something like:



[DirectMethod]
public static string ProcessGlobalData(string rawData)
{
JSON.RequestSettings = JSON.CopyCurrentSettings;
JSON.RequestSettings.TypeNameHandling = TypeNameHandling.All;

ParentClass[] data = (ParentClass[])JSON.Deserialize(rawData, data.GetType());

return string.Join(", ", data.Select(item => item.Test()));
}


This is unfortunately a limitation, as there's no telling one wants to change the JSON.RequestSettings within the direct method until it is called and, in order for it to be called, it needs the parameter already done. So in this case, one way or another, you have to re-deserialize the parameter after the desired setting is determined.

Hope this helps!

lbrohan
Nov 13, 2020, 9:03 AM
Ok, thanks for tps, feel free to close this thread

Just a feature request - would it be possible to add TypeNameHandling to the DirectMethod attribute in future?

I will go with strings as you recomended above, I guess to parse string data I should use JSON.parse in browser

Thanks

fabricio.murta
Nov 13, 2020, 5:25 PM
Just a feature request - would it be possible to add TypeNameHandling to the DirectMethod attribute in future?

Feature request logged as issue #1841 (https://github.com/extnet/Ext.NET/issues/1841). And yes, this seems pretty possible as far as our preliminary investigation went, unless there are limitations in specifying the object as an argument to the attribute.


I will go with strings as you recommended above, I guess to parse string data I should use JSON.parse in browser

I believe you won't need JSON.parse(), as you would still be able to return the non-string type from the direct method. You would only need to stringify the object before passing it back to the server.

We will post an update here as soon as the feature is implemented into Ext.NET.