Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Making jQuery calls to WCF/ASMX with a ServiceProxy Client


:P
On this page:

I’ve discussed creating a WCF or ASMX Service proxy for operation with jQuery and without using the Microsoft AJAX libraries. This was some time ago and some things have changed since then – most notably the introduction native JSON parsers in IE 8 and FireFox 3.5 as well as some changes in .NET 3.5 SP1 that makes some JSON transfer tasks a little bit easier in some situations. So I think it’s time to revisit this topic and update the code I showed in the past.

How the WCF ServiceProxy works

The idea of the service proxy is to make it as easy as a single function call to call a WCF or ASMX service. There no dependencies here other than jQuery.js. The goal is to initialize the proxy with a URL and then simply call .invoke() methods to call service methods on the service. The resulting client code using the proxy looks like the following.

Include a script reference into the page in the header:

 <script src="../scripts/serviceproxy.js" type="text/javascript"></script>     

There are two steps to calling services: Initializing the proxy which can be done globally at the top of the script code and then actually making the service method callbacks.

Start by adding the initialization code in a global location on your page or in a separate external .js file:

// *** Create a static service instance (serviceProxy.js)
// *** which includes ServiceProxy, JSON2, and JSON extensions 
var serviceUrl = "BasicWcfService.svc/";
var proxy = new ServiceProxy(serviceUrl);

Then you’re ready to make service calls. The following is the ever illustrious HelloWorld request called from a click button handler:

function showHelloWorld()
{
    proxy.invoke("HelloWorld",
         { name: $("#" + serverVars.txtHelloNameId).val() },
         function(result) {
             $("#divHelloWorldResult")
                .text(result)
                .slideUp("hide", function() { $(this).slideDown("slow") });
         },
         onPageError, false);
}

The individual .invoke calls then simply specify the method to call on the server, and an object map with parameter names as properties. So the HelloWorld method has a name parameter hence { name: "Rick" } is sent. If you had multiple parameters the map would have multiple properties: { parm1: "rick", parm2: 10, parm3: new Date() }. Parameter types can be of any type as long as they match in structure what the server’s method expects.

The third and fourth parameters are the success callback which receives the result value, and the failure callback which receives an exception like object. The success handler typically is used to assign the result value to the page somehow by displaying some notification or message, or updating some page content. Here an anonymous function is used inline to handle the success callback which receives the result of the service callback – in this case a string of an HTML fragment – as a parameter. The html is then promptly embedded into the page and made visible with a small effect.

Setting up a WCF Service on the Server

On the server side a WCF Service that implements the service methods using the WCF AJAX style WebScriptServiceHostFactory can be set up like this:

Go into web.config file and REMOVE all the System.ServiceModel entries created from the WCF service
Open the .SVC file created and add the Factory attribute to the page as shown below

<%@ServiceHost Language="C#"
      
Service
="WcfAjax.BasicWcfService"                
      
CodeBehind
="BasicWcfService.cs"
      
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
%>

Notice the Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" definition in the markup page – this is a shortcut for Microsoft AJAX style messaging that allows you to bypass all web.config configuration. In fact you can remove all WCF related entries from the config file if you like once you use the script factory. You can still use the web.config settings – they will override the defaults, but typically that is not required. If you want ASP.NET compatibility you might want to add one thing to the .config file:

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>

All that’s left to do then is to set up the service and service methods. For simplicity I’m just using a class that implements the contract. If you want to be anal and prefer setting up a separate ServiceContract interface for an internal service class that will never be reused be my guest – but for most application internal AJAX services that hardly a necessary practice.

