简体   繁体   中英

Calling WebAPI Service in ASP.NET MVC 5 application, using jQuery

I'm trying to build a simple WebApi service that will return comments for posts in real time. So the service only have Get method implemented and it's a simple returning IEnumerable of strings for passed Post ID:

 public class CommentApiController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<string> Get(int id)
        {
            return new ApplicationDbContext().Posts.First(x => x.PostId == id).CommentId.Select(x => x.CommentText).ToList();
        }

        // POST api/<controller>
        public void Post([FromBody]string value)
        {
        }

        // PUT api/<controller>/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/<controller>/5
        public void Delete(int id)
        {
        }
    }

I've made WebApiConfig class too and specified routing as following:

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
}

In my Global.asax.cs file I've added a reference for that routing:

protected void Application_Start()
{
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

In simple partial view I'm trying to call this service every 8 seconds, so the comments for posts can appear by themselves, without need for refreshing the page, to check if some other users posted a comment on a post.

@model List<StudentBookProject.Models.Post>

<table class="table table-striped">
    @foreach (var item in Model)
    {
        if (item.ImagePost != null)
        {
            <tr class="info">
                <td>@item.CurrentDate</td>
            </tr>
            <tr class="info">
                <td>
                    |@Html.ActionLink("Delete", "Delete", new { id = item.PostId }) |
                    @Html.ActionLink("Comment", "AddComment", new { id = item.PostId }, new { @class = "comment" }) |
                </td>
            </tr>
            <tr class="info">
                <td>
                    <img src="data:image/png;base64,@Convert.ToBase64String(item.ImagePost, 0, item.ImagePost.Length)" width="620" />
                </td>
            </tr>
            <tr>
                <td>
                    @Html.Partial("~/Views/Posts/ListComment.cshtml", item.CommentId)
                </td>
            </tr>
        }
        if (item.FilePost != null)
        {
            <tr class="info">
                <td>@item.CurrentDate</td>
            </tr>
            <tr class="info">
                <td>
                    | @Html.ActionLink("Delete", "Delete", new { id = item.PostId }) |
                    @Html.ActionLink("Comment", "AddComment", new { id = item.PostId }, new { @class = "comment" }) |
                </td>
            </tr>
            <tr class="info">
                <td>
                    File attachment
                </td>
            </tr>
        }
        if (item.TextPost != "")
        {
            <tr class="info">
                <td>@item.CurrentDate</td>
            </tr>
            <tr class="info">
                <td>
                    | @Html.ActionLink("Edit", "Edit", new { id = item.PostId }, new { @class = "lnkEdit" }) |
                    @Html.ActionLink("Delete", "Delete", new { id = item.PostId }) |
                    @Html.ActionLink("Comment", "AddComment", new { id = item.PostId }, new { @class = "comment" }) |
                </td>
            </tr>
            <tr class="info">
                <td>
                    @item.TextPost
                </td>
            </tr>
        }
    }
</table>

@{
    int i = 0;

    while (i < Model.Count())
    {
        if (Model[i].ImagePost != null || Model[i].TextPost != null || Model[i].FilePost != null)
        {
            <script>
                $(document).ready(function () {

                    var showAllComments = function () {                 
                        $.ajax({
                            url: "/api/CommentApi/" + "@Model[i].PostId.ToString()"        
                        }).done(function (data) {
                            var html = "";                              
                            for (item in data) {
                                html += '<div>' + data[item] + '</div>';
                            }
                            var divID = "@("quote" + Model[i].PostId.ToString())"

                            $('#' + divID).html(html);

                            setInterval(function () {   
                                showAllComments();                                          
                            }, 8000);                                                       

                        });
                    };
                })
            </script>
        }
        ++i;
    }
}

My service is not called, at least not in a proper way, cause new comments shows only after refreshing a page. I know that WebApi, especially in this case should be easy to implement and pretty straight forward, but I'm totally new to this technology and I don't have a clue what I've missed or implemented wrongly. Been trying to find some fitting tutorial to help me to solve this problem, but nothing helped me yet.

I've read somewhere that I've should add one line to Web.config file for WebDAV, but that didn't helped me also.

<handlers>
      <remove name="WebDAV"/>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

Does someone sees what I've didn't done and where is possible mistake? Also, does someone knows some good .NET WebApi tutorial?

PS When I made a call to a WebApi Get method directly from browser using: .../api/CommentApi/id route services gets invoked and returns comments for passed id of post, so service is ok, I'm not calling it in a code in a good way...

First of all, as you yourself say, when you type the URL in a browser and press enter you get a response from the Web API: that means that the Web API service, and the server are correcty configured. So your problem has nothing to do with configuration: the problem is in the request itself.

URL and method

For the request to work it must be a GET request, to the correct URL, and with the correct parameters.

Your route template looks like this routeTemplate: "api/{controller}/{id}" with optional id . Your method is a get method. In this kind of method the parameter can be recovered from either the route ( {id} segment in the template) or the query string, ie:

  • GET api/CommentApi/5
  • GET api/CommentApi?id=5

You can use any of this URLs.

Returned data format, and Accept header

The Web API can return the data in two different formats, XML or JSON. If you don't specify anything, the returned data comes in XML format (that's what you get you get when you type the URL in your browser). If you prefer JSON, which is the case, you need to add a header to your request: Accept: application/json

NOTE: it makes no sense to specify a Content-Type because you cannot send payload (data in the body) in a GET request

Doing it with jQuery

The easiest way to get data from a GET method in the Web API service, is using jQuery .getJSON . If you look at the docs you'll see that this method is equivalent to

