Hiep
Hiep

Reputation: 2612

How to add more parameters to a JSON WebService without breaking call from old clients?

I want to add more parameters to my JSON WebService without breaking call from old clients. Example:

My WebService in Service.asmx

[WebMethod]
public string Ping(string msg, string additionalInfo)
{
    if (string.IsNullOrEmpty(additionalInfo))
    {
        return "Process msg with old version";
    }
    return "Process msg with new version"; ;
}

//My old web service does not have additionalInfo arguments
//public string Ping(string msg) {..}

Web.config tells that my WebService is JSON-based

<system.web.extensions>
  <scripting>
       <webServices>
           <jsonSerialization maxJsonLength="50000000" />
       </webServices>
   </scripting>

if clients call my new Json WebService with all the parameters => everything is fine

CallWs("http://localhost:48918/Service.asmx/Ping", '{"msg":"hello", "additionalInfo":""}')

But all the current clients won't give the additionalInfo:

CallWs("http://localhost:48918/Service.asmx/Ping", '{"msg":"hello"}')

my new WebService will immediately return error:

string(654) "{"Message":"Invalid web service call, missing value for parameter: \u0027additionalInfo\u0027.","StackTrace":"   at System.Web.Script.Services.WebServiceMethodData.CallMethod(Object target, IDictionary`2 parameters)\r\n   at System.Web.Script.Services.WebServiceMethodData.CallMethodFromRawParams(Object target, IDictionary`2 parameters)\r\n   at System.Web.Script.Services.RestHandler.InvokeMethod(HttpContext context, WebServiceMethodData methodData, IDictionary`2 rawParams)\r\n   at System.Web.Script.Services.RestHandler.ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)","ExceptionType":"System.InvalidOperationException"}"

So my customers will have to change theire code in order to use my new WebService, I don't want that. I want to give default values to my new WebService, What is the best way to do?

Possible duplication: Can I have an optional parameter for an ASP.NET SOAP web service But none of the response works for me.


FYI my customers often call my JSON WebService via PHP, they simply make a POST request to the service endpoint:

$ch = curl_init("http://localhost:48918/Service.asmx/Ping");
$wsrq = array(
    "msg" => "Hello",
    //"additionalInfo" => "World",
);
curl_setopt_array($ch, array(
    CURLOPT_POST => TRUE,
    CURLOPT_RETURNTRANSFER => TRUE,
    CURLOPT_SSL_VERIFYPEER => FALSE,
    CURLOPT_POSTFIELDS => json_encode($wsrq),
    CURLOPT_HTTPHEADER => array("Content-type: application/json; charset=utf-8"),
));
$response = curl_exec($ch);

Upvotes: 0

Views: 1408

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1039298

It looks like the proper way to achieve that is to use method overloads for your service methods. Also for the future methods I would recommend you using models:

public class MyModel
{
    public string Message { get; set; }
    public string AdditionalInfo { get; set; }
}

and then:

[WebMethod]
public string Ping(MyModel model)
{
    ...
}

This will give you more flexibility because you will be able to add properties easily in the future without breaking.

This being said, there's one approach or a workaround that you might consider: manual deserialization (I totally don't recommend it but worth mentioning).

Make your WebMethod without any parameters:

[WebMethod]
public string Ping()

and then read the request body manually by accessing the input stream:

[WebMethod]
public string Ping()
{
    Context.Request.InputStream.Seek(0, SeekOrigin.Begin);
    using (var inputStream = Context.Request.InputStream)
    using (var reader = new StreamReader(inputStream))
    {
        string body = reader.ReadToEnd();

        // TODO: Worth checking the request headers before attempting JSON deserialization
        // For example the Content-Type header
        var model = JsonConvert.DeserializeObject<MyModel>(body);
        if (string.IsNullOrEmpty(model.AdditionalInfo))
        {
            return "Process msg with old version";
        }
        return "Process msg with new version"; ;
    }
}

To avoid mixing multiple responsibilities in your service method you could move the parsing of the body stream into some separate extension method:

public static class RequestExtensions
{
    public static T ParseRequest<T>(this HttpRequest request)
    {
        request.InputStream.Seek(0, SeekOrigin.Begin);
        using (var inputStream = request.InputStream)
        using (var reader = new StreamReader(inputStream))
        {
            string body = reader.ReadToEnd();
            return JsonConvert.DeserializeObject<T>(body);
        }
    }
}

and then your WebMethod:

[WebMethod]
public string Ping()
{
    var model = Context.Request.ParseRequest<MyModel>();
    if (string.IsNullOrEmpty(model.AdditionalInfo))
    {
        return "Process msg with old version";
    }
    return "Process msg with new version"; ;
}

Now clients can call the Ping method like that:

POST /WebService1.asmx/Ping HTTP/1.1
Content-type: application/json; charset=utf-8
Host: localhost:14529
Content-Length: 61

{
    "msg": "Hello",
    "additionalInfo": "add info"
}

or the old way:

POST /WebService1.asmx/Ping HTTP/1.1
Content-type: application/json; charset=utf-8
Host: localhost:14529
Content-Length: 26

{
    "msg": "Hello"
}

Upvotes: 1

Related Questions