Blog Home  Home Feed your aggregator (RSS 2.0)  
ASP.NET AJAX Extensions Internals - Web Service Proxy Generation - Manuel Abadia's ASP.NET stuff
# Saturday, March 17, 2007

In my last post about MS AJAX I gave a brief explanation about the web.config changes needed and explained the serialization process.

Now, I'm going to explain how a client proxy is generated. As I told in the last post, the WebServiceClientProxyGenerator.GetClientProxyScript method was were all the job was done.

There are a few classes involved in the client proxy generation:

  • WebServiceTypeData, that is used to store type information about the web service to call.
  • WebServiceMethodData, that is used to store information about a web service method (parameters, return type, caching, etc), and it has the functionality to invoke the method.
  • WebServiceParameterData, used to store information about a parameter of a web service method.
  • WebServiceData. This class uses the previous classes and has a lot of logic. Basically a WebServiceData instance is created for each web service to call (for each instance there is an associated WebServiceTypeData instance). When the web service methods are required, the EnsureMethods method uses reflection to obtain all WebMethods from the type (and its ancestors).

By default, a web service can't be called from client script. To change this, the web service needs the ScriptServiceAttribute applied to it.

To be able to call a web method from client script, the parameters and return types of the web method have to be available in client script, and the web service needs to have the WebMethodAttribute (the ScriptMethodAttribute should be used only to override the default invocation method and respone format, that is HTTP POST and JSON). If a web service method needs a parameter of a custom type, we have to use the GenerateScriptTypeAttribute attribute in the method or in the web service so MS AJAX will automatically generate a client side class for that type.

So when the methods are processed, the ClientTypes property will be filled in the ProcessClientTypes method with a list of the types that need to be available in client script.

However, the automatic type generation has some limitations, as the GenerateScriptTypeAttribute can not be applied to types implementing IEnumerable or IDictionary, interfaces, abstract classes, types without a public parameterless constructor, and generic types with more than one parameter.

The WebServiceData class also inherits from the JavaScriptTypeResolver class to provide string to type conversion (and viceversa) for the serialization/deserialization process. You may be wondering why the SimpleTypeResolver isn't used, but as the GenerateScriptTypeAttribute has a property called ScriptTypeId that modifies the __type field stored in the JSON, another type resolver was needed.

As you can see, the WebServiceData class does a lot of work, so to get WebServiceData instances the static method GetWebServiceData is used because it caches instances by URL.

If you don't want to create a new class for a few methods, you can add them to your ASPX page and call them directly. For this to work, the methods should be static and have the WebMethodAttribute attribute applied.

The ClientProxyGenerator class uses a WebServiceData instance and generates the client proxy code, although the code calls to the static method GetClientProxyScript of the WebServiceClientProxyGenerator class to get the generated client proxy code, because it performs caching based on the web service type and the modified time of the associated assembly.

For the following web service:

[WebService(Namespace = "")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]


public class AjaxWebService {





