Rajeeshcv.com

Sharing my knowledge

ASP.NET AJAX - Passing JSON object from client to server

  1. Part 1- Calling server side methods from client side

Read the Part 1 of this tutorial if you don't know how to call server side methods directly from client side.

In this post, I will explain how to pass data from client to server in JSON format.

Microsoft AJAX client library has built-in methods which helps in serializing and de-serializing JSON. The methods are defined in "Sys.Serialization.JavaScriptSerializer" class. In server side we have "JavaScriptSerializer"that will help you out to do the JSON serialization and de-serialization.

With the help of these two classes, we can easily do the data transfer. Suppose you have class called "Customer" in server side, which looks like this

 
using System;

namespace ServerClientInteration
{
    [Serializable]
    public class Customer
    {        
        public int ID { get; set; }

        public string Name { get; set; }

        public string Address { get; set; }
    }
}

This is our payload for this example, we are going to move this object from server to client and vice versa. For that I have exposed two static methods on the server side; one is for retrieving the customer details and the other is for updating the customer.

 
  public partial class _Default : System.Web.UI.Page
    {
        private static Customer customer = null;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (customer == null)
            {
                customer = new Customer();
                customer.ID = 1;
                customer.Name = "microsoft";
                customer.Address = "www.microsoft.com";
            }
            
        }

        [WebMethod]
        [ScriptMethod(UseHttpGet=true)]
        public static string GetTime()
        {
            return DateTime.Now.ToLongTimeString();
        }

        [WebMethod]
        [ScriptMethod(UseHttpGet = true)]
        public static string GetCustomer()
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(customer);
        }

        [WebMethod]
        [ScriptMethod]
        public static string UpdateCustomer(string jsonCustomer)
        {
            try
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                Customer cust = serializer.Deserialize(jsonCustomer);
                customer.ID = cust.ID;
                customer.Name = cust.Name;
                customer.Address = cust.Address;

                return "Updated";
            }
            catch(Exception Exception)
            {
                
            }

            return "Error occured while updating";
        }
    }

In the above code, you can see "GetCustomer" and "UpdateCustomer" methods. "GetCustomer" will serialize the static customer object and return it as JSON string; "UpdateCustomer" method accepts JSON string as input and that string is de-serialized in to a customer object and the static customer variable is updated with the new values. "UpdateCustomer" will return status message after the update.

Usually, we persist the customer in DB or some other states, but here for simplicity I have used a static variable which will simulate the persistence.

Now we need to call this from the client-side, for that I have two hyperlinks; one will get the customer from the server and other send the customer to the server.

Here is how the HTML looks like

 
<head runat="server"> 
<title>Untitled Page</title> 
<style type="text/css"> 
 
#result 
{ 
width: 250px; 
height: 300px; 
overflow: auto; 
border:1px solid #00d; 
} 
 
</style> 
</head> 
<body> 
<form id="form1" runat="server"> 
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true"> 
</asp:ScriptManager> 
<div> 
<a href="javascript:callServerMethod()" >Call Server method</a> 
<hr /> 
<a href="javascript:getCustomer()" >Get Customer</a> 
<div id="result"></div> 
<hr /> 
<div> 
ID : <input type="text" id="custID" /><br /> 
Name : <input type="text" id="custName" /><br /> 
Adress : <input type="text" id="custAdd" /><br /> 
</div> 
<a href="javascript:updateCustomer()" >Update customer</a><br /> 
 
</div> 
<script language="javascript" type="text/javascript"> 
//<![CDATA[ 
 
function callServerMethod() 
{ 
PageMethods.GetTime(callBackMethod); 
} 
 
function callBackMethod(result) 
{ 
alert(result); 
} 
 
function getCustomer() 
{ 
PageMethods.GetCustomer(callBackGetCustomer); 
} 
 
function callBackGetCustomer(result) 
{ 
var customer = Sys.Serialization.JavaScriptSerializer.deserialize(result); 
var content = "<br />ID :" +  customer.ID + "<br />" + "Name : " + customer.Name + "<br />" + "Address : " + customer.Address; 
$get("result").innerHTML += content; 
} 
 
var clientCustomer = function(id,name,add){ 
this.ID = id; 
this.Name = name; 
this.Address = add; 
}; 
 
 
function updateCustomer() 
{ 
var id = $get("custID").value; 
var name = $get("custName").value; 
var address = $get("custAdd").value; 
 
var customer = new clientCustomer(id,name,address); 
var serializedCustomer = Sys.Serialization.JavaScriptSerializer.serialize(customer); 
PageMethods.UpdateCustomer(serializedCustomer,callBackUpdateCustomer); 
} 
 
function callBackUpdateCustomer(result) 
{ 
alert(result); 
} 
 
 
//]]> 
</script> 
</form> 
</body>

I think the above code is self explanatory, let me know if you have any difficulty in understanding this.

Hope this will gave some idea about passing JSON between client and server. I have uploaded the source code in my server and you can get it from here - http://www.rajeeshcv.com/download/ServerClientInteration.zip


