1

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?

P.S. 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...

6
  • 1
    Do you get any script errors? Have you tried looking in your browsers developer tools to see what the request returns? Commented Feb 2, 2015 at 13:42
  • @jgauffin I don't get a thing...which is really weird...I've tried using browser console first, and after that Fiddler...When i set break point it never gets to that part of code...I guess that I've didn't done some mapping well... Commented Feb 2, 2015 at 13:46
  • Is that page generated through an ajax request? If that's the case, the document.ready callback will never be invoked. Try removing it from the page (executing the script directly). Commented Feb 2, 2015 at 14:05
  • Which jQuery do you use? Did you look at this site? link Commented Feb 2, 2015 at 14:20
  • @Marcin I've tried version from that example, but still nothing... I was using older version before that... Commented Feb 2, 2015 at 14:37

5 Answers 5

4
+25

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, i.e.:

  • 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);                                        
    });
};
Sign up to request clarification or add additional context in comments.

4 Comments

Wow so nice explanation, thank you very much on this! But, still believe it or not code is not working properly... It doesn't call service on every 8 seconds like it suppose to do...and it's hard to track why...:-/ @JotaBe
Then we must track the problem down: please, open Chrome, press F12 and do this 2 things: 1) go to Sources, choose the file where this script is in, and put some breakpoints in the function code so that you can see if the code is reached. 2) go to the Network tab and look for the requests made by the app. You should find the failed requests, and, if you examine them,perhaps you can give me some more information. And answer this question: is the request made by a web app in a different domain or machine or port that the one the Wep API is working on? We'll get it right, don't worry!
Did that last day and hear this... I've made some changes in Web.config file, after not calling service at all and after showing nothing in browser Network console I've managed to make it call the service, HTTP status is OK, and when I examine each call it returns valid data in JSON... But it still doesn't print the result on the page...So I guess that the problem is in View now...And Web Api is on the same port as the rest od the app :) @JotaBe
Then you have to make use of Chrome debugging console to determine what the problem is. I bet you're getting the wrong id to find your div. Press F12 to open Chrome debugging tools. Go to the sources tab, put a breakpoint on the line $('#'+divId).... When the breakpoint is hit, go to the debugger console (if it's not open, press Esc) and type divId + enter. Check if a div with this id really exists in your html. Ifit looks like that is true, type $('#'+divId) and press enter. You should see an array with an element, which is the searched div.If they are missing, correct the code.
1

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.

8 Comments

Just tried it...didn't worked...It's weird that doesn't call service at all... Maybe url should be different? @hutchonoid
@nemo_87 I have changed the data to data: { id: @Model[i].PostId }, please try that. I think it would have thought it was a string.
Nope, didn't worked :( Thanks for trying to help me...I totally can't believe this simple thing can be this complicated... @hutchonoid
@nemo_87 It shouldn't be, will try in a test project.
@nemo_87 Try the update, it isolates it from the razor loop etc just to see it's being called. ps you need to add get to url.
|
1

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

Comments

0

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)

1 Comment

you mean like defining routing right above Get method? I've didn't try that...But I can try it...Still I think that the real problem is in ajax call...but that's just a guessing...thanks for advice...it you find time later I will be more than thankful :) @finman
-1

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')
            }});

2 Comments

nope...not working :( Ajax doesn't call service at all...so I guess that error is in url or maybe function for invoking isn't good...
Mmmm? type: 'POST' for a get method?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.