Monday, April 22, 2013

ASP.net WebAPI Weird Behavior

I have a simple WebAPI service with a method that accept and return single string parameter. The code look like this:


[HttpPost]
public string SubmitString(string input)
{
    return "Success";
}


Then, I use the following code to call my WebAPI:


using (HttpClient client = GetClientProxy())
{
    HttpResponseMessage response = client.PostAsync<string>("api/expense/SubmitString", input,
        new System.Net.Http.Formatting.JsonMediaTypeFormatter()
        ).Result;

    if (response.IsSuccessStatusCode)
    {
        // Parse the response body. Blocking!
        result = response.Content.ReadAsAsync<string>().Result;
    }
    else
    {
        // throw exception;
    }
}

It is weird that I keep getting HTTP404 error whenever I try to invoke the WebAPI method. Here is how my HTTP RAW content look like:


POST http://127.0.0.1:65000/api/expense/SubmitString HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: 127.0.0.1:65000
Content-Length: 6
Expect: 100-continue
Connection: Keep-Alive

"test"

After "googling" around, and found that WebAPI somehow not picking up simple value parameter automatically. I need to add [FromBody] attribute to the parameter, like this:


[HttpPost]
public string SubmitString([FromBody]string input)
{
    return "Success";
}

Yes, it works now. But, it behave differently when I create a WebAPI method which accept object parameter, I do not need to specify the [FromBody] attribute.

Also, note that WebAPI method only accept object type as parameter and the media formatter type are restricted to XML and JSON only.

In my previous post, I mentioned that the HttpClient.PostAsync<string> and HttpClient.PostAsync with StringContent are different. PostAsync<string> is posting whole string as an object, while the PostAsync with StringContent is posting HTTP RAW content.

What if I have the following WebAPI method which accept more than one parameter? Like this:


[HttpPost]
public string Submit2String(string input1, string input2)
{
    return "Success";
}


Unfortunately, it is not supported. The HTTP POST content can contain one object only. Therefore, we have to form an object that wrap both string objects into one object then only pass it to WebAPI method. WCF or web service developer like me would hate this kind of extra work and limitation. WCF has its built in SOAP serializer to parse the request packet and bind them correctly to the service method parameter a.k.a. the operation contract and data contract. However, the extra steps to wrap objects into one object before passing to WebAPI, somehow I feel it is similar to form SOAP envelope with wrapped objects. I only see one advantage of using WebAPI is the object (POX or JSON) passing via the wire is lightweight compare to SOAP.

Back to the problem, if you wish to accept more than one parameter in your WebAPI, you have to do the workaround like this:


[HttpPost]
public string Submit2String()
{
    string httpContent = this.Request.Content.ReadAsStringAsync().Result;
    string[] contents = httpContent.Split('&');

    Dictionary<string, string> parameterValue = new Dictionary<string, string>();
    foreach (string content in contents)
    {
        var temp = content.Split('=');
        parameterValue.Add(temp[0], temp[1]);
    }

    string input1 = parameterValue["input1"];
    string input2 = parameterValue["input2"];

    //Alternatively, if you hate the above string splitting code, you can have shortcut by using HttpUtility.ParseQueryString
    //Because the HTTP RAW content look like a query string anyway

    var option2 = HttpUtility.ParseQueryString(httpContent);

    string input_1 = option2["input1"];
    string input_2 = option2["input2"];

    return "Success";
}


Then, for your client, this is how you post your content:


using (HttpClient client = GetClientProxy())
{
    //Note: use content type application/x-www-form-urlencoded
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

    //Option 1:
    HttpResponseMessage option1 = client.PostAsync("api/expense/Submit2String",
        new StringContent("input1=" + input1 + "&input2=" + input2)
        ).Result;

    //Option 2:
    var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
        {
            new KeyValuePair<string, string>("input1", input1),
            new KeyValuePair<string, string>("input2", input2)
        });

    HttpResponseMessage option2 = client.PostAsync("api/expense/Submit2String", content).Result;

}


As you can see, you have to post the content with FormUrlEncodedContent and specify the content type as "application/x-www-form-urlencoded". The HTTP content must be in this format: parameter1=value1&parameter2=value2&parameter3=value3. When the content has been posted to WebAPI method, you can manipulate or parse the content by splitting it with &, and then convert the value into any other object type you wish. Or, you can use HttpUtility.ParseQueryString to convert the content into NameValueCollection since the value format look like query string.

If you wish to post more than one parameter with object type, you can do the same workaround by posting the HTTP content with the serialized object XML or JSON string.


No comments:

Post a Comment

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...