Here’s is the service class:

    [ServiceContract(Namespace = "DevConnections")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
#if DEBUG 
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
#endif
    public class BasicWcfService
    {
        [OperationContract]
        public string HelloWorld(string name)
        {
            return "Hello " + name +". Time is: " + DateTime.Now.ToString();
        }

        [OperationContract]
        public StockQuote GetStockQuote(string symbol)
        {
            StockServer server = new StockServer();
            StockQuote quote;

            return server.GetStockQuote(symbol);
        }

        [OperationContract]
        public StockQuote[] GetStockQuotes(string[] symbols)
        {
            StockServer server = new StockServer();      
            return server.GetStockQuotes(symbols);
        }

        [OperationContract] 
        [WebGet]
        public Stream GetStockHistoryGraph(string stockSymbols,string title, int width, int height)
        {
            string[] symbols = stockSymbols.Split(',');
            
            StockServer server = new StockServer();
            byte[] img = server.GetStockHistoryGraph(symbols, title, width, height, 2);

            MemoryStream ms = new MemoryStream(img);
            return ms;
        }

        [OperationContract]
        public DateTime AddDays(DateTime date, int days)
        {
            return date.AddDays(days); 
        }

        [OperationContract]
        public string ThrowServerException()
        {
            throw new InvalidOperationException("User generated Error. This error has been purposefully created on the server");
            return "Gotcha!";
        }
    }

Nothing fancy here – just methods marked up with [OperationContract] attributes and a service class that implements the [ServiceContract] and you’re ready to go.

Calling the Service – Another example

For a slightly more involved example let’s call the GetStockQuotes method on the server which returns a result that looks like this:

StockList[4]

The result here is returned as an array of objects that contains stock quotes, which is then rendered into the page on the client via a ‘filling the holes’ template approach (there are other ways to accomplish this using templates). Here’s what this request looks like:

function getStockQuotes() {
    var symbols = $("#" + serverVars.txtSymbolsId ).val().split(",");
    proxy.invoke("GetStockQuotes",
         { symbols: symbols },  // pass symbol array as 'symbols' parameter
         function(quotes) {  // result is an array for each of the symbols                            
             var jCnt = $("#divStockDisplay").fadeIn("slow");
             var jDiv = $("#divStockContent").empty();

             // quotes is an array
             $.each(quotes, function(index) {
                 var jCtl = $("#StockItemTemplate").clone();
                 jCtl.attr("id", "stock_" + this.Symbol);
                 var symbol = this.Symbol;

                 jCtl.find(".itemstockname").text(this.Company);
                 jCtl.find("#tdLastPrice").text(this.LastPrice.formatNumber("n2"));
                 jCtl.find("#tdOpenPrice").text(this.OpenPrice.formatNumber("n2"));
                 jCtl.find("#tdNetChange").text(this.NetChange.formatNumber("n2"));
                 jCtl.find("#tdTradeDate").text(this.LastQuoteTimeString);
                 jCtl.fadeIn().click(function() { alert('clicked on: ' + symbol); });
                 jCtl.find(".hoverbutton").click(function(e) { alert("delete clicked on: " + symbol); e.stopPropagation(); });

                 jDiv.append(jCtl);
             });
         },
         onPageError);                          
}

The code takes an input string of comma delimited symbols, splits them into an array symbols. The symbols array is attached to the parameter map in the call to .invoke() as the input parameter to GetStockQuotes of the service method. The callback handler receives the array of quotes and creates a new list of stock item templates. The ‘template’ is really just an invisible HTML fragment:

<!-- 'Template' used to render each of the stock items -->
<div id="StockItemTemplate" class="itemtemplate" style="display:none">
    <div class="stockicon"></div>
    <div class="itemtools">
        <a href='javascript:{}' class="hoverbutton" ><img src="../images/remove.gif" /></a>
    </div>
    <div class="itemstockname"></div>
    <div class="itemdetail">
        <table cellpadding="3"><tr>
            <td>Price:</td>
            <td id="tdLastPrice" class="stockvaluecolumn"></td>
            <td>Open:</td>
            <td id="tdOpenPrice" class="stockvaluecolumn"></td>
            <td>Change:</td>
            <td id="tdNetChange" class="stockvaluecolumn"></td>                
            <td id="tdTradeDate" colspan="2"></td>
        </tr></table>
    </div>
</div>

and the code reads this fragment, clones it and then fills in the ‘holes’ with the retrieved data. Each individual StockItem is then added to the the list container (divStockContent) to display the list.

The data for all of this is using JSON and the stock array that is returned from the server looks like this:

{"d":[{"__type":"StockQuote:#StockPortfolio",
       "Company":"Microsoft Corpora",
       "LastPrice":25.20,
       "LastQuoteTime":"\/Date(1253055600000-0700)\/",
       "LastQuoteTimeString":"Sep 15, 4:00PM",
       "NetChange":0.20,
       "OpenPrice":24.95,
       "Symbol":"MSFT"},
      {"__type":"StockQuote:#StockPortfolio",
       "Company":"Intel Corporation",
       "LastPrice":19.55,
       "LastQuoteTime":"\/Date(1253055600000-0700)\/",
       "LastQuoteTimeString":"Sep 15, 4:00PM",
       "NetChange":0.19,
       "OpenPrice":19.51,
       "Symbol":"INTC"}
    ]
 }

Notice that this is Microsoft’s funky encoding format that includes a ‘wrapping’ type d that is consistent WCFs serialization formatting. The serviceProxy client however cleans this up. The data contains various types which are preserved on the client and also back up to the server if you post data back. And notice the funky date format which is also Microsoft specific ("\/Date(999123312)\/"). The serviceProxy properly deserializes these dates as well so result[0].LastQuoteTime will be a date. It also handles proper encoding of dates that are sent to the server so they appear as dates there.

The sample is part of the jQuery samples linked at the bottom of this post if you want to check it out a little closer and play with it for yourself.

Ok so much for the background…

ServiceProxy Implementation

I have previously written about the ServiceProxy implementation, but there have been a number of changes that had to be made to deal with recent changes in the browser world namely the addition of native JSON parsers. The old version I created relied on a customized version of Douglas CrockFord’s JSON2.js file. It was customized to handle the date encoding and decoding. JSON2.js is still required since not all browsers support native JSON parsing (only IE 8 and FF 3.5 currently do), but it’s fully compatible to the native JSON parser’s object model so it’s a perfect fit.

However with the advent of native JSON parsers the ‘customization’ of JSON2.js is no longer possible since the native implementations are fixed and immutable. They are also significantly faster so we definitely want to take advantage of this new feature. JSON parsers allow for a Replacer (stringify) and Reviver (parse) function that can be used to provide some post parsing formatting which allows hooking up the date processing both to the native parsers as well as the JSON2 parser with the same code.

I’ve included a ServiceProxy.js class with this post which provides all 3 items in a single file:

  • ServiceProxy class
  • JSON2
  • JSON2 Extensions

JSON2 is included in this package to have everything wrapped up in a single file for easier portability.  Alternately you can also grab ww.jquery.js which also includes all three of these components plus a host of jQuery plug-ins for typical AJAX use.

ServiceProxy Class

Let’s quickly look at the first and last components starting with the ServiceProxy. The service proxy is meant to make the service call easy. It relies on jQuery’s .ajax() function to make the actual callback. The proxy provides a number of useful features:

  • Single Method Service Calls
  • JSON encoding/decoding
  • Consistent Error Handling

The latter is an often overlooked part of using jQuery’s .ajax() function because it provides a  poor job of returning error information in a meaningful way. The proxy wraps errors into an object with a simple message regardless of whether you’re dealing with a transmission error, a client error or a service application fault.

Here’s the ServiceProxy class implementation:

this.ServiceProxy = function(serviceUrl) {
    /// <summary>
    /// Generic Service Proxy class that can be used to
    /// call JSON Services generically using jQuery
    /// depends on JSON2.js modified for MS Ajax usage
    /// </summary>
    /// <param name="serviceUrl" type="string">
    /// The Url of the service ready to accept the method name
    /// should contain trailing slash (or other URL separator ?,&)
    /// </param>
    /// <example>
    /// var proxy = new ServiceProxy("JsonStockService.svc/");
    /// proxy.invoke("GetStockQuote",{symbol:"msft"},
    ///              function(quote) { alert(result.LastPrice); },onPageError);
    ///</example>

    var _I = this;
    this.serviceUrl = serviceUrl;
    this.isWcf = true;

    this.invoke = function(method, params, callback, errorHandler, bare) {
        /// <summary>
        /// Calls a WCF/ASMX service and returns the result.
        /// </summary>    
        /// <param name="method" type="string">The method of the service to call</param>
        /// <param name="params" type="object">An object that represents the parameters to pass {symbol:"msft",years:2}       
        /// <param name="callback" type="function">Function called on success. 
        /// Receives a single parameter of the parsed result value</parm>
        /// <param name="errorCallback" type="function">Function called on failure. 
        /// Receives a single error object with Message property</parm>
        /// <param name="isBare" type="boolean">Set to true if response is not a WCF/ASMX style 'wrapped' object</parm>

        var json = _I.isWcf ? JSON.stringifyWcf(params) : JSON.stringify(params);

        // Service endpoint URL        
        var url = _I.serviceUrl + method;

        $.ajax({
            url: url,
            data: json,
            type: "POST",
            processData: false,
            contentType: "application/json",
            timeout: 10000,
            dataType: "text",  // not "json" we'll parse
            success: function(res) {
                if (!callback) return;

                // Use json library so we can fix up MS AJAX dates
                var result = JSON.parseWithDate(res);

                // Bare message IS result
                if (bare)
                { callback(result); return; }

                // Wrapped message contains top level object node
                // strip it off
                for (var property in result) {
                    callback(result[property]);
                    break;
                }
            },
            error: function(xhr, status) {                
                var err = null;
                if (xhr.readyState == 4) {
                    var res = xhr.responseText;
                    if (res && res.charAt(0) == '{')
                        var err = JSON.parseWithDate(res);
                    if (!err) {
                        if (xhr.status && xhr.status != 200)
                            err = new CallbackException(xhr.status + " " + xhr.statusText);
                        else
                            err = new CallbackException("Callback Error: " + status);
                        err.detail = res;
                    }
                }
                if (!err)
                    err = new CallbackException("Callback Error: " + status);

                if (errorHandler)
                    errorHandler(err, _I, xhr);

            }
        });
    }
}

The invoke method does all the work along with the success and error handlers of the Ajax functions that fix up the result. The .invoke() method starts by encoding the input object into JSON. Notice that for WCF there’s a special flag used since WCF does not (yet?) understand ISO style date strings as dates. So a special call the JSON.stringifyWcf is made to handle this formatting scenario.  If you’re calling an ASMX service set isWcf=false; as ASMX services can deal with ISO dates just fine.

Next the service Url is assigned. The service url points to the .SVC or .ASMX file plus the trailing slash. The method name is appended to this url which routes the method call property on the server to run the appropriate method. Next the ajax call is made. Notice that hte content type is set and all native JSON conversion is turned OFF. That’s right we need to handle the JSON conversion ourselves in order to convert dates back properly into date values via JSON.parseWithDates(). The .ajax callback handler also fixes up the response by stripping out the wrapper root object (d:) from the service response by default. If you are calling a raw REST service that doesn’t wrap results you can pass the isBare parameter as true on the invoke method. If there’s a callback handler mapped it is now called with the fixed up result.

The most complex part of this component is the error handling portion that has to check for the various ways that error information is returned by the $.ajax function. The first check is made for an object in the response text which means a server error was detected and should be displayed .THat’s the easy part – it’s much harder to figure out client errors, timeouts or ‘unknown’ errors and the rest of the code in the error handler deals with this. For you as the user though you always get an object back which in my opinion is how it should be.

JSON Extensions to handle Date Encoding/Decoding

The final piece are the JSON extensions to handle date encoding/decoding on requests. This is done by extending the JSON object created by native browsers or JSON2 and adding methods that use a Replacer or Reviver object in calls to stringify or parse. Here’s what this looks like:

if (this.JSON && !this.JSON.parseWithDate) {
    var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;
    var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
    // original var reMsAjax = /^\/Date\((d|-|.*)\)\/$/;

    JSON.parseWithDate = function(json) {
        /// <summary>
        /// parses a JSON string and turns ISO or MSAJAX date strings
        /// into native JS date objects
        /// </summary>    
        /// <param name="json" type="var">json with dates to parse</param>        
        /// </param>
        /// <returns type="value, array or object" />
        try {
            var res = JSON.parse(json,
            function(key, value) {
                if (typeof value === 'string') {
                    var a = reISO.exec(value);
                    if (a)
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
                    a = reMsAjax.exec(value);
                    if (a) {
                        var b = a[1].split(/[-+,.]/);
                        return new Date(b[0] ? +b[0] : 0 - +b[1]);
                    }
                }
                return value;
            });
            return res;
        } catch (e) {
            // orignal error thrown has no error message so rethrow with message
            throw new Error("JSON content could not be parsed");
            return null;
        }
    };
    JSON.stringifyWcf = function(json) {
        /// <summary>
        /// Wcf specific stringify that encodes dates in the
        /// a WCF compatible format ("/Date(9991231231)/")
        /// Note: this format works ONLY with WCF. 
        ///       ASMX can use ISO dates as of .NET 3.5 SP1
        /// </summary>
        /// <param name="key" type="var">property name</param>    
        /// <param name="value" type="var">value of the property</param>            
        return JSON.stringify(json, function(key, value) {
            if (typeof value == "string") {
                var a = reISO.exec(value);
                if (a) {
                    var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + ')/';
                    this[key] = val;
                    return val;
                }
            }
            return value;
        })
    };
}

The parse() function looks for a regEx match on string values that matches either an ISO or MS Ajax style string. If found that string is parsed into a date value and converted into a date using this Reviver function.

The stringifyWcf() function looks for strings that match the ISO signature. Unfortunatey JSON first turns the date into an ISO date THEN makes the Replacer function. This means the ISO string needs to be parsed into a string first then back into a date. Yuk. Further JSON quote()s the string AFTER it has been converted so it’s actually IMPOSSIBLE to create "\/Date(90232312)\/" style strings in the output. Natch.

By pure luck though I discovered that WCF also supports dates in the format of: "/Date(232133993)/" and that is exactly what the code above does. Lucked out on that one and I wouldn’t be surprised if this is actually an undocumented hack the WCF team added for just this scenario. If it wasn’t for this deviant behavior JSON extension wouldn’t be possible for the full MS Ajax style date (AFAIK) short of creating a custom JSON encoder and forfeiting the native JSON parsers completely.

Ultimately the best solution to this issue will be for WCF supporting ISO string date formats the same way the JavaScript serializer does.

And that’s it.

All of this looks pretty complicated but it’s really not – the whole thing is wrapped in a single ServiceProxy.js file so you can just include that in your project and off you go making service calls. You’ll also need jQuery loaded of course before this ServiceProxy.js since it depends on jQuery for the Ajax callbacks. If you’re using the West Wind West Wind Web Toolkit or ww.jquery.js this functionality is already baked into it as well so no need for a separate add of the ServiceProxy.js.

Why does this matter?

Now, if you’re using Microsoft ASP.NET AJAX with Web Forms and a ScriptManager you’re probably thinking: WTF? This is a big hassle. And yes, this is more work than using a ScriptReference. In fact if you already use ASP.NET AJAX in your application go ahead and use that by all means – it’s easier to use the premade proxy and doesn’t add any additional overhead.

However, more and more there are situations where ScriptManager is not available. MVC applications for example – there’s no ScriptManager and no script references and the closest thing in Microsoft Ajax is Sys.Net.WebServiceProxy.invoke  which is very similar in fact to this service proxy (but then one look at this API makes me ill :-}).  Or you may simply eschew use of ASP.NET AJAX in general and prefer using only jQuery (or some other library). No need to add ASP.NET AJAX into the mix if it’s just for Web Service calls.

Also keep in mind that there are alternatives to calling WCF/ASMX services. In MVC you might just use plain POST values to send data to the server and use a JsonResult() response. You can also rig something similar with any WebForms page that accepts POST. Or you can use the AjaxMethodCallback control in the West Wind Web Toolkit (links below) which allow page, control or HttpHandler callbacks a little more transparently.

However, personally I still prefer using a completely separate service or component (whether it’s WCF, West Wind Web Toolkit or an MVC Controller specific to Ajax Callbacks) rather than mixing UI and data calls into Page processing logic. But – you can do it either way.

Choices are good and this gives the you the freedom to decide how to handle calls. Even if you’re not using the ServiceProxy for WCF callbacks, I think you might find the $.ajax() wrapper code useful as this really applies to any kind of XHR call to the server – error handling and JSON parsing apply regardless of which technology you use on the server.

Resources

  • ServiceProxy.zip
    Package that contains ScriptProxy.js and ScriptProxy.min.js which contains ScriptProxy,JSON2, and the JSON extensions described above.
  • ww.jquery.js
    West Wind jQuery extensions also include all of the above functionality plus a bunch of other commonly used plugins and utility functions. It’s bigger (about 20k compressed) but includes a ton of functionality (docs).
  • Samples from jQuery Presentation
    The sample discussed is included in these samples as BasicWcfService.aspx. Includes the serviceProxy.js and ww.jquery.js files.