ASP.NET AJAX - Calling server side methods from client side

ASP.net AJAX has got nice bridging between your server side and client side technologies. Using the ASP.net AJAX you can easily call a method in a page. The restriction here is that, the methods your are trying to call should be static and public.

This feature(calling server side static method) is not enabled by default, so you have to manually enable this and it is very very simple; just set the "EnablePageMethods" property of the "ScriptManager" to true like this

 

 

 

 

<asp:scriptmanager id="ScriptManager1" runat="server" enablepagemethods="true">      </asp:scriptmanager>

After this, create a public static method in your asp.net page and decorate it with WebMethod(System.Web.Services.dll) and ScriptMethod(System.Web.Extensions.dll) attributes. For example I have created a static method called "GetTime()" which returns current time as string.

	
public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        [WebMethod]
        [ScriptMethod]
        public static string GetTime()
        {
            return DateTime.Now.ToLongTimeString();
        }
    }  

Now you have configured everything correctly and I will show you how to call this method from the client side.

Earlier you have set the "EnablePageMethods" property of "ScriptManager" to true, as a result of that a new object called "PageMethods" is created by the ScriptManager when the page is rendered. This object has all the public static methods in that page. So that you call the server side method like PageMethods.[ServerSideMethodName]. When calling this methods from client side you have to provide a callback function because here all the call scriptmanager made will be asynchronous, and this callback method will get the returned result.

Below is the sample code

	
	function callServerMethod()
        {
            PageMethods.GetTime(callBackMethod);
        }
        
        function callBackMethod(result)
        {
            alert(result);
        }

Complete aspx page looks like this

	
<body>     <form id="form1" runat="server">       <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">      </asp:ScriptManager>      <div>          <a href="javascript:callServerMethod()" >Call Server method</a>      </div>      <script language="javascript" type="text/javascript">      //<![CDATA[          function callServerMethod()          {              PageMethods.GetTime(callBackMethod);          }          function callBackMethod(result)          {              alert(result);          }      //]]>      </script>      </form>
</body>

Extra bits

In the above code you can see that I have placed link, when user click on that link a message is shown with the server time. So behind an asynchronous request is send to the server in this format (http://[sitename]/[MethodName]). Server in turn returns the result as JSON back to the client. Here is a screen shot from the firebug

image

image

There is optimization we have do here - you can see that, by default the request is a POST request, but in our case a POST request is heavy because we could have achieved the same using a GET request ( Difference between POST and GET ).

In order to change this request in to a GET request, we need to do a minor tweaking in the server side, set "UseHttpGet=true" parameter for the "ScriptMethod" attribute.

 
	[WebMethod]
        [ScriptMethod(UseHttpGet=true)]
        public static string GetTime()
        {
            return DateTime.Now.ToLongTimeString();
        }

After this you can see that, request is changed to a GET request

image


Attach to Any ASP.NET Web Server from Visual Studio in One Click

This is an update to my previous blog post Attach to Visual Studio Development Server with One Click.

The Visual Studio Macro from previous article doesn’t support IISExpress or IIS; it only supported the Visual Studio Development Server, more over it doesn’t detect latest Development Web Server “WebDev.WebServer40.exe”.

Now I have updated the Macro so that it will automatically detect the Web Server setting from the project properties and attach it accordingly.

Below is the code for the new Macro, I think it is self-explanatory

Public Sub AttachToWebServer()
    Dim attached As Boolean = False
    Dim project As EnvDTE.Project = GetStartupProject()

    If (project Is Nothing) Then
        MsgBox("Couldn't find a web project that can be attached")
        Return
    End If

    attached = AttachToProcess(project)

    If (Not attached) Then
        MsgBox("Couldn't attach to the process")
    End If
End Sub

Private Function GetStartupProject() As EnvDTE.Project
    Dim startUpProject As String = DTE.Solution.Properties.Item("StartupProject").Value

    For Each currentProject As EnvDTE.Project In DTE.Solution.Projects
        If currentProject.Name = startUpProject Then
            Return currentProject
        End If
    Next
    Return Nothing
End Function

Private Function AttachToProcess(ByVal project As EnvDTE.Project) As Boolean
    Dim serverProcessNamePattern As String
    ' Using either IIS express or IIS
    If project.Properties.Item("WebApplication.UseIIS").Value = "True" Then
        ' Using IIS Express
        If project.Properties.Item("WebApplication.DevelopmentServerCommandLine").Value.ToString().Length > 0 Then
            serverProcessNamePattern = ".*iisexpress.exe"
        Else ' Real IIS
            serverProcessNamePattern = ".*w3wp.exe"
        End If

    Else ' Assume development web server
        serverProcessNamePattern = ".*WebDev.WebServer\d+.EXE"
    End If

    Return AttachToWebServer(serverProcessNamePattern)

End Function

Private Function AttachToWebServer(ByVal serverProcessNamePattern As String) As Boolean

    Dim attached As Boolean = False

    For Each process In DTE.Debugger.LocalProcesses
        If (Regex.IsMatch(process.Name, serverProcessNamePattern)) Then
            process.Attach()
            attached = True
            Exit For
        End If
    Next

    Return attached
End Function

Read my previous article Attach to Visual Studio Development Server with One Click to understand how we can add this Macro as command to the Visual Studio toolbar.


Stay away from Request.Url

The title might be misleading but I will explain why we shouldn’t use the Request.Url in any asp.net application directly.

If you are writing an web application and you don’t where it is going to be deployed, environment of the server where it is getting deployed, then it is better not to use Request.Url it directly.

image

This blog is running on a home grown blog engine, which is written using asp.net MVC 3. For implementing some of the functionalities like generating sitemap.xml I had to get the root of the url(i.e. without any path). So I used the Request.Url.GetLeftPart(UriPartial.Authority) method and it worked perfectly on my local machine even when it is deployed my local machine IIS server. When I deployed my blog engine to Appharbor environment, the generated sitemap.xml has a URL with port number like http://www.rajeeshcv.com:4566 instead of just http://www.rajeeshcv.com.

Why?

After googling for some time I came across the solution posted appharbor.com knowledge base - workaround for generating absolute urls without port number. In their environment there are load balancers which sits in front on the webserver, which uses a specific port number for contacting the server where the application is running. Below is a simple pictorial representation of that

image

So whenever we try to get Request.Url, application will get the actual request Url received by that webserver, which will have the port number also.

How to solve it

Appharbor has provided a code snippet in the same article but it didn’t worked for me, I was still getting port number in the generated Url. My assumption is that, there is condition check for ignoring the local request so that it will work correctly in the local development environment.

if (httpContext.Request.IsLocal)
{
    uriBuilder.Port = httpContext.Request.Url.Port;
}

According to the MSDN documentation

The IsLocal property returns true if the IP address of the request originator is 127.0.0.1 or if the IP address of the request is the same as the server's IP address.

In my case I think IsLocal was true (I really don’t the exact reason!!!). So instead of using the appharbor code snippet I came across a code from FunnelWeb which does the same (HttpRequestExtensions.cs),

Here is my version of that code

/// <summary>
/// Environments the specific request URL.        
/// </summary>
/// <param name="request">The request.</param>
/// <returns></returns>
/// <remarks>
/// Code from FunnelWeb:  https://bitbucket.org/TheBlueSky/funnelweb/src/b64c74f361d3/src/FunnelWeb/Utilities/HttpRequestExtensions.cs
/// </remarks>
public static Uri EnvironmentSpecificRequestUrl(this HttpRequestBase request)
{
   
    UriBuilder hostUrl = new UriBuilder();
    string hostHeader = request.Headers["Host"];

    if (hostHeader.Contains(":"))
    {
        hostUrl.Host = hostHeader.Split(':')[0];
        hostUrl.Port = Convert.ToInt32(hostHeader.Split(':')[1]);
    }
    else
    {
        hostUrl.Host = hostHeader;
        hostUrl.Port = -1;
    }

    Uri url = request.Url;
    UriBuilder uriBuilder = new UriBuilder(url);

    if (String.Equals(hostUrl.Host, "localhost", StringComparison.OrdinalIgnoreCase) || hostUrl.Host == "127.0.0.1")
    {
        // Do nothing
        // When we're running the application from localhost (e.g. debugging from Visual Studio), we'll keep everything as it is.
        // We're not using request.IsLocal because it returns true as long as the request sender and receiver are in same machine.
        // What we want is to only ignore the requests to 'localhost' or the loopback IP '127.0.0.1'.
        return uriBuilder.Uri;
    }

    // When the application is run behind a load-balancer (or forward proxy), request.IsSecureConnection returns 'true' or 'false'
    // based on the request from the load-balancer to the web server (e.g. IIS) and not the actual request to the load-balancer.
    // The same is also applied to request.Url.Scheme (or uriBuilder.Scheme, as in our case).
    bool isSecureConnection = String.Equals(request.Headers["X-Forwarded-Proto"], "https", StringComparison.OrdinalIgnoreCase);

    if (isSecureConnection)
    {
        uriBuilder.Port = hostUrl.Port == -1 ? 443 : hostUrl.Port;
        uriBuilder.Scheme = "https";
    }
    else
    {
        uriBuilder.Port = hostUrl.Port == -1 ? 80 : hostUrl.Port;
        uriBuilder.Scheme = "http";
    }

    uriBuilder.Host = hostUrl.Host;

    return uriBuilder.Uri;
}    

Conclusion

IMHO this is a limitation with .Net framework itself because here we have to modify the behaviour of the framework class to achieve what we really want . If there is way in which the IIS web server can detect the topology on which it is running, then the HttpRequest.Url could be implemented correctly. So that the developer need not to worry about the deployment scenarios(at least in this simple case).