[CLOSED] [#636] Difference between DirectMethod request params and Ajax request params

Page 1 of 3 123 LastLast
  1. #1

    [CLOSED] [#636] Difference between DirectMethod request params and Ajax request params

    Hi,

    I think Arrays are not being submitted in the same way for DirectMethod requests as they are for Ajax requests:

    Consider this (the URL that direct method or ajax request is going to doesn't matter - it can fail, it is the HTTP post that is significant)

    var params = {
      a: 1,
      b: [1, 2, 3]
    };
    Now, if I submit via direct method:

    Ext.net.DirectMethod.request({
      url: '/does/not/matter',
      cleanRequest: true,
      params: params
    })
    Look at the HTTP request, and you will see params are sent like this:

    application/x-www-form-urlencoded
    Parameters
    a 1
    b [1,2,3]
    Raw
    a=1&b=%5B1%2C2%2C3%5D


    Now if you run the same with Ajax:

    Ext.Ajax.request({
        url: '/does/not/matter',
        method:'post',
        params:params,
    });
    You get this:

    application/x-www-form-urlencoded
    Parameters
    a 1
    b 1
    b 2
    b 3
    Raw
    a=1&b=1&b=2&b=3

    This is quite significant for me in a certain scenario.

    Is there any way Direct Method can be configured to send params in the way that Ext.Ajax.request is doing when using params when some of the params (complex objects) contains arrays?

    Reason this is important for me is that it makes it consistent, especially for those coming in from a pure Ext JS background and also works better with ASP.NET MVC Controllers that use plain old C# objects as controller argument. In other words the type of the argument is a class that has many properties including things like List<int> in the type definition. ASP.NET MVC automatically binds such variables to these if they are sent, as seen by the Ext.Ajax.request approach. When I am using a DirectMethod like above it becomes null because MVC cannot deserialize.

    To clarify, if I have a C# object like this:

    public class MyClass
    {
        int a { get; set; }
        List<int> b { get; set; }
    }
    Then my Controller signature can take an argument of type MyClass as the parameter, but b will be null...
    Last edited by Daniil; Jan 17, 2015 at 10:43 AM. Reason: [CLOSED]
  2. #2
    Hi Anup,

    I will be investigating it tomorrow with a fresh head:)
  3. #3
    I was rushed last night when I wrote that post so didn't have a chance to create an example, but here is one to help reproduce:

    Test ASPX:
    <%@ Page Language="C#" %>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>DirectMethod with Arrays</title>
    </head>
    <body>
        <ext:ResourceManager runat="server" />
            
        <ext:Button ID="Button1" runat="server" Text="Call direct method">
            <Listeners>
                <Click Handler="
                    var params = {
                        a: 1,
                        b: [1, 2, 3]
                    };
    
                    Ext.net.DirectMethod.request({
                        url: '/ArraysInDirectMethod',
                        cleanRequest: true,
                        params: params,
                        success: function(result) {
                            Ext.Msg.alert('Result', 'This is what we got: ' + result);
                        }
                    });
                " />
            </Listeners>
        </ext:Button>
    </body>
    </html>
    Test controller:

        public class MyClass
        {
            [JsonProperty(PropertyName="a")]
            int A { get; set; }
    
            [JsonProperty(PropertyName = "b")]
            List<int> B { get; set; }
        }
    
        public class ArraysInDirectMethodController : Controller
        {
            public ActionResult Index(MyClass myClassExample)
            {
                return this.Direct(JSON.Serialize(myClassExample));
            }
        }

    When you click the button, the arguments will be passed through as shown in the first post in this thread. When the direct method finishes, it will returned a serialized version of what it thinks it received, which will be alerted to the user. It will show that the "b" property is null, rather than the array that it was given. The alert message will be something like:

    This is what we got: {"a":0,"b":null}



    Notes,
    • The result will be the same whether I use [DirectMethod] attribute on the controller or not
    • This is only observed with DirectMethods (and I am guessing DirectEvents)
    • When I use Ext.Ajax.request, or Store AJAX proxies etc, then this is not a problem.
      • This means if I have another controller (or maybe the same one) called that also takes the same object as one of its arguments, the values are correctly present.

    • Because of this inconsistency, this is perhaps quite a significant issue (at least for me).
      • For example, I reuse the same complex class for both AJAX Proxy load requests and and manually calling other DirectMethods to perform other operations on a bunch of data, but this inconsistency may mean I cannot reuse that code.

    • Last night I was looking at why Ext.Ajax.request worked, and it is something to do with using Ext.Object.toQueryString I think so that it creates the form post parameters in the correct way. Perhaps something similar needs to apply for DirectMethod/Event requests?


    I can understand that a potential fix may have compatibility issues for anyone relying on the current behaviour, so if there is a way to fix it with a flag (maybe at ResourceManager level or global level) to say what kind of serialization behaviour DirectMethod will have, perhaps that is a way out?

    Hope that helps?
    Attached Thumbnails alert-null-array.png  
  4. #4
    Thank you, Anup!

    A quick question for now - was is introduced in v3 or it is similar in v1 and v2?
  5. #5
    Hi,

    I think the problem exists in all versions - definitely 2.x as well as I copy pasted the code into a 2.x test project.

    I have a test 1.x project but it doesn't have MVC support (very old project!) and while I need to run a few manual steps to get the controller routes to work etc I can see the HTTP post contains the same type of array post as show in the original post, so I suspect the problem is there too.

    If your question was implying whether to fix in those versions too, I am not sure. Given it seems others have not been affected (perhaps) then maybe only fixing in 3.x is okay - of course, I am biased :)

    The reason I have not had this problem in 1.x even though I am using the same objects as I migrate my massive project from 1.x to 3.x is that in 1.x I used ASHX handlers and manually deserialized my post variables as needed. In 3.x I am moving wholesale to MVC/Web API controllers so this is why I have only noticed it now.

    Hope that helps.
  6. #6
    Ok, thanks.

    This is what we got: {"a":0,"b":null}
    Could you, please, clarify why "1" from params does not go to the "a" property? There is 0 which is a default for the int type. I guess MVC cannot handle this case automatically? I mean automatic serialization a params to a MyClass instance. Can we avoid using an IModelBinder in such a case with the MyClass? Here is some information about the IModelBinder interface.
    http://forums.ext.net/showthread.php...l=1#post105622

    Also I cannot reproduce the different between the Ext.Ajax.request and Ext.net.DirectMethod.request. Is my test case valid? I see the same behavior for both.

    - An array is being sent as an array [1,2,3] for both the requests
    - A MyClass instance doesn't get both a and b from the sent params.

    Am I missing something?

    View
    @{
        var X = Html.X();    
    }
    
    <!DOCTYPE html>
    <html>
    <head>
        <title>Ext.Net.MVC v3 Example</title>
    
        <script>
            var params = {
                a: 1,
                b: [1, 2, 3]
            };
    
            var callDirectMethod = function () {
                Ext.net.DirectMethod.request({
                    url: '/Razor/TestAction',
                    cleanRequest: true,
                    params: params,
                    success: function (result) {
                        Ext.Msg.alert('DirectMethod', 'This is what we got: ' + result);
                    }
                });
            };
    
            var callExtJsAjax = function () {
                Ext.Ajax.request({
                    url: '/Razor/TestAction',
                    method: 'post',
                    params: params,
                    success: function (r) {
                        Ext.Msg.alert('ExtJS result', 'This is what we got: ' + r.responseText);
                    }
                });
            };
        </script>
    </head>
    <body>
        @X.ResourceManager()
    
        @(X.Button()
            .Text("DirectMethod")
            .Handler("callDirectMethod")
        )
    
        @(X.Button()
            .Text("ExtJS AJAX")
            .Handler("callExtJsAjax")
        )
    </body>
    </html>
    Controller
    public class MyClass
    {
        [JsonProperty(PropertyName = "a")]
        int A { get; set; }
    
        [JsonProperty(PropertyName = "b")]
        List<int> B { get; set; }
    }
    
    public ActionResult Index()
    {
        return this.View();
    }
    
    public ActionResult TestAction(MyClass myClassExample)
    {
        return this.Direct(JSON.Serialize(myClassExample));
    }
  7. #7
    Hi,

    Regarding not seeing any difference between DirectMethod and Ext JS Ajax, for a moment I could not either. Then I realized the order of the two examples seems to matter.

    For example if you run this in Firebug console:

    var params = {
      a: 1,
      b: [1, 2, 3]
    };
    
    Ext.Ajax.request({
        url: '/does/not/matter',
        method:'post',
        params:params,
    });
    
    
    Ext.net.DirectMethod.request({
      url: '/does/not/matter',
      cleanRequest: true,
      params: params
    });
    You will see a difference.

    Now, if you
    a) Clear the console
    b) Swap the two requests around

    You will see both send in the way DirectMethod sends.

    It may be that reusing params in this contrived example is not good. I used two different test objects and it seems to be consistently different now:

    var params1 = {
      a: 1,
      b: [1, 2, 3]
    };
    
    var params2 = {
      a: 1,
      b: [1, 2, 3]
    };
    
    Ext.net.DirectMethod.request({
      url: '/does/not/matter',
      cleanRequest: true,
      params: params1
    });
    
    Ext.Ajax.request({
        url: '/does/not/matter',
        method:'post',
        params:params2,
    });
    In the above, Ext.Ajax.request consistently sends separate b parameters for each array item, where Ext.net.DirectMethod does not.

    As for other part - why is the response in my example showing a as 0 instead of 1 - good spot! I didn't notice that... And I am not sure why. In my real example with a much more complex object, I get the other simpler properties populated, just these arrays are not. I will double check that and get back to you.
  8. #8
    Ok, so I am not sure what happened with my earlier example (I think one issue was the properties were not public, which was a typing accident. But even then it didn't seem to work). Anyway, I worked backwards from my real complex object to cut it down further and here is another example, which I hope helps!

    <%@ Page Language="C#" %>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>DirectMethod with Arrays</title>
    </head>
    <body>
        <ext:ResourceManager runat="server" />
            
        <ext:Button ID="Button3" runat="server" Text="Call controller with Direct and Ext JS Ajax request methods">
            <Listeners>
                <Click Handler="
                    var paramsDirect = {
                        intProperty : 4,
                        stringProperty : 'blah',
                        arrayProperty : [116, 117]
                    }, paramsExt = {
                        intProperty : 4,
                        stringProperty : 'blah',
                        arrayProperty : [116, 117]
                    },
                    msgs = [];
    
                    Ext.net.DirectMethod.request({
                        url: '/ArraysInDirectMethod/Query',
                        cleanRequest: true,
                        params: paramsDirect,
                        success: function(result) {
                            showMsgs('DirectMethod: ' + JSON.stringify(result));
                        }
                    });
    
                    Ext.Ajax.request({
                        url: '/ArraysInDirectMethod/Query',
                        method: 'POST',
                        params: paramsExt,
                        success: function(response) {
                            showMsgs('Ext.Ajax ' + JSON.stringify(JSON.parse(response.responseText).result));
                        }
                    });
                    
                    function showMsgs(msg) {                
                        msgs.push(msg);
                        if (msgs.length === 2) {
                            Ext.Msg.alert('Result', msgs.join('<br>'));
                        }
                    }
                " />
            </Listeners>
        </ext:Button>
    </body>
    </html>
    In the above, the button press will first call a controller action with a Direct Method. Once that succeeds, it will then call the same controller action but via Ext.Ajax.request.

    Once both are completed, the combined responses will be shown so you can see the difference.

    The controller:

        public class Query
        {
            [JsonProperty(PropertyName = "intProperty")]
            public int IntProperty { get; set; }
    
            [JsonProperty(PropertyName = "stringProperty")]
            public string StringProperty { get; set; }
    
            [JsonProperty(PropertyName = "arrayProperty", NullValueHandling = NullValueHandling.Ignore)]
            public List<int> ArrayProperty { get; set; }
        }
    
        public class ArraysInDirectMethodController : Controller
        {
            [HttpPost]
            public ActionResult Query(Query query)
            {
                return this.Direct(query);
            }
        }
    What I see in HTTP post for Direct Method (array property is "url unencoded"):

    intProperty=4&stringProperty=blah&arrayProperty=[116,117]
    What I see in HTTP POST for Ajax Request

    intProperty=4&stringProperty=blah&arrayProperty=116&arrayProperty=117
    You can see the difference is that for an array the same property is sent as a duplicate key, which ASP.NET (and I think form posts in general?) understand.

    As the controller is just passing back what it got, this is what the alert message box shows:

    DirectMethod: {"intProperty":4,"stringProperty":"blah","arrayProperty":[]}
    Ext.Ajax {"intProperty":4,"stringProperty":"blah","arrayProperty":[116,117]}
    As you can see, for the DirectMethod, the array is lost (comes back empty), but is preserved for the Ext.Ajax request.

    I think Ext JS achieves this using Ext.Object.toQueryString somewhere in the request preparation. I wonder if that is what is missing from the DirectMethod request?

    Hope that helps.
  9. #9
    Thank you for the test case.

    It may be that reusing params in this contrived example is not good.
    Exactly. A DirectMethod's request call deals with the params object and changes it encoding the object type properties.

    In my example if call an ExtJS AJAX request first, then a DirectMethod, then there is the difference that you are talking about.

    The fact that a DirectMethod encodes object-type properties (including arrays) causes the difference.

    There is the following code in the Ext.net.DirectMethod.request method.
    if (options.params && options.json !== true) {
        for (var key in options.params) {
            if (options.params.hasOwnProperty(key)) {
                obj = options.params[key];
    
                if (obj === undefined) {
                    delete options.params[key];
                }
                else if (obj && typeof obj === "object") {
                    options.params[key] = Ext.encode(obj);
                }
            }
        }
    }
    1. Looking at the "options.json !== true" condition I came up with the first option to get your example working. Using a "json: true" option for a DirectMethod config helps. The params are not encoded and a request goes as a JSON request. That is OK for ASP.NET MVC. The array starts to be deserialized correctly. Are you OK with such an option?
    Ext.net.DirectMethod.request({
        json: true,
        ...
    });
    2. Thank you for a prompt with the Ext.Object.toQueryString. Indeed, Ext.Ajax.request uses it to prepare parameters. Well, we might need to use it also by default instead of Ext.encode (at least, for in MVC projects), but, I agree with you, it is probably a breaking change, which we don't want to introduce. We added a bool? Encode option in the revision #6249 (trunk). Here is a related Issue.
    https://github.com/extnet/Ext.NET/issues/636

    True to encode parameters using Ext.decode, false to use Ext.Object.toQueryString. Defaults to true.

    Please look at the example. Is that OK to you?

    Example

    <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
     
    <!DOCTYPE html>
     
    <html>
    <head runat="server">
        <title>Ext.Net.MVC v3 Example</title>
    
        <script>
            // A global setting
            // Ext.net.DirectEvent.encode = false;
    
            var paramsDirect = {
                    intProperty : 4,
                    stringProperty : 'blah',
                    arrayProperty : [116, 117]
                }, paramsExt = {
                    intProperty : 4,
                    stringProperty : 'blah',
                    arrayProperty : [116, 117]
                },
                msgs = [];
    
    
            var onClick = function() {
                Ext.net.DirectMethod.request({
                    url: '/Aspx/Query',
                    cleanRequest: true,
                    params: paramsDirect,
                    encode: false,
                    success: function(result) {
                        showMsgs('DirectMethod: ' + JSON.stringify(result));
                    }
                });
     
                Ext.Ajax.request({
                    url: '/Aspx/Query',
                    method: 'POST',
                    params: paramsExt,
                    success: function(response) {
                        showMsgs('Ext.Ajax ' + JSON.stringify(JSON.parse(response.responseText).result));
                    }
                });
            }
    
            var showMsgs = function showMsgs(msg) {                
                msgs.push(msg);
    
                if (msgs.length === 3) {
                    Ext.Msg.alert('Result', msgs.join('<br>'));
                }
            }
        </script>
    </head>
    <body>
        <ext:ResourceManager runat="server" />
             
        <ext:Button runat="server" Text="DirectMethod and Ext JS AJAX" Handler="onClick" />
    
        <ext:Button runat="server" Text="DirectEvent">
            <DirectEvents>
                <Click Action="Query" Success="showMsgs('DirectEvent: ' + JSON.stringify(result));" Encode="false">
                    <ExtraParams>
                        <ext:Parameter Name="intProperty" Value="4" Mode="Raw" />
                        <ext:Parameter Name="stringProperty" Value="blah" Mode="Value" />
                        <ext:Parameter Name="arrayProperty" Value="[116, 117]" Mode="Raw" />
                    </ExtraParams>
                </Click>
            </DirectEvents>
        </ext:Button>
    </body>
    </html>
  10. #10
    Thanks for the prompt reply.

    I will try to look at this as soon as I can (I have been distracted onto another task for a couple of days but will try to get back to this in between some waiting time!)

    To answer your json question in the meanwhile: I did think about doing that, but in my real example, my controllers have other parameters as well, and while I could change those signatures to combine the parameters into one parameter class (which may not be a bad idea more generally), it could be a bit of a large ripple effect in our app.

    That being said, having the option to influence the encoding is useful, and I see you have made a change to add encoding to the DirectEvent which DirectMethod will fall back to. So it looks like I could even change it globally (if I want to).

    I will try this out when I get a moment and let you know how it goes.

    Many thanks!
Page 1 of 3 123 LastLast

Similar Threads

  1. Replies: 2
    Last Post: Oct 10, 2014, 8:50 AM
  2. Replies: 13
    Last Post: May 16, 2011, 1:26 PM
  3. [CLOSED] Adding params as jsonData for jsin Ajax requests
    By r_honey in forum 1.x Legacy Premium Help
    Replies: 7
    Last Post: May 06, 2010, 7:31 AM
  4. Ext.net.DirectMethod.request PARAMS
    By sipo in forum 1.x Help
    Replies: 0
    Last Post: Apr 15, 2010, 5:06 AM
  5. Page_ Ajax Load Complete BUG on Ajax Request
    By jeybonnet in forum 1.x Help
    Replies: 8
    Last Post: Jun 22, 2009, 11:19 AM

Posting Permissions