Posted in ASP.NET  jQuery  JavaScript  

The Voices of Reason


 

Tom
September 16, 2009

# re: Take Two: A jQuery WCF/ASMX ServiceProxy Client

Thanks for sharing Rick, great stuff! Just to make sure I understood the licence correctly, we can use the library in commerial products as long as we retain the copyright, correct?

Graham
September 16, 2009

# re: Take Two: A jQuery WCF/ASMX ServiceProxy Client

Dave Ward also has some nice ideas for this (http://encosia.com/2009/07/21/simplify-calling-asp-net-ajax-services-from-jquery/), I particularly like the way he deals with the "d" issue.

Rick Strahl
September 16, 2009

# re: Take Two: A jQuery WCF/ASMX ServiceProxy Client

@Graham - actually that may not work in all cases. The value may be something other than 'd'. You have to look for the first property in the in the wrapper object. ServiceProxy handles that as well with:

for (var property in result) {
    callback(result[property]);
    break;
}

Jakub
September 18, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Great post. Thanks Rick!

#.think.in
October 02, 2009

# #.think.in infoDose #43 (11th September - 22nd September)

#.think.in infoDose #43 (11th September - 22nd September)

thanigainathan
November 02, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Hi,

Very nice article. But including proxies ? does that makes heavy or not ?

Please explain me.

Thanks,
Thani

Landon C.
December 14, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Rick,

Thanks for all your great posts regarding WCF, jQuery, and JSON. I've been messing with using jQuery to call WCF services for a while now, and have encountered a strange problem that I'd like your insight on. I copied the example in this post, except my service only has these two methods:

        [OperationContract]
        public IEnumerable<ProcessInfo> GetProcessesAsType()
        {
            return
                  (
                     from p in Process.GetProcesses()
                     orderby p.WorkingSet64 descending
                     select new ProcessInfo
                     {
                         Name = p.ProcessName,
                         WorkingSet = p.WorkingSet64,
                         StartTime = p.StartTime
                     }
                  ).Take(10);
        }
 
        [OperationContract]
        public string GetProcessesAsString()
        {
            var processes = (
                from p in Process.GetProcesses()
                orderby p.WorkingSet64 descending
                select new ProcessInfo
                {
                    Name = p.ProcessName,
                    WorkingSet = p.WorkingSet64,
                    StartTime = p.StartTime
                }
                ).Take(10).ToList();
 
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string json = serializer.Serialize(processes);
            return json;
        }


ProcessInfo is a very simple class, like so:

    [DataContract]
    public class ProcessInfo
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public long WorkingSet { get; set; }
        [DataMember]
        public DateTime StartTime { get; set; }
    }


