When I recently received this message from a frustrated reader:
After hours and hours of slamming my head into the desk it turns out it was the darn "d" in the response. My home computer is on .NET 2.0 and my work computer is on 3.5. Jimminie Christmas!
I realized that the “.d” introduced in ASP.NET AJAX 3.5’s JSON responses is still all too common a stumbling block when calling ASP.NET AJAX services through a library such as jQuery. In fact, with jQuery’s popularity among ASP.NET developers on the rise, this appears to have become an even more frequent problem.
Since a lot of people are having trouble with it, I want to share one method you can use to completely isolate your code from the problem. If you bake this into an $.ajax() code snippet or otherwise use it as a template for calling ASP.NET AJAX services in jQuery, you should never have to think or worry about the “.d” again.
In this post, I will show you how to detect the “.d” and how you can completely isolate your $.ajax success handler from it.
“.d” what?
If you aren’t familiar with the “.d” I’m referring to, it is simply a security feature that Microsoft added in ASP.NET 3.5’s version of ASP.NET AJAX. By encapsulating the JSON response within a parent object, the framework helps protect against a particularly nasty XSS vulnerability.
Before ASP.NET 3.5, “ScriptServices” and page methods returned their data at the top level of the JSON response, like this:
In ASP.NET 3.5 and later, the same server-side code returns this:
For more information about the change and why the change is a good one, be sure to see my earlier post: A breaking change between versions of ASP.NET AJAX.
However, what my previous post lacks is a solution for mitigating the inconsistency entirely. Using different client-side code against 2.0 and 3.5 based services is workable, but far from ideal. Wouldn’t it be nicer to not have to worry about it?
Determining whether or not the “.d” is there
In order to isolate ourselves from the “.d”, we first need a reliable way to test for its presence. Though JavaScript provides several methods for determining this, I suggest hasOwnProperty, as recommended by Douglas Crockford.
By using hasOwnProperty, your code is protected against unexpected changes to an object’s prototype chain. Though it is an unlikely problem to encounter, it’s always best to code defensively in JavaScript. The browser is a hostile environment!
Using hasOwnProperty to test for “.d”, you might end up with something like this:
$.ajax({ type: "POST", url: "WebService.asmx/MethodName", data: "{}", contentType: "application/json; charset=utf-8", dataType:"json", success: function(msg) { if (msg.hasOwnProperty("d")) // Leave the .d behind and pass the rest of // the JSON object forward. DoSomething(msg.d); else // No .d; no transformation necessary. DoSomething(msg); } }); function DoSomething(msg) { // Do something with the response data here. // Expect it to consistently have no .d. }
This code will perform identically against any version of ASP.NET AJAX.
Unfortunately, this might still get in your way. You may not always want to use the response in a call to another function, and you’ll have to remember the conditional every time you write a success handler.
Don’t make me think
I prefer a solution that doesn’t touch the success handler at all. Then, you’re free to integrate the “.d” handling into a generic $.ajax code snippet in Visual Studio and/or easily copy-paste it between files without modification.
Luckily, jQuery provides a mechanism that allows us to do just that: dataFilter.
The dataFilter parameter to $.ajax allows you to arbitrarily transform a response just before the success handler fires. Specifically tailored to this sort of situation, it passes response data into a callback function, captures the return value of that callback, and then passes the modified data into your success handler.
Hence, you can forever stop worrying about that pesky “.d” like this:
$.ajax({ type: "POST", url: "WebService.asmx/MethodName", data: "{}", contentType: "application/json; charset=utf-8", dataFilter: function(data) { // This boils the response string down // into a proper JavaScript Object(). var msg = eval('(' + data + ')'); // If the response has a ".d" top-level property, // return what's below that instead. if (msg.hasOwnProperty('d')) return msg.d; else return msg; }, success: function(msg) { // This will now output the same thing // across any current version of .NET. console.log(msg.foo); } });
Now, regardless which of these JSON forms the server returns:
// ASP.NET 2.0 with the ASP.NET AJAX Extensions installed. {'foo':'bar'} // ASP.NET 3.5 and 4.0. {'d':{'foo':'bar'}}
Your success handler will simply receive this consistent JSON object every time:
{'foo':'bar'}
dataType: none of your business
It’s important to note the removal of the dataType parameter in the $.ajax() code above. This is required in order to prevent a double-eval of service responses containing only a single string.
Internally, jQuery uses a combination of the dataType parameter and the implicit type the response. If the dataType is "json" and typeof(response) is “string”, then jQuery uses eval() to deserialize the response.
In the example above, manually deserializing the response in dataFilter results in it being of type Object, jQuery leaves it alone, and our dataFilter’d object makes its way back to the success callback either way.
However, if the dataType is set to “json” and the “.d” sanitized response happens to be of JavaScript type “string”, jQuery will assume that it is a JSON response from the server and still needs to be deserialized. That will throw an error at best.
The solution is to simply drop the dataType parameter from the $.ajax() call. It is only needed for purposes of instructing jQuery how to deserialize the response, and we’re handling that ourselves now.
Thanks to Brett for pointing this out.
Wait, isn’t eval() supposed to be evil?
If the eval() usage gives you pause, don’t worry. For now (as of jQuery 1.3.2), this is the same mechanism that jQuery uses to deserialize JSON too. Though eval() is potentially evil, it is still a necessary evil in many browsers.
In my next post, I’ll show you how to modify this to leverage a native browser implementation of JSON.parse instead of eval(), available in some newer browsers.
转载自:http://encosia.com/never-worry-about-asp-net-ajaxs-d-again/
请发表评论