WannabePuppetMaster
WannabePuppetMaster

Reputation: 173

Posting to Web API Controller from Razor Page Always Returns 400 Error

I am new to .Net Core and MVC. I've got several Razor Pages that allow users to post stuff like comments and ratings for individual posts, but I want to try something new for liking a post so that it can be done using javascript without refreshing the page. To do this I am trying to pass data from the page to a service class via an API controller using jQuery ajax. Unfortunately every time I try to pass a simple value to the controller I get a an error 400 saying that the value cannot be converted to System.WhateverObjectTypeITry. For instance if I try to pass it as an integer I get "The JSON value could not be converted to System.Int32" and if I try to pass it as a string I get "The JSON value could not be converted to System.String"

My API controller looks like this:

[HttpPost]
        [Route("AddPostLike")]
        public async Task<string> AddPostLike([FromBody] int postid)
        {
            if(_signInManager.IsSignedIn(User))
            {
                ApplicationUser user = await _userManager.GetUserAsync(User);
                Likes newLike = new Likes();
                newLike.Post = Convert.ToInt32(postid);
                newLike.Commentid = null;
                newLike.Userid = user.Id;
                await _likeService.LikeAsync(newLike);
                return $"liked";
            }
            else
            {
                return $"User Must Be Logged in to Like";
            }
            
        }

My jQuery in the Razor Page looks like this:

<script>
         $(document).ready(function () {
             $('#like-post').on('click', function () {
                 var postid = parseInt($('#idlabel').text());
                 $.ajax({
                     url: '/api/Likes/AddPostLike/',
                     type: 'POST',
                     dataType: 'text',
                     data: '{"postid":"' + postid + '"}',
                     contentType: 'application/json',
                     success: function () {
                         var likecount = parseInt($('#like-post-count').text());
                         likecount + 1;
                         $('#like-post-count').text(likecount);
                     },
                     error: function (XMLHttpRequest, textStatus, errorThrown) {
                         alert("responseText=" + XMLHttpRequest.responseText + "\n textStatus=" + textStatus + "\n errorThrown=" + errorThrown);
                     }
        });
             });


    });
    </script>

I am using .Net Core 5.0 and am trying to use the Contoso Crafts demo as a guide, but the people at Microsoft that built Contoso Crafts decided to use a Blazor component instead of a razor page which somehow communicates with controllers despite not requiring the developer to write any javascript (see https://github.com/dotnet-presentations/ContosoCrafts/blob/master/src/Components/ProductList.razor) and they don't use a database to store data (they use a static JSON file), so I've had to go out and find a solution that might actually work in the real world.

Upvotes: 0

Views: 839

Answers (3)

Rena
Rena

Reputation: 36575

If you just want to post an Integer data, just change like below:

data: JSON.stringify(postid)

but the success function did not work.

That is because the counter does not increase, you can use likecount++ or likecount = likecount + 1 to make it work.

Another problem seems to be with the response from the controller which never tells people they must be logged in if the user is not signed in like it should.

That is because the else clause in your backend is a correct response for the ajax, you can simply throw an exception like:throw new Exception("User Must Be Logged in to Like");

Another way, you can change your code like below:

[HttpPost]
[Route("AddPostLike")]
public async Task<IActionResult> AddPostLike([FromBody] int postid)
{
    if (xxx)
    {
        return Ok("liked");
    }
    else
    {
        //return $"User Must Be Logged in to Like";
        return BadRequest("User Must Be Logged in to Like");
    }
}

A whole working demo:

<label id="idlabel">1</label>
<label id="like-post-count" >4</label>
<input id="like-post" type="button" value="Post"/>
@section Scripts
{
    <script>
        $(document).ready(function () {
            $('#like-post').on('click', function () {
                var postid = parseInt($('#idlabel').text());
                $.ajax({
                    url: '/api/Likes/AddPostLike/',
                    type: 'POST',
                    dataType: 'text',
                    data: JSON.stringify(postid),    //change here....
                    contentType: 'application/json',
                    success: function () {
                        var likecount = parseInt($('#like-post-count').text());
                        likecount++;
                        $('#like-post-count').text(likecount);
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        alert("responseText=" + XMLHttpRequest.responseText + "\n textStatus=" + textStatus + "\n errorThrown=" + errorThrown);
                    }
                });
            });
        });
    </script>
}

Controller:

[HttpPost]
[Route("AddPostLike")]
public async Task<string> AddPostLike([FromBody] int postid)
{
    if (xxxx)
    {
        return $"liked";
    }
    else
    {
        //return $"User Must Be Logged in to Like";
        throw new Exception("User Must Be Logged in to Like");
    }

}

Upvotes: 1

Serge
Serge

Reputation: 43860

In addition to fixing the way data can be sending to controller ( @StephenCleary already offered one of the possible ways), you have to fix a success function too by adding a result parameter

 success: function (result) {
 var likecount = parseInt(result).text());
 likecount + 1;
 $('#like-post-count').text(likecount);
 },

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 456322

Your [FromBody] attribute is saying that the body should be parseable as an integer, but the body is actually something like {"postid":"13"}.

The body is an object with a property named postId. So try defining that as your DTO:

public sealed class AddPostLikePostDto
{
  public int PostId { get; set; }
}

...

public async Task<string> AddPostLike([FromBody] AddPostLikePostDto dto)

Upvotes: 2

Related Questions