Click here to Skip to main content
Click here to Skip to main content

Extending an existing ASP.NET Web Service to support JSON

, 1 Aug 2008
Rate this:
Please Sign up or sign in to vote.
Describes how to use JSON serialization functionality in .NET 3.5 to add JSON support to a standard ASP.NET Web Service.

Introduction

If you use ASP.NET Web Services often, then you know how they can be called in different ways. SOAP 1.1/1.2, HTTP POST, and HTTP GET are some of the supported protocols you can use to call ASP.NET Web Services. Wouldn't it be great to be able to call a Web Service through HTTP POST or GET but with the capability of passing in and receiving JSON-encoded objects, instead of plain strings and XML?

Background

Why call Web Services through HTTP POST and GET in the first place anyway? Because, sometimes you want to call the service using JavaScript and AJAX, for example. One flaw with this approach is that you will still get XML back from the service that you have to parse and convert to some JavaScript object.

Recently, I’ve been hearing a lot about JSON in .NET 3.5, and was hoping to see it added as another standard way to call an ASP.NET Web Service. I’ve been using JSON for a while, even returning JSON serialized objects from Web Service methods inside the XML SOAP envelope – hey, it’s better than deserializing the objects at the client, and enables reuse of the Web Service code at the server.

I don’t like the idea of having to write the same function twice, only because they return data that is in different formats but is otherwise the same – even if the function just contains one line which calls some shared function in a library – duplicate code gets out of sync, gradually changes, and ends up splitting into different versions of what is otherwise the same functionality.

So, I was glad to find that .NET 3.5 enables us to expose WCF services in such a way that they can be interacted with via JSON alone – meaning you can both pass in JSON-encoded parameters and receive JSON-encoded objects. Since JSON is JavaScript, at the client, you won’t have to do any parsing or deserialization.

Still, I don’t want a JSON service, and I don’t want an XML service, I want a service which can be interacted with using JSON or XML, whichever I feel like. So, after playing around a little, I found that everything we need to have a standard ASP.NET Web Service support JSON is already inside .NET 3.5 – that is, JSON Serialization and Reflection.

The Code

The idea is to be able to mark the functions we wish to be callable with JSON with an attribute, such as [JSONMethod].

The attribute portion is really easy, just create a new attribute class. It can be an empty class since at this time, we just use the attribute as a flag – we could add properties to the attribute to help in documentation, for example.

public class JSONMethodAttribute : System.Attribute
{
    public JSONMethodAttribute()
    {
    }
}

The next thing that needs to be done is to create a new Web Service class – that is, a class that extends System.Web.Services.WebService. Why is this? If you take a look at any service you have created in the past, you’ll see that, by default, they extend the WebService class. What we really want is to be able to intercept the web method calls, so by implementing our own WebService class and having our Web Services extend that class, we get a nice place in which to put the code to handle the JSON requests.

In this example, I create a class named EnhancedWebService which extends System.Web.Services.WebService. We will use this class’ constructor to intercept the JSON requests. This begs the question: how will we know if a request is a JSON request? There’s probably a number of ways to do that; for our purposes, I just check the request’s querystring for a variable named “form” set to “json” (so, the URL would contain something like …&form=json).

Here is the initial EnhancedWebService class:

    public class EnhancedWebService : System.Web.Services.WebService
    {
        public EnhancedWebService()
            : base()
        {
            string ServiceMethodName = GetMethodName();
            bool IsJSON = Context.Request.QueryString["form"] == "json";
            if (IsJSON) InterceptJSONMethodRequest(ServiceMethodName);
        }
        private string GetMethodName()
        {
            return Context.Request.Url.Segments[Context.Request.Url.Segments.Length - 1];
        } 
        private void InterceptJSONMethodRequest(string ServiceMethodName)
        { 
            JSONMethodAttribute JMA = GetMethodJSONMethodAttribute(ServiceMethodName);
            if (JMA == null)
            {
                Context.Response.Write("throw new Exception('The Web Service method " + 
                                       ServiceMethodName + " is not available " + 
                                       "as a JSON function. For more " + 
                                       "information contact " + 
                                       "the Web Service Administrators.');");
            }
            else
            { 
                //ToDo: deserialize parameters, call target method, 
                //      deserialize and write return value
            }
            Context.Response.Flush();
            Context.Response.End();
        } 
        private JSONMethodAttribute GetMethodJSONMethodAttribute(string WebServiceMethodName)
        {
            MethodInfo ActiveMethod = this.GetType().GetMethod(WebServiceMethodName);
            JSONMethodAttribute JMA = null;
            if (ActiveMethod != null)
            {
                object[] Attributes = ActiveMethod.GetCustomAttributes(true);
                foreach (object Attribute in Attributes)
                {
                    if (Attribute.GetType() == typeof(JSONMethodAttribute))
                    {
                        JMA = (JSONMethodAttribute)Attribute;
                    }
                }
            }
            return JMA;
        }
    }
}