Here's the rub: both of these methods error out with "access denied" on Process.StartTime. However, the first method that returns the ProcessInfo type returns NO response at all, whereas the method that simply returns a string returns the complete, nicely detailed JSON error message from the server. Have you encountered this before? This seems like a big problem to me, since getting nothing at all back from the service if there's an error could be a real pain-in-the ass usability-wise. Regardless, I'd appreciate your insight.

Thanks,
Landon

Rick Strahl
December 15, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

I think WCF can't serialize IEnumerable<T> - use .ToList() or .ToArray() on the result instead.

Landon C.
December 15, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Wow, OK, good advice. I changed the return type from IEnumerable<T> to List<T> and now I get the nice JSON-encoded error back! Good get! However, it's still a little strange, because if there is NO server error, WCF can serialzie IEnumerable<T>. If I change the line causing the error from

StartTime = p.StartTime


to

StartTime = DateTime.Now


it serializes the IEnumerable<T> just fine. So, it seems like WCF can serialize IEnumerable<T>, provided there are no errors. Pretty strange, huh? Regardless, thanks for solving this for me.

By the way, serviceproxy.min.js uses a type CallbackException that isn't in the library. That type is in your ww.jquery.js library, but not in the standalone serviceproxy library. Could you add it in, please? :-)

Thanks again for the help! That was really perplexing me...