    public User GetModifiedUser(User usr) {

        usr.ID = usr.ID * 2;

        usr.Name = "Manu";


        return usr;



the User and Address classes were introduced in the previous post.

the generated proxy is:

var AjaxWebService=function() {


    this._timeout = 0;

    this._userContext = null;

    this._succeeded = null;

    this._failed = null;




    GetModifiedUser:function(usr,succeededCallback, failedCallback, userContext) {

        return this._invoke(AjaxWebService.get_path(), 'GetModifiedUser',false,{








AjaxWebService._staticInstance = new AjaxWebService();

AjaxWebService.set_path = function(value) {

    var e = Function._validateParams(arguments, [{

        name: 'path', type: String


    ]); if (e) throw e; AjaxWebService._staticInstance._path = value;



AjaxWebService.get_path = function() {

    return AjaxWebService._staticInstance._path;



AjaxWebService.set_timeout = function(value) {

    var e = Function._validateParams(arguments, [{

        name: 'timeout', type: Number


    ]); if (e) throw e; if (value < 0) {

        throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout);


    AjaxWebService._staticInstance._timeout = value;



AjaxWebService.get_timeout = function() {

    return AjaxWebService._staticInstance._timeout;



AjaxWebService.set_defaultUserContext = function(value) {

    AjaxWebService._staticInstance._userContext = value;



AjaxWebService.get_defaultUserContext = function() {

    return AjaxWebService._staticInstance._userContext;



AjaxWebService.set_defaultSucceededCallback = function(value) {

    var e = Function._validateParams(arguments, [{

        name: 'defaultSucceededCallback', type: Function


    ]); if (e) throw e; AjaxWebService._staticInstance._succeeded = value;



AjaxWebService.get_defaultSucceededCallback = function() {

    return AjaxWebService._staticInstance._succeeded;



AjaxWebService.set_defaultFailedCallback = function(value) {

    var e = Function._validateParams(arguments, [{

        name: 'defaultFailedCallback', type: Function


    ]); if (e) throw e; AjaxWebService._staticInstance._failed = value;



AjaxWebService.get_defaultFailedCallback = function() {

    return AjaxWebService._staticInstance._failed;




AjaxWebService.GetModifiedUser= function(usr,onSuccess,onFailed,userContext) {




var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;


if (typeof(Address) === 'undefined') {

    var Address=gtc("Address");




if (typeof(User) === 'undefined') {

    var User=gtc("User");




The generated proxy class is called like the web service, and inherits from the Sys.Net.WebServiceProxy class, which has the functionality to invoke a web service. The class have some properties set in order call the specific web service that it is proxying, and the web methods exposed (GetModifiedUser in our case).

After the class to call the web service, the User and Address classes are created and registered to be used by client code (thanks to the GenerateScriptTypeAttribute). However, no property of the original classes is generated in client script. The core of this client classes is the call to the Sys.Net.WebServiceProxy._generateTypedConstructor method. This method creates a new class that accepts an object as a parameter for the constructor. The constructor of the class will iterate through the members of the object passed as the parameter and copy them to the current instance.

So you can create an instance of the Address class that has 2 fields (propertyOne and propertyTwo) like this:

new Address({"firstField":"firstValue","secondField":"secondValue"}).


and accessing to Address.firstField will return "firstValue".

So if you want to call the previous web service from client script you can do something like this:


function callWebService()


            var user = new User();

            user.ID = 1;

            user.Name = "Manuel";

            user.SurName = "Abadia";

            user.CreationDate = new Date();

            user.Address = new Address();

            user.Address.FirstLine = "C/ Torre Alvarez";

            user.Address.SecondLine = "Murcia, 2007";

            user.Address.Country = "Spain";

            AjaxWebService.GetModifiedUser(user, OnSucceeded, OnError);



        function OnSucceeded(result)


            var RsltElem = document.getElementById("Results");

            RsltElem.innerHTML = result.Name + "[" + result.ID + "]";



        function OnError(message)


           alert(message.get_message() + " " + message.get_stackTrace());



when the User instance is created it contains no members, but you keep adding them in each assignment (this is how JavaScript works). You can even add nonexisting members like user.Foo = "This property doesn't exists in server code" and they will be ignored on the server side in the deserialization process.

When the GetModifiedUser method is called from client script, it generates an asynchronous HTTP request to the web service (http://localhost/AJAXServerSide1/AjaxWebService.asmx/GetModifiedUser in my case) with the following posted data:

{"usr":{"__type":"User","ID":1,"Name":"Manuel","SurName":"Abadia","CreationDate":"\/Date(1174133124369)\/","Address":{"__type":"Address","FirstLine":"C/ Torre Alvarez","SecondLine":"Murcia, 2007","Country":"Spain"}}}


and the server generates a response with the following data:



{"__type":"User","ID":2,"Name":"Manu","SurName":"Abadia","Address":{"__type":"Address","FirstLine":"C/ Torre Alvarez","SecondLine":"Murcia, 2007","Country":"Spain"},"CreationDate":"\/Date(1174133124369)\/"}

that is passed to directly the User constructor in order to generate the returned value from the web service.

In my next post about MS AJAX I'll sumarize what we have learned and how it applies to the ScriptHandlerFactory, the application web services, and then continue with the next HttpHandler, the ScriptResourceHandler.

Saturday, March 17, 2007 2:09:19 PM (Romance Standard Time, UTC+01:00)  #    Comments [4]   Ajax | ASP.NET | JavaScript  | 
Copyright © 2019 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.