4

I'm making an ASP.NET Web API web service, and an HTML/javascript page to test it. The issue I'm having is with passing a complex data parameter and having it come through properly in the web API controller.

I know there are numerous similar questions and I've read them and tried the solutions and haven't solved it. I have also read some JQuery documentation.

Here's my controller:

public class TitleEstimateController : ApiController
{
    public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
    {
            // All the values in "query" are null or zero
            // Do some stuff with query if there were anything to do
    }
}

public class EstimateQuery
{
    // Various fields
}

The route mapping in WebApiConfig.cs:

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{query}"
            );

And the javascript:

var uri = 'api/titleEstimate/';
var query = {
            "username": $("#user").val(),
            // other fields
        };

      $.getJSON(uri,query)
          .done(function (data) {
              $('#product').text("OK");
          })
          .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
          });

Currently I'm getting a 404. I tried $.getJSON(uri + '/' + query) but that didn't work either. Before I was passing this object I was calling it successfully so I think the routing is generally OK. I tried a type converter, but that didn't help, still a 404. Does anyone see what I'm missing/doing wrong?

nasch
  • 5,330
  • 6
  • 31
  • 52

3 Answers3

6

Answer

You are using the wrong uri. You need api/titleEstimate/getTitleEstimate. That explains and will resolve your 404.

Answer to Follow Up Question

Everything else you're doing almost works. After you resolve the 404, you'll find that you aren't receiving the value FromUri, and you'll have a follow up question. So, you need to change your route config to this:

config.Routes.MapHttpRoute(
    name: "Default",
    routeTemplate: "api/{controller}/{action}"
);

Then you'll not only resolve your 404 but also receive the FromUri value that you're sending as query string parameters.

Demo

Here is a fiddle for evidence, which is calling into this controller, which is hosted LIVE here. The only thing I changed in the working version are the uri to resolve the 404 and the route config to make sure you receive the FromUri value. That's all.

HTML

<label>Username:
    <input id="user" />
</label>
<button id="submit">Submit</button>
<button id="submit-fail">Submit Fail</button>
<div id="product"></div>

JavaScript

This will succeed.

Note, we only need domain because we doing cross-site scripting here for the demo purpose.

var domain = "https://cors-webapi.azurewebsites.net";

$("#submit").click(function () {

    var uri = domain + '/api/titleEstimate/getTitleEstimate';

    var query = {
        "username": $("#user").val(),
        // other fields
    };

    $.getJSON(uri, query)
        .done(function (data) {
        $('#product').text(data);
    })
        .fail(function (jqXHR, textStatus, err) {
        $('#product').text('Error: ' + err);
    });
});

This will 404.

$("#submit-fail").click(function () {

    var uri = domain + '/api/titleEstimate';

    var query = {
        "username": $("#user").val(),
        // other fields
    };

    $.getJSON(uri, query)
        .done(function (data) {
        $('#product').text(data);
    })
        .fail(function (jqXHR, textStatus, err) {
        $('#product').text('Error: ' + err);
    });
});

Controller

public class TitleEstimateController : ApiController
{
    public class EstimateQuery
    {
        public string username { get; set; }
    }

    public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
    {
        // All the values in "query" are null or zero
        // Do some stuff with query if there were anything to do
        if(query != null && query.username != null)
        {
            return Ok(query.username);
        }
        else
        {
            return Ok("Add a username!");
        }
    }        
}

