nurchi
nurchi

Reputation: 800

@Ajax.ActionLink works, but $.ajax doesn't

I am new to MVC and Ajax, but have good working knowledge and experience with C#.

As a test, I have created an ApiController with 4 methods, 2 using GET and 2 using POST.

All 4 methods work perfectly fine with @Ajax.ActionLink, but (to my best knowledge) equivalent $.ajax call fails POST (but passes GET).

Here is my ApiController:

public class ValuesController : ApiController
{
    [HttpGet] // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    [HttpGet] // GET api/values/5
    public string Get(int id)
    {
        return $"id={id}, ~id={~id}";
    }

    [HttpPost] // POST api/values
    public string PostData(string value="123")
    {
        var x = Request;
        return new string(value.Reverse().ToArray());
    }

    [HttpPost] // POST api/values
    public JsonResult<object> PostData(string value, int i)
    {

        return Json((object)new { s=new string(value.Reverse().ToArray()), i });
    }
}

And the corresponding calls from .cshtml file:

@Ajax.ActionLink("Click me for Get()", "Get", "api/Values", null, new AjaxOptions() { HttpMethod = "GET", OnSuccess = "res0", OnFailure = "fail0", UpdateTargetId = "status0" })
<script language="javascript">
    function res0(res) {
        $('#status00')[0].innerText = "Result: " + res;
    }
    function fail0(a, b, c) {
        $('#status000')[0].innerText = "Error\n" + b + '\n' + c;
    }
</script>
<form action="" id="form0">
    <input type="submit" />
</form>
<div id="status0"></div>
<div id="status00"></div>
<div id="status000"></div>

@Ajax.ActionLink("Click me for Get(5)", "Get", "api/Values", new { id = 5 }, new AjaxOptions() { HttpMethod = "GET", OnSuccess = "res1", OnFailure = "fail1", UpdateTargetId = "status1" })
<script language="javascript">
    function res1(res) {
        $('#status11')[0].innerText = "Result: " + res;
    }
    function fail1(a, b, c) {
        $('#status111')[0].innerText = "Error\n" + b + '\n' + c;
    }
</script>
<form action="" id="form1">
    <input type="submit" />
</form>
<div id="status1"></div>
<div id="status11"></div>
<div id="status111"></div>

@Ajax.ActionLink("Click me for PostData('Hello')", "PostData", "api/Values", new { value = "Hello" }, new AjaxOptions() { HttpMethod = "POST", OnSuccess = "res2", OnFailure = "fail2", UpdateTargetId = "status2" })
<script language="javascript">
    function res2(res) {
        $('#status22')[0].innerText = "Result: " + res;
    }
    function fail2(a, b, c) {
        $('#status222')[0].innerText = "Error\n" + b + '\n' + c;
    }
</script>
<form action="" id="form2">
    <input type="submit" />
</form>
<div id="status2"></div>
<div id="status22"></div>
<div id="status222"></div>

@Ajax.ActionLink("Click me for PostData(\"Hello\", 3)", "PostData", "api/Values", new { value = "Hello", i = 3 }, new AjaxOptions() { HttpMethod = "POST", OnSuccess = "res3", OnFailure = "fail3", UpdateTargetId = "status3" })
<script language="javascript">
    function res3(res) {
        $('#status33')[0].innerText = "Result: " + JSON.stringify(res);
    }
    function fail3(a, b, c) {
        $('#status333')[0].innerText = "Error\n" + b + '\n' + c;
    }
</script>
<form action="" id="form3">
    <input type="submit" />
</form>
<div id="status3"></div>
<div id="status33"></div>
<div id="status333"></div>

<script language="javascript">
    document.getElementById('form0').onsubmit = function (e) { e.preventDefault(); form0_submit(); };
    document.getElementById('form1').onsubmit = function (e) { e.preventDefault(); form1_submit(); };
    document.getElementById('form2').onsubmit = function (e) { e.preventDefault(); form2_submit(); };
    document.getElementById('form3').onsubmit = function (e) { e.preventDefault(); form3_submit(); };

    function form0_submit() {
        $.ajax({
            type: 'GET',
            url: '/api/Values/Get',
            success: function (res) {
                $('#status0')[0].innerText = res;
            },
            error: function (jqXHR, tStatus, errThrown) {
                $('#status00')[0].innerText = tStatus;
                $('#status000')[0].innerText = errThrown;
            }
        });
    }

    function form1_submit() {
        $.ajax({
            type: 'GET',
            url: '/api/Values/Get',
            data: {id:@DateTime.Now.TimeOfDay.Seconds},
            success: function (res) {
                $('#status1')[0].innerText = res;
            },
            error: function (jqXHR, tStatus, errThrown) {
                $('#status11')[0].innerText = tStatus;
                $('#status111')[0].innerText = errThrown;
            }
        });
    }

    function form2_submit() {
        $.ajax({
            type: 'POST',
            url: '/api/Values/PostData',
            data: JSON.stringify({ value: "Test string" }),
            contentType: 'application/json; charset=UTF-8',
            dataType: 'json',
            success: function (res) {
                $('#status2')[0].innerText = res;
            },
            error: function (jqXHR, tStatus, errThrown) {
                $('#status22')[0].innerText = tStatus;
                $('#status222')[0].innerText = errThrown;
            }
        });
    }

    function form3_submit() {
        // not implemented yet, need to get form2_submit() working
    }