$.ajax({
  dataType: "json",
  url: url,
  data: data,
  success: success
});

And, if you read the docs for .ajax() , you'll understand that specifying dataType: "json" is equivalent to including the header Accept:application/json , which asks the Web API to return JSON data. And you'll also understand that the default method is GET. So the only other thing that you must ensure is that the URL looks as expected. Look at the signature of getJSON : jQuery.getJSON( url [, data ] [, success ] )

It requires an URL and optionally data and a succes callback. I recommend not using the callback, so let's see 2 possible options to make the request:

  • $.getJSON('/api/CommentApi/'+id) which would render an URL like /api/CommentApi/5 (data not specified)
  • $.getJSON('/api/CommentApi',{id:5}) which would render an URL like /api/CommentApi?id=5 (data specified as JavaScript object, with property names like the action's parameter names: id in this case)

Using the response

I recommend not to use the success callback, and use the promise .done method instead. So, whichever syntax you use for the call, you must postpone the .done like this (as you did in your original code):

$.getJSON('/api/CommentApi/'+id).done(function(response) {
    // use here the response
});

The final code

So, the modified showAllComments method would look like this

Please, pay special attention to the comments

var showAllComments = function () {
    $.getJSON("/api/CommentApi/" + "@Model[i].PostId.ToString()")
    .done(function (data) {
        // create empty jQuery div
        var $html = $('<div>'); 
        // Fill the empty div with inner divs with the returned data
        for (item in data) {
          // using .text() is safer: if you include special symbols
          // like < or > using .html() would break the HTML of the page
          $html.append($('<div>').text(data[item]));
        }
        // Transfer the inner divs to the container div
        var divID = "@("quote" + Model[i].PostId.ToString())";
        $('#' + divID).html($html.children());
        // recursive call to the same function, to keep refreshing
        // you can refer to it by name, don't need to call it in a closure
        // IMPORTANT: use setTimeout, not setInterval, as
        // setInterval would call the function every 8 seconds!!
        // So, in each execution of this method you'd bee asking to repeat
        // the query every 8 seconds, and would get plenty of requests!!
        // setTimeout calls it 8 seconds after getting each response,
        // only once!!
        setTimeout(showAllComments, 8000);                                        
    }).fail(function() {
        // in case the request fails, do also trigger it again in seconds!
        setTimeout(showAllComments, 8000);                                        
    });
};

You need to set the dataType , contentType and also pass the id parameter across in the data;

Try the following:

 $.ajax({
        url: "/api/CommentApi/Get",
        type: 'GET',
        data: { id: @Model[i].PostId },
        dataType: 'json',
        contentType: 'application/json',
        success: function (data) { 

                            var html = "";                              
                            for (item in data) {
                                html += '<div>' + data[item] + '</div>';
                            }
                            var divID = "@("quote" + Model[i].PostId.ToString())"

                            $('#' + divID).html(html);

                            setInterval(function () {   
                                showAllComments();                                          
                            }, 8000);                                                        }
    });

Update

Ok, breaking this down into a simpler working example the following will work.

javascript

    var urlString = "http://localhost/api/CommentApi/Get";

    $.ajax({
        url: urlString,
        type: 'GET',
        data: { id : 1},
        dataType: 'json',
        contentType: 'application/json',
        success: function (data) { console.log(data); }
    });
</script>

Have your controller just return the hardcoded values as follows:

// GET api/values
public IEnumerable<string> Get(int id)
{
    return new List<string>() { "one", "two" };
}

Run the above and test that it prints out the values to the console.

Hi at first it looks you didn't call the method. I cannot find any call of 'showAllComments' function. When I copied your code and just call after declaration

 <script>
    $(document).ready(function () {

        var showAllComments = function () {
            // code
        };

        showAllComments();
    })
</script>

I saw that ApiController method is called, html is updated for me. Are you sure that you call the 'showAllComments' function?

And JotaBe's answer is very good, should work. And follow for a setTimeout function as JotaBe wrote. See the description of functions.

http://www.w3schools.com/jsref/met_win_setinterval.asp http://www.w3schools.com/jsref/met_win_settimeout.asp

Have you looked at the routing on the calls and what the real server response is?

You might want to setup the Route attribute onto the actions then look at quickly calling it using Postman to see the responses. I found without using postman it made checking the routing a lot harder than it needed to be.

The example items seem to just call the root as /api/comments/1 or api/comments/?id=1 where the get call looks to be /api/comments/get?id=1 from the routing as IIRC default routing would have the action as Index not get or view.

I'll look at this in a bit more detail later when I can spin up a demo project and test out what is going on

edit: if you add the attributes to the controller/action you can see the way the calls are constructed a little easier:

[RoutePrefix("api/Account")]
public class AccountController : BaseAPIController
{
       [Route("someroute")]
       [HttpGet]
       public int mymethod(int ID){return 1;}
}

In this example you would call a get to /api/account/soumeroute?id=123123 and you would get 1 back. The HTTPGet is just a help for separating code on readability (and the XML API documentation if you have it enabled, it's a nice to have)

tDid you try to add this:

dataType: 'json',
async: true,
type: 'GET', //EDIT: my mistake should be GET, thx JotaBe for pointing that

to your $.ajax call?

Or try this way:

$.ajax({
                url: "/api/CommentApi/" + "@Model[i].PostId.ToString()",

                dataType: 'json',
                async: true,
                type: 'GET',
                error: function () {
                    alert('Error');
                }

            success: function (data) {
                alert('Success')
            }});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM