Web service proxy in ASP.NET MVC – how to avoid cross-site script warnings

In this article I will walk through the process of creation of web service proxy in ASP.NET MVC. This is specifically useful as a method of preventing cross-site scripting (XSS) warnings on web page. This is a feature of all modern web browsers and cannot be easily omitted as it enforces same origin policy. This sort of attack can happen when browser tries to download client script from outside of application domain – simply speaking when web browser downloads script from different location.

For the purpose of this demo I will use IP look-up web service available at http://ipinfodb.com/. This web service allows to obtain geodata for given IP address and can be extremely useful for in example centralizing map (i.e. Google maps or Bing maps) on user’s location.

Valid request should look like this: http://ipinfodb.com/ip_query.php?ip=213.180.146.27

Of course it is possible to call this web service at Ajax request level, by simply writing:

    var ipaddress = '213.180.146.27';
    $.get('http://ipinfodb.com/ip_query.php?ip=' + ipaddress + '&timezone=false',
        function(data) {...}
    );

although on all most recent web browsers (IE 8, FF 3.6, Chrome, Safari) user will get very ugly security warning every time browser executes this code. This is due to XSS vulnerability mentioned above and can be extremely annoying to user when application performs this operation on very first page load.

Thankfully ASP.NET provides set of mechanisms to overcome this problem. In simplest words there is an extra layer between web service and Ajax call needed, and this layer is web service proxy.

The core component of proposed solution is a HTTP handler class, in this example named LocationProxyHandler. This class will create and execute HTTP request to web service and return response to client. It needs to implement two interfaces: IHttpHandler and IRouteHandler. First interface guarantees that class can process HTTP request, second allows to use class in routing rule that will be required to redirect requests to the handler class. Implementation of class is as follows:

    public class LocaionProxyHandler : IHttpHandler, IRouteHandler
    {
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            string ipAddress = context.Request.QueryString["ipaddress"];

            string str = string.Format(@"http://ipinfodb.com/ip_query.php?ip={0}",
                ipAddress);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(str);
            request.Method = "GET";
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream());

            HttpResponse res = context.Response;
            res.ContentType = "application/xml; charcode=utf8";
            res.StatusCode = 200;
            res.Write(reader.ReadToEnd());
        }

        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return this;
        }
    }

The most important method in the class is ProcessRequest(). It takes current HttpContext as parameter and for this context it executes HTTP request to external web service and builds and returns response. Before it fires HTTP request, it takes IP address as parameter from URL.

There is one more thing that needs doing before this proxy can serve the purpose. It has to be registered in ASP.NET MVC routing table within the application. To accomplish this the following code is needed in RegisterRoutes() method inside Global.asax.cs:

    routes.Add(new Route("{action}.proxy", new SocialCitiProxy.LocaionProxyHandler()))

This makes calls like http://localhost:1431/myApp/Service.proxy understandable for MVC engine and ensures the request is redirected to LocationProxyHandler.

Very final step is Ajax request to LocationProxyHandler:

var ipaddress = '213.180.146.27';
$.ajax({
        url: 'GetUserLocation.proxy',
        data: 'ipAddress=' + ipaddress,
        dataType: 'xml',
        contentType: 'application/xml; charset=utf8',
        error: function (xhr, status, e) {...},
        success: function (data) {...}
});

No security warning messages appears now and this is exactly what we wanted to achieve.

Cascading dropdowns with just a bit of Ajax in ASP.NET 2.0

In this sample I am going to go through very common idea of cascading dropdowns. Very basically speaking it is all about having set of dropdowns where next dropdown is populated on the basis of value selected from previous one.

Let’s start with very simple xml document:

<?xml version="1.0" encoding="utf-8" ?>
<People>
    <Person ID="1" Name="Laith Francis" ParentID="0" />
    <Person ID="2" Name="Hammett Swanson" ParentID="0" />
    <Person ID="3" Name="Macon Hayden" ParentID="0" />
    <Person ID="4" Name="Kennan Mooney" ParentID="0" />
    <Person ID="5" Name="Jin Hutchinson" ParentID="4" />
    <Person ID="6" Name="Emery Schwartz" ParentID="1" />
    <Person ID="7" Name="Melvin Garrett" ParentID="2" />
    <Person ID="8" Name="Giacomo Lamb" ParentID="4" />
    <Person ID="9" Name="Stephen Harding" ParentID="3" />
    <Person ID="10" Name="Ian Ward" ParentID="1" />
    <Person ID="11" Name="Raphael Walters" ParentID="2" />
    <Person ID="12" Name="Lee Gould" ParentID="1" />
    <Person ID="13" Name="Carter Carpenter" ParentID="4" />
    <Person ID="14" Name="Kareem Shepard" ParentID="14" />
    <Person ID="15" Name="Zahir Montgomery" ParentID="12" />
    <Person ID="16" Name="Zachery Mcmahon" ParentID="14" />
    <Person ID="17" Name="Hu Dillon" ParentID="12" />
    <Person ID="18" Name="Allistair Bradford" ParentID="13" />
    <Person ID="19" Name="Vincent Bryan" ParentID="11" />
    <Person ID="20" Name="Vance Santos" ParentID="12" />
</People>

Now let’s create very simple .aspx page, that has three dropdowns. First one we will populate using standard ASP.NET binding method so we will use DropDownList control in this instance. Because we do not populate other dropdowns straight away, nothing stops us from using html select tags. The markup can be like this:

<form id="form1" runat="server">
<div>
    Boss:
    <asp:DropDownList DataSourceID="xmlBosses"
        DataTextField="Name"
        DataValueField="ID"
        ID="ddlBosses" runat="server"></asp:DropDownList>
    <asp:XmlDataSource DataFile="~/App_Data/data.xml"
        ID="xmlBosses" runat="server" XPath="/People/Person[@ParentID=0]">
    </asp:XmlDataSource>
    <br />
    Manager:
    <select id="ddlManagers"></select>
    <br />
    Workers:
    <select id="ddlWorkers"></select>
</div>
</form>

Having this markup done we now need a mechanism to populate ddlManagers when ddlBosses changes. Same action needs to happen to ddlWorkers when ddlManagers is changed. To accomplish that we need make Ajax request to some data provider. In our instance, because application is created in ASP.NET 2.0, I opted for web service, (.Net 3.5 with MVC with it’s JsonResult class and Json() method is much more friendly on this front thought).

Firstly we need web service. We want to give it a boss Id and get list of all managers that report to this boss. Web service can look like this one:

public class People : System.Web.Services.WebService {
    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
    public List GetData(int ParentID) { 
        List<int> result = new List<int>(); 

        XmlDocument doc = new XmlDocument(); 
        doc.Load(Server.MapPath("~/App_Data/data.xml"));
        XmlNodeList nodes = doc.SelectNodes("/People/Person[@ParentID=" 
            + ParentID.ToString() + "]");

        foreach (XmlNode node in nodes) {
            Person p = new Person();
            p.ID = Convert.ToInt32(node.Attributes["ID"].Value);
            p.Name = node.Attributes["Name"].Value;
            p.ParentID = Convert.ToInt32(node.Attributes["ParentID"].Value);
            result.Add(p);
        } 
        return result;
    }

    [Serializable]
    public class Person {
       private int _id;
       private string _name;
       private int _parentId;

       public int ID {
           get { return _id; }
           set { _id = value; }
       }

       public string Name {
           get { return _name; }
           set { _name = value; }
       }

       public int ParentID {
           get { return _parentId; }
           set { _parentId = value; }
       }
    }
}

Having all of that we are now ready for very final stage – building Ajax call to execute web service and process returned data. The only thing we need is a bit of jQuery code, that will do it all:

$(function() {
    // for each dropdown bind change event
    $('select').change(function(e) {
        // capture which dropdown caused request
        if(!e) var e = window.event;
        var srcId = e.srcElement.id;

        // create ajax request
        $.ajax({
            url: 'WebServices/People.asmx/GetData', //call to created web method
            data: { ParentID: $(this).val() },  //web method expects ParentID
            contentType: 'application/xml; charset=utf-8',
            dataType: 'xml',
            error: function(xhr, status, e) { ... },
            success: function(data) {
                // if success get dropdown to be updated
                var select = null;
                switch(srcId) {
                    case 'ddlBosses':
                        select = $('select#ddlManagers');
                    break;
                    case 'ddlManagers':
                        select = $('select#ddlWorkers');
                    break;
                    default:                         
                    break;
                };

                // remove items from dropdown
                $(select).empty();

                // loop through Ajax request results
                $('Person', data).each(function() {
                    // for each result create option element
                    var option = document.createElement('option');
                    $(option).text($('Name', this).text()).val($('ID', this).text());
                    // and append it to dropdown
                    $(select).append(option);
                });
            }
        });
    });
});

And this is it!

I hope you will enjoy it!