</script>

I used to get a 404 - Not found error until in public string PostData(string value="123") I made value an optional parameter. Now the function is called with the default "123" value, not what I pass it. In form2_submit() I tried different combinations for the data parameter.

Analysing the response using the Chrome developer tools (right-click->Inspect, then Network tab), I can see that the ActionLink sends the parameters as Query String Parameters, while the $.ajax call does it as Request Payload or Form Data depending on exactly what I pass to data parameter. In both cases, the parameter name/value pair is correct, but the C# ApiController seems to ignore that value (I have tried [FromBody] attribute as well).

I've checked other posts here and on other sites, but cannot figure out why the data is being transmitted differently

Edit:

As requested, the output of Get-Package:

PM> Get-Package

Id                                  Versions         ProjectName
--                                  --------         -----------
Antlr                               {3.4.1.9004}     WebApplication1
bootstrap                           {3.0.0}          WebApplication1
jQuery                              {1.10.2}         WebApplication1
Microsoft.ApplicationInsights       {2.2.0}          WebApplication1
Microsoft.ApplicationInsights.Ag... {2.0.6}          WebApplication1
Microsoft.ApplicationInsights.De... {2.2.0}          WebApplication1
Microsoft.ApplicationInsights.Pe... {2.2.0}          WebApplication1
Microsoft.ApplicationInsights.Web   {2.2.0}          WebApplication1
Microsoft.ApplicationInsights.Wi... {2.2.0}          WebApplication1
Microsoft.ApplicationInsights.Wi... {2.2.0}          WebApplication1
Microsoft.AspNet.Mvc                {5.2.3}          WebApplication1
Microsoft.AspNet.Razor              {3.2.3}          WebApplication1
Microsoft.AspNet.Web.Optimization   {1.1.3}          WebApplication1
Microsoft.AspNet.WebApi             {5.2.3}          WebApplication1
Microsoft.AspNet.WebApi.Client      {5.2.3}          WebApplication1
Microsoft.AspNet.WebApi.Core        {5.2.3}          WebApplication1
Microsoft.AspNet.WebApi.HelpPage    {5.2.3}          WebApplication1
Microsoft.AspNet.WebApi.WebHost     {5.2.3}          WebApplication1
Microsoft.AspNet.WebPages           {3.2.3}          WebApplication1
Microsoft.CodeDom.Providers.DotN... {1.0.8}          WebApplication1
Microsoft.jQuery.Unobtrusive.Ajax   {3.2.5}          WebApplication1
Microsoft.Net.Compilers             {2.4.0}          WebApplication1
Microsoft.Web.Infrastructure        {1.0.0.0}        WebApplication1
Modernizr                           {2.6.2}          WebApplication1
Newtonsoft.Json                     {6.0.4}          WebApplication1
Respond                             {1.2.0}          WebApplication1
WebGrease                           {1.5.2}          WebApplication1

PM>

Upvotes: 0

Views: 75

Answers (1)

Shyju
Shyju

Reputation: 218722

By default, If the parameter of your web api method is a simple type like string or int, Web API tries to get the value from the uri.

If you are sending the string in the request body, you should explicitly use the FromBody attribute decorator so that web api know it should read the data from the request body. When FromBody attribute is present, web api will use the content-type header value to pick a formatter which will work with the content it received.

[HttpPost] // POST api/values
public string PostData([FromBody]string value)
{
    if (value == null) value = "some non null";
    var x = Request;
    return new string(value.Reverse().ToArray());
}

Now on the client side, make sure you are sending JSON string version of the string you want to send, while specifying the content type as application/json

$.ajax({
    type: 'POST',
    url: '/api/Values/PostData',
    data: JSON.stringify("My Test string"),
    contentType: 'application/json',
    success: function (res)
    {
        console.log(res);
    },
    error: function (jqXHR, tStatus, errThrown)
    {
        console.log(errThrown);
    }
});

Upvotes: 1

Related Questions