You can read more details about WebApi routing here. From reading it you can probably come up with an alternative solution within your route config. There are also lots of terrific examples in this blog post.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 2
    Thank you very much for taking the time to put together such a detailed answer. I had high hopes, but when I ran the fiddle and pressed Submit (with or without a value) I get Error: Not Found. So I think there's still something wrong with the routing there. – nasch May 01 '15 at 16:50
  • @nasch Fixed :-). I was using an old `uri`, and when I updated the fiddle, I forgot to set the new version as base. That is, I was using `/api/values/GetTitleEstimate` instead of `/api/titleEstimate/GetTitleEstimate`. The linked fiddle will now work, because I have set the most recent version as base. – Shaun Luttin May 01 '15 at 17:00
  • Fantastic, it's working now, thank you! One thing that's puzzling me is that I can't get the results to display on the web page. I have this just like you: `$('#product').text(data);` but it's not doing anything. I can log or alert with the value and it's fine, but the text() call doesn't work. I tried putting in a string literal value, moving it to the beginning of the function, putting it in document.ready(), no effect. I'm sure it's something stupid but if you have any idea what that might I'd be glad to hear it. The div is this: `
    Information goes here
    `
    – nasch May 01 '15 at 18:53
  • You're welcome. I'm not sure why the results won't display for you. You've tried most of the troubleshoot steps that I would try. E.g. log, alert, etc. I might try it with another `div` and another `id`. Also, create a fiddle that replicates the mistake; that way you can share exactly the code that's producing the error. – Shaun Luttin May 01 '15 at 19:24
  • OK thanks, if nothing occurs to me I may open up another question, but the web page isn't a critical part of the project. Thanks again for your help. – nasch May 01 '15 at 21:53
  • This is a great example/method and description for this sort of web-api call. Thank you so much for writing it up. – secretwep Aug 26 '16 at 00:26
0

For complex objects, I usually send them in the message body rather than the URL.

Would you have any objection to an approach similar to the answer of this question? How to pass json POST data to Web API method as object

It seems like the more straightforward/natural approach.

Something like (untested, but should be close):

[RoutePrefix("api/titleestimate")]
public class TitleEstimateController : ApiController
{
   [HttpGet]
    public IHttpActionResult GetTitleEstimate([FromBody] EstimateQuery query)
    {
        // All the values in "query" are null or zero
        // Do some stuff with query if there were anything to do
     }
}

public class EstimateQuery
{
    public string QueryName{ get; set; }
    public string Whatever{ get; set; }
}

$(function () {
   var query = {QueryName:"My Query",Whatever:"Blah"};
   $.ajax({
       type: "GET",
       data :JSON.stringify(query),
       url: "api/titleEstimate",
       contentType: "application/json"
   })
      .done(function (data) {
          $('#product').text("OK");
      })
      .fail(function (jqXHR, textStatus, err) {
        $('#product').text('Error: ' + err);
      });
});
Community
  • 1
  • 1
Colin
  • 4,025
  • 21
  • 40
  • Thanks, I tried this version with 'JSON.stringify(query)' and it's still giving me a 404. Since it's a 404 and not a 500 or something, it seems that there's something about passing this parameter that the Web API doesn't like. It can't find the controller method to call. – nasch Apr 30 '15 at 21:51
  • This answer won't work either because its uri is also incorrect. – Shaun Luttin May 01 '15 at 02:44
0

First, I would try to use the attribute routing feature of web.api like this:

[RoutePrefix("api/titleestimate")]
public class TitleEstimateController : ApiController
{
    [HttpGet]
    public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
    {
            // All the values in "query" are null or zero
            // Do some stuff with query if there were anything to do
    }
}

It would also be helpful to see the request in your dev tools. I disagree with Colin that you should make this a POST, because HTTP POST is supposed to be used to create new items. You are trying to get information so HTTP GET makes sense.

I think Web.Api assumes methods are GETs by default, but declarating it with the HttpGet attribute will for sure take care of that.

peinearydevelopment
  • 11,042
  • 5
  • 48
  • 76
  • Small detail, but decent point. I'll adjust my answer to make that improvement. – Colin Apr 30 '15 at 21:17
  • I tried adding those but it didn't seem to do much. I'm now getting lots of debug output about PDB files and skipping loading symbols, but no errors. Here's Chrome's message: `GET http://localhost:47503/api/titleEstimate/?username=whatever…&StreetSuffix=Street&County=Boulder&IsPrimary=on&State=CO&Unit=&Zip=80303 404 (Not Found)` I'm willing to use POST if that's what it takes but I agree, GET makes more sense here and it seems like it should work. – nasch Apr 30 '15 at 21:48
  • I've worked through these types of errors many times. Its hard to say without having all of the information though. For instance, username is lowercase, but all the others start with an UpperLetter. I usually also put a [Route("Blah")] attribute on my controller method and then your code should save var uri = 'api/titleEstimate/blah'; It has also depended on my browser before. Chrome seems to use default headers as application/json and Firefox seems to use application/xml by default. Look at the headers, check casing and be more explicit on your routes and see if that helps at all. – peinearydevelopment May 01 '15 at 01:53
  • You don't need `HttpGet`. It isn't necessary. – Shaun Luttin May 01 '15 at 03:21