5

I have an MVC application with a few simple pages that will mostly run on Web API calls. For simplicity I want to include them in the same project. I can launch and navigate to my page just fine, but when I try to call my API through Ajax I keep getting a 404 error - it can't find the API function.

Here is my javascript file:

$(document).ready(function () {
    //set the login button to call our test API function
    document.getElementById("login_submit").addEventListener("click", GetUser);

});

function GetUser() {
    var response = $.ajax({        
        url: '/api/User',
        method: 'GET',
        contentType: 'application/json; charset=utf-8',
        success: function (data) {
            alert("Success!");
        },
        error: function (request, status, error) {
            alert(error);
        }
    });
}

And here is my controller:

namespace MyProject.Controllers.API
{
    public class UserController : ApiController
    {
        // GET api/<controller>
        [HttpGet]        
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
        [HttpGet]        
        public string Get(int id)
        {
            return "value";
        }
    }
}

The API Controllers are in their own folder (called "API") inside my Controllers folder in my project - that's why the namespace contains "API" on this example controller.

When I use F12 on the browser to capture the request getting sent, I can see that my call has the following info:

Request URL: http://localhost:50035/api/User
Request Method: GET
Status Code: 404 / Not Found

Now my understanding is that this should find the API with the name UserController and find the function with the [HttpGet] tag with no arguments, then return the string array ("value1", "value2"). Instead it doesn't find anything.

As a final note, here is my routing config (and yes, it is being initialized on Global.asax):

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

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

UPDATE:

Based on the feedback I've received so far, I moved my Global.asax configuration around. It now looks like this:

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

    }

Now when I call my API function, I get... Pending. It doesn't return a success message. It just hangs. I do not get the "Success!" alert.

Arghya C
  • 9,805
  • 2
  • 47
  • 66
Max
  • 849
  • 9
  • 24
  • 2
    What is the order you register routes in Global.asax Application_Start() method? IF you are registering MVC routes before WebAPI roues, then api routes will fail! – Arghya C Sep 10 '15 at 22:10
  • 2
    Reproduced the problem by registering MVC routes _before_ WebApi routes. – Andrea Scarcella Sep 10 '15 at 22:21
  • If you put a breakpoint in the Get() method of the User controller, does it get hit? – Andrea Scarcella Sep 10 '15 at 22:41
  • 1
    Yes, the breakpoint on Get() is getting hit. Interestingly, trying codeMonkey's suggestion below worked (which is counter to what I think I understand so far about API calls), and I am no longer getting the hanging "Pending" requests. It looks like it's working fine now? – Max Sep 10 '15 at 22:44
  • Love you forever if you upvote my comment then ;) – codeMonkey Sep 10 '15 at 22:56
  • possible duplicate of [ASP.NET Web API 2 - POST / jQuery](http://stackoverflow.com/questions/31920716/asp-net-web-api-2-post-jquery) – codeMonkey Sep 10 '15 at 23:04

2 Answers2

2

Though I'm not sure it's the same problem that I've guessed in my comment, but it is a probable reason. So anyone facing similar problem, this is the issue.

MVC routing engine tries to match incoming requests with routes in the same order they were registered.

  1. So, if you register MVC route first - {controller}/{action}/{id} id:optional
  2. And, then register the WebAPI route - api/{controller}/{id} id:optional

Then incoming requests will be matched with the MVC route and IF it does NOT MATCH a route PATTERN, then only it will be matched against the WebAPI route.

Now, if you have a request like api/User, it will MATCH with the MVC route pattern, and will NOT be matched against the WebAPI route. As a result, MvcHandler will try to create a ApiController MVC controller class and invoke User() method on that. As a result, client will get 404 - resource not found!

Additionally, if you are not using attribute routing, you might want to remove/comment this line

//config.MapHttpAttributeRoutes();

And for safer http-verb-to-api-method routing, you can add the following in your api config.

routes.MapHttpRoute("RestApiRoute", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" }); //this replaces your current api route
routes.MapHttpRoute("ApiWithActionRoute", "Api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
routes.MapHttpRoute("DefaultApiGetRoute", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(new string[] { "GET" }) });
routes.MapHttpRoute("DefaultApiPostRoute", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(new string[] { "POST" }) });
routes.MapHttpRoute("DefaultApiPutRoute", "Api/{controller}", new { action = "Put" }, new { httpMethod = new HttpMethodConstraint(new string[] { "PUT" }) });
routes.MapHttpRoute("DefaultApiDeleteRoute", "Api/{controller}", new { action = "Delete" }, new { httpMethod = new HttpMethodConstraint(new string[] { "DELETE" }) });

First two routes make it possible to call the end points in [1] pure REST way & to [2] call them with method name (Not aligned to REST standards though!)

Arghya C
  • 9,805
  • 2
  • 47
  • 66
  • Thank you - this was very helpful. I moved my code around and now I can get past the initial error I was getting, but I'm still not getting my "Success!" alert. Please see my edit for more info. – Max Sep 10 '15 at 22:37
  • @user2291983 I do not see WebApiConfig.Register() in your app start method, am I missing something? And, can you just hit http://localhost:50035/api/User (adjust the port number if that has changed) from your browser address bar, and see what happens? – Arghya C Sep 10 '15 at 22:44
  • 1
    The WebApiConfig.Register is in the GlobalConfiguration.Configure() call. I honestly don't know what that does but all of my books did it that way. The URL call worked as expected - I got a JSON object back containing the string array. – Max Sep 10 '15 at 22:47
  • @user2291983 Ok. Good your function is working now. Btw, you might use .done() & .fail() in your jQuery, instead of .success() & .error(). Please refer http://api.jquery.com/jquery.ajax/ – Arghya C Sep 10 '15 at 22:52
  • Added complete set of api routes, that can handle all type of api calls (pure REST and non-REST with method name). – Arghya C Sep 10 '15 at 23:09
1

Try

/api/User/Get 

instead of just /api/user. If you want to get fancy with it, check out [RoutePrefix] and [Route] so you can manage this stuff by hand.

codeMonkey
  • 4,134
  • 2
  • 31
  • 50
  • 1
    This worked but it goes counter to what I thought I understood about API calls. User is the controller name, and I'm trying to call the GET function with no parameters by using an HTTP Get request. – Max Sep 10 '15 at 22:45
  • @user2291983 mark it as right answer anyway? :) I know, MS isn't so hot on explaining this stuff, especially when it shows you the default routing architecture. That's just how it is, though . . . /{controllerName}/{actionName}/{parameter} or /?parameter=XXX . Once you start making custom APIs (with names other than 'GET' and 'POST') it'll make more sense. – codeMonkey Sep 10 '15 at 22:51