Rick Strahl
December 15, 2009

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

I can never remember what WCF can and can't serialize but you're always better off returning fully qualified types because that's what WCF expects for service contracts. This doesn't matter the same way with JSON (since there's no service contract) but many of those rules still apply.

For example, the JavaScript serializer can serialize object just fine - the DataContractJsonSerializer cannot.

Serialization errors cause weird return values to the client. The whole way that error reporting works with the WCF REST APIs is one of the reasons I don't use it if I can help it instead relying on ASMX or West Wind Web Toolkit Callback handlers instead.

The problem with WCF REST is that it's trying to squeeze REST into the mold of typical WCF services which is a pretty bad functionality/requirements mismatch so some of these issues are a result of that.

sky sanders
February 18, 2010

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Hey again,
I was struggling to get your json2.js mod to result in correct local times on the server. It turns out that adding '-0000' in the replacer of stringifyWcf does the trick.

    JSON.stringifyWcf = function(json) {
        /// <SUMMARY />
        /// Wcf specific stringify that encodes dates in the
        /// a WCF compatible format ("/Date(9991231231)/")
        /// Note: this format works ONLY with WCF. 
        ///       ASMX can use ISO dates as of .NET 3.5 SP1
        /// 
        /// <PARAM type="var" name="key" />property name
        /// <PARAM type="var" name="value" />value of the property         
        return JSON.stringify(json, function(key, value) {
            if (typeof value == "string") {
                var a = reISO.exec(value);
                if (a) {
                    var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + '-0000)/';
                    this[key] = val;
                    return val;
                }
            }
            return value;
        })
    };


http://skysanders.net/subtext/archive/2010/02/18/wcf-to-json-dates-and-back-again.aspx

Sky
February 20, 2010

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

something I noticed in my roundtripping tests is that stringifyWcf is actually setting the field of the object to the wcf date string.

It is my opinion that asking for a representation of an object in json should not include altering the state of the object. It smells funny to me. This is what I think would serve the wider audience with more expected results. Includes a lightly tested mod to fix dates.

What do you think?

    JSON.stringifyWcf = function(json) {
        /// <summary>
        /// Wcf specific stringify that encodes dates in the
        /// a WCF compatible format ("/Date(9991231231)/")
        /// Note: this format works ONLY with WCF. 
        ///       ASMX can use ISO dates as of .NET 3.5 SP1
        /// </summary>
        /// <param name="key" type="var">property name</param>
        /// <param name="value" type="var">value of the property</param>         
        return JSON.stringify(json, function(key, value) {
            if (typeof value == "string") {
                var a = reISO.exec(value);
                if (a) {
                    var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + '-0000)/';
                    //SKY: rick, I don't think that asking for a JSON representation
                    // of an object should modify the state of the object. my 2 pesos.
                    //this[key] = val;
                    return val;
                }
            }
            return value;
        })
    };

Jess Borrevik
May 19, 2010

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Hi .... I'm embarassingly stuck trying to call a WCF service using the ServiceProxy (or JQUERY proxy). The issue is my parameter always comes across as empty.

Here is the service:

[OperationContract]
List<SiteAddress> PremAddrFromRisers(IList<Riser> CandidateRisers);

Riser type:

[DataContract]
public class Riser
{
[DataMember]
public virtual string RiserOID { get; set; }
}

Here is the JS:

proxy.invoke("PremAddrFromRisers", { "Riser": { "RiserOID": "529901"} },
function(result) { AjaxSucceeded(result)}, onPageError, false);

I believe I have everything configured as described above. I'm able to set a breakpoint in the WCF service when the method is called and CandidateRisers is always empty.

... any thoughts MUCH appreciated.

Jess

ASP.NET Web Forms bloggers
September 22, 2010

# Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

The other day I got a question about how to call an ASP.NET ASMX Web Service or PageMethods with the

Girish
October 19, 2010

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

hi,

i am new to WCF and just doing my first project in it.

i got the error about undefined ServiceProxy.

i have one wcf service and through other web application in same solution i am trying to call the service. i added web refer. in my application and using these lines of code in js files

#####Javascript code######

var serviceUrl = "";
var proxy = new ServiceProxy(serviceUrl);

######################

but not sure what URL i need to put here.

RosaleenRosy
January 27, 2011

# Making jQuery calls to WCF/ASMX with a ServiceProxy Client

The post is written in very a good manner and it entails many useful information for me. I am happy to find your distinguished way of writing the post. Now you make it easy for me to understand and implement the concept. Thank you for the post.

Mads Frisk Sørensen
January 28, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Hey

I'm having some trouble passing my JSON object back to WCF when using arrays.

My JSON object looks like this:
{"request":{"Task":{"Id":"{96C62E03-1708-4BB0-B7A3-7C94B56B0A37}","Location":"{35EE05B0-C609-408B-AEDF-6F249248700C}","TEST":["Mads", "Tester"],"TaskOwnerName":"Mads Frisk Sørensen"}}}

After 'var json = JSON.stringifyWcf(params)'

The JSON object have been parsed to the string:
{"request":{"Task":{"Id":"{96C62E03-1708-4BB0-B7A3-7C94B56B0A37}","Location":"{35EE05B0-C609-408B-AEDF-6F249248700C}","TEST":"[\"Mads\", \"Tester\"]","TaskOwnerName":"Mads Frisk Sørensen"}}}

Where the array have been written as:
"TEST":"[\"Mads\", \"Tester\"]"

And WCF returns an error: ....
OperationFormatter encountered an invalid Message body. Expected to find an attribute with name 'type' and value 'object'. Found value 'array'. ...

If I replace it with:
"TEST":["Mads", "Tester"]

The call goes through and the array in .net is filled correctly.

Any ideas?

Bilal
February 22, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Hi Rick,

So can I say that ServiceProxy is best used for WCF/ASMX services with POST requests?

Thank you
Regards

Rick Strahl
February 22, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

@Bilal - yes ServiceProxy is meant specifically for MS AJAX style WCF and ASMX services. It's for taking input parameters and serializing to JSON and POSTing them to the server and expecting a JSON response back. If you just need to retrieve JSON data use jquery's native $.getJSON() or ajaxJson() in ww.jquery.js (the latter supports date conversions and POST optionally). If you need to post there's also $.postJSON() in ww.jquery.js.

@bhaidar
March 02, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Thanks Rick for your response!

Are you by any chance attending the MVP Global Summit 2011?

Regards

Rick Strahl
March 02, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

@Bilal, you're welcome. Nope not at the summit this year... too much work going on and too close after a long travel absence :-)

Tamas Hegedus
November 15, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Is there any way to translate all the error messages to other language??

Thanks your help is advance.

Rick Strahl
November 15, 2011

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

@Tamas - Which ones? The ones on the client side could be translated by creating a resource object and then loading different versions of these resource objects. On the server side you can localize using standard ASP.NET localization features.

James
February 27, 2013

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

Why do i get an onPageError not defined error?

my code:
var serviceurl = webServicePrefix + '/..../myservice.svc/';
var proxy = new ServiceProxy(serviceurl);

proxy.invoke("GetOAuthAuthentication",
'{"ConsumerKey": "' + LinkedInKey + '", "ConsumerSecret": "' + LinkedInSecret + '"}',
function (result) {
debugger;
},
onPageError);

Thank in advance

James

Rick Strahl
February 27, 2013

# re: Making jQuery calls to WCF/ASMX with a ServiceProxy Client

@James - The above is an example and onPageError is a generic function I use (in a custom library - ww.jquery.js) to handle errors by simply displaying the errors on a status bar. You can either implement the method or create an inline anonymous function:

proxy.invoke("GetOAuthAuthentication",
   '{"ConsumerKey": "' + LinkedInKey + '", "ConsumerSecret": "' + LinkedInSecret + '"}',
     function (result) {
         debugger;
     },
     function(error) { 
         alert("Error: " + error.message); 
      }
);

Juan
October 07, 2013

# jotason-proxy-writer

Json Proxy Writer, is a generator, writes a JavaScript proxy from a web service. Net with extension .asmx, take it as input and uses JSON to serialize and deserialize objects.

http://sourceforge.net/projects/jotason-proxy-writer/?source=directory


Features

•The application was developed with Visual Studio 2012 Express Edition
•The application was developed with C# lenguaje
•The application uses NVelocity templates
•The application uses JQuery library for invoke web services
•The web methods are invoked synchronically from the proxy JavaScript methods.
•The application uses JSON (JavaScript Object Notation) format to serialize and deserialize objects.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024