The GetMethodName function just retrieves the name of the function being called from the URL. When calling Web Methods via HTTP POST or GET, the method name is in the URL, something like: http://www.mysite.com/myservice.asmx/MyWebMethodName.

The InterceptJSONMethodRequest above still needs to be completed; however, notice how we flush and end the response right at the end – if we do not do this, after the control leaves the EnhancedWebService class’ constructor, the ASP.NET service handler would proceed to service the request and write out the SOAP response, which we don’t want to let happen whenever we are dealing with a JSON request, for obvious reasons.

Also, inside InterceptJSONMethodRequest, we call GetMethodJSONMethodAttribute to retrieve the [JSONAttribute] for the method that is being called, if one has been provided. If it has not, then we throw an exception – a JavaScript exception that is. You can throw a server-side exception if you want, but when it reaches the client, the browser will not have a nice message for debugging (by the way, we use Reflection in System.Reflection to look for the attribute, Reflection is great).

Obviously, we will also need a way to serialize and deserialize to and from JSON. Since .NET 3.5 has that built-in, that’s not a problem; just add the following two functions to the ExtendedWebService class:

private string JSONSerialize(object SerializationTarget)
{
    DataContractJsonSerializer serializer = 
      new DataContractJsonSerializer(SerializationTarget.GetType());
    MemoryStream ms = new MemoryStream();
    serializer.WriteObject(ms, SerializationTarget);
    string Product = Encoding.Default.GetString(ms.ToArray());
    ms.Close();
    return Product;
}
private object JSONDeserialize(string DeserializationTarget, Type TargetType)
{
    MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(DeserializationTarget));
    DataContractJsonSerializer serializer = new DataContractJsonSerializer(TargetType);
    object Product = serializer.ReadObject(ms);
    ms.Close();
    return Product;
}

You’ll need to use the System.Runtime.Serialization.Json namespace, which will require references to System.Runtime.Serialization, System.ServiceModel, and System.ServiceModel.Web.

For the final piece, we just need to complete the ToDo in the InterceptJSONMethodRequest function; here it is:

else
    {
        Type Service = this.GetType();
        MethodInfo JSONMethod = Service.GetMethod(ServiceMethodName);
        if (JSONMethod == null) return;
        ParameterInfo[] JSONMethodParameters = JSONMethod.GetParameters();
        object[] CallParameters = new object[JSONMethodParameters.Length];
        for (int i = 0; i < JSONMethodParameters.Length; i++)
        {
            ParameterInfo TargetParameter = JSONMethodParameters[i];
            string RawParameter = Context.Request.Form[TargetParameter.Name];
            if (RawParameter == null || RawParameter == "")
                RawParameter = Context.Request.QueryString[TargetParameter.Name];
            if (RawParameter == null || RawParameter == "")
                throw new Exception("Missing parameter " + TargetParameter.Name + ".");
            CallParameters[i] = JSONDeserialize(RawParameter, TargetParameter.ParameterType);
        }
        object JSONMethodReturnValue = JSONMethod.Invoke(this, CallParameters);
        string SerializedReturnValue = JSONSerialize(JSONMethodReturnValue);
        Context.Response.Write(SerializedReturnValue);
    }
    …

Starting from the top, we get the current Web Service class type – notice that the type of “this” isn’t EnhanceWebService, but the actual Web Service class which we’ll create at some later time and which will contain the various web methods.

We then proceed to obtain the information on the method being called – if it doesn’t exist, then something very wrong happened, and we just exit without ending the response, which allows the ASP.NET web service handler to continue.

From the method’s info, we obtain its list of parameters which we iterate through. Here is where the interesting stuff happens. For each parameter, we look both to the QueryString and the Form collection for a variable with the same name – we check both because parameters can be passed in both ways. If no variable exists for a given parameter, then that parameter was not provided, and we throw an exception – here, just a server-side exception for a change – otherwise, we deserialize its value into an object and save it for later.

Once we have deserialized all of the parameters, we invoke the target method and get the return value. Of course, at that point, all that needs to be done is serialize the return value and write out the response.

That’s all for the EnhancedWebService class. Now, you can expose any WebMethods to JSON with a single line, for instance:

public class MyService : EnhancedWebService
{
    public MyService () {
    }

    [JSONMethod]
    [WebMethod]
    public string[] MethodThatSupportsJSON(string Parameter)
    {
        return new string[] { Parameter, Parameter, Parameter };
    }

    [WebMethod]
    public string[] MethodThatDoesNotSupportJSON(string Parameter)
    {
        return new string[] { Parameter, Parameter, Parameter };
    }
}

If you use the code in this example, you may want to check for additional conditions, such as whether the target method is void (does not return a value), and write out an empty string whenever that’s the case, but this should be good enough to get started.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Bobby Soares

Unknown
No Biography provided

Comments and Discussions

 
QuestionSample code PinmemberMeena Trivedi9-Feb-14 20:46 
QuestionHey Bobby Pinmembercarlmalden27-Oct-11 18:58 
QuestionThanks! Very helpful! PinmemberSilasW19-Jul-11 12:54 
Questionhi,can you give a whole example? PinmemberJLKEngine0088-Feb-09 1:55 
AnswerRe: hi,can you give a whole example? Pinmemberqubing8-Apr-09 14:22 
Generalcannot see json format Pinmembercadit25-Jan-09 4:51 
GeneralSerializationException with identical code PinmemberMember 30109616-Oct-08 11:19 
GeneralRe: SerializationException with identical code Pinmemberenzorio7-Oct-08 1:14 
GeneralRe: SerializationException with identical code Pinmemberneskire_neros7-Jan-09 0:11 
GeneralInput type on POST Pinmemberenzorio4-Oct-08 5:07 
GeneralCalling the service PinmemberThomas Watson19-Sep-08 8:58 
Thank you for the article! I am having trouble calling this .net service from Javascript specifically via XMLHttpRequest object. Can you show me an example of calling it via JSON?
 
I have tried several things, but specifically, here is what I used to do calling my service the standard way:
 
//variable decleration, assume PolyDraw_XML_HTTP exists and is valid
var PolyDraw_PointInPolygonWebService_Mode = "POST";
var PolyDraw_PointInPolygonWebService_URL = "/common/services/MapServices.asmx";
var PolyDraw_PointInPolygonWebService_SOAPAction = "http://tempuri.org/DetermineIfPointsInPolygon";
var PolyDraw_PointInPolygonWebService_ContentType = "text/xml";
 

 
PolyDraw_XML_HTTP.open("POST",PolyDraw_PointInPolygonWebService_URL);
    PolyDraw_XML_HTTP.onreadystatechange = PolyDraw_DetermineIfPointsInPolygon_ReadyStateChange;
    PolyDraw_XML_HTTP.setRequestHeader("Content-Type", PolyDraw_PointInPolygonWebService_ContentType);
    PolyDraw_XML_HTTP.setRequestHeader("SOAPAction", PolyDraw_PointInPolygonWebService_SOAPAction);
    
   
    //Create the polygon points fragment
    var myPolygonPointsXMLFragment = "";
    for(i=0;i<pPolygonPoints.length;i++)
    {
        myPolygonPointsXMLFragment += "\
            <LatLng>\
                <Lat>" + pPolygonPoints[i].lat() + "</Lat>\
                <Lng>" + pPolygonPoints[i].lng() + "</Lng>\
            </LatLng>";
    }
    
    
    //Create the verify points Fragment
    var myPointsToVerifyXMLFragment = "";
    for(i=0;i<pPointsToVerify.length;i++)
    {
        myPointsToVerifyXMLFragment += "\
            <LatLng>\
                <Lat>" + pPointsToVerify[i].lat() + "</Lat>\
                <Lng>" + pPointsToVerify[i].lng() + "</Lng>\
            </LatLng>";
    }
    
     var myMessage = " \
        <soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>\
            <soap:Body>\
                <DetermineIfPointsInPolygon xmlns='http://tempuri.org/'>\
                    <pPolygon>\
                        " + myPolygonPointsXMLFragment + "\
                    </pPolygon>\
                    <pPoints>\
                        " + myPointsToVerifyXMLFragment + "\
                    </pPoints>\
                </DetermineIfPointsInPolygon>\
            </soap:Body>\
        </soap:Envelope>";
    
    
    PolyDraw_XML_HTTP.send(myMessage);
 

 
and finally, here is my service:
 
 /// <summary>
    /// Summary description for MapServices
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    [ScriptService]
    
    public class MapServices : EnhancedWebService
    {
 
        [WebMethod, JSONMethod]
        public Boolean[] DetermineIfPointsInPolygon(LatLng[] pPolygon, LatLng[] pPoints)
        {
            var returnArray = new Boolean[pPoints.Length];
 
            var i = 0;//used to control the index of the return array
            foreach (var point in pPoints)
            {
                returnArray[i] = MapUtilities.PointInPolygon(pPolygon, point);
                i++;
            }
 
            //return results
            return returnArray;
        }
    }

QuestionWhy reinvent? Pinmemberpataykhan15-Aug-08 7:20 
AnswerRe: Why reinvent? PinmemberBobby Soares25-Aug-08 16:04 
GeneralThanks for the article :)! PinmemberMike Borozdin2-Aug-08 7:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 2 Aug 2008
Article Copyright 2008 by Bobby Soares
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid