Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

405 error (Method Not Allowed) on Web API get

I'm having a frustrating problem when I try to make a call to my Web API controller method with $.getJSON : it always ends up with the following message being displayed in the console : "Failed to load resource: the server responded with a status of 405 (Method Not Allowed)"

Here's my controller :

using MyProject.Domain;
using MyProject.WebApp.Session;
using System;
using System.Linq;
using System.Web.Mvc;

namespace MyProject.WebApp.ApiControllers.Favorites
{
    public class FavoriteArticlesController : BaseController<FavoriteArticle, Guid>
    {
        [HttpGet]
        public object SetFavorite(Guid articleId, bool isFavorite)
        {
            try
            {
                if (isFavorite)
                {
                    var favorite = new FavoriteArticle
                    {
                        UserId = UserInfo.GetUserId(),
                        ArticleId = articleId
                    };
                    _repo.Upsert(favorite);
                }
                else
                {
                    var favorite = _repo.GetAll()
                        .First(fa => fa.ArticleId.CompareTo(articleId) == 0);
                    _repo.Delete(favorite.Id);
                }
                _repo.Commit();
                return new { Success = true, Error = (string)null };
            }
            catch (Exception ex)
            {
                return new { Success = false, Error = ex.Message };
            }
        }
    }
}

In case that is relevant in any way, BaseController naturally derives from ApiController. Here's the code if needed :

using MyProject.Data.Repository;
using MyProject.Data.Services;
using MyProject.Domain;
using System.Web.Http;

namespace MyProject.WebApp.ApiControllers
{
    public class BaseController<TEntity, TKey> : ApiController
        where TEntity : class, IEntity<TKey>, new()
    {
        protected UnitOfWork _unitOfWork;
        protected Repository<TEntity, TKey> _repo;

        protected BaseController()
        {
            _unitOfWork = new UnitOfWork();
            _repo = _unitOfWork.GetRepository<TEntity, TKey>();
        }
    }
}

And here's one of the functions that make the call :

$.fn.bindFavoriteArticle = function () {
    this.click(function () {
        var link = $(this);
        $.getJSON('/api/FavoriteArticles/SetFavorite', { ajax: true, articleId: link.attr('data-target-id'), isFavorite: true }, function (response) {
            if (response.Success === true) {
                link.children('i').removeClass('fa-heart-o')
                    .addClass('fa-heart');
                link.attr('data-toggle', 'unfavoriteArticle')
                    .unbind('click')
                    .bindUnfavoriteArticle();
            } else {
                // TODO : use bootstrap alert messages
                alert(response.Error);
            }
        });
    });
};

I saw here and there that the route configuration could be the source of the problem, so here's the content of RouteConfig.cs :

using System.Web.Mvc;
using System.Web.Routing;

namespace MyProject.WebApp
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                name: "ApiDefault",
                url: "api/{controller}/{action}/{id}",
                defaults: new { controller = "SubscriptionsController", action = "GetSelectList", id = UrlParameter.Optional }
            );
        }
    }
}

Any idea of what's going on ? I feel like there's a lot I'm missing concerning how Web APIs work...

like image 970
ZipionLive Avatar asked Mar 01 '26 09:03

ZipionLive


2 Answers

As mentioned in one of the other answers, the code configured the wrong routes. For web api configure the WebApiConfig

public static class WebApiConfig {
    public static void Register(HttpConfiguration config) {
        // Web API routes

        //Enable Attribute routing is they are being used.
        config.MapHttpAttributeRoutes();

        //Convention based routes.

        //Matches GET /api/FavoriteArticles/SetFavorite
        config.Routes.MapHttpRoute(
            name: "DefaultActionApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

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

Make sure that it is configured before MVC routes.

protected void Application_Start() {
    // Pass a delegate to the Configure method.
    GlobalConfiguration.Configure(WebApiConfig.Register);
    //...other configurations
}

Some advice about refactoring the controller to be a little more restful. Try to return IHttpActionResult from actions. simplifies the framework and give you more control of how the response is returned.

[RoutePrefix("api/favoritearticles"]
public class FavoriteArticlesController : BaseController<FavoriteArticle, Guid> {


    [HttpPost]
    [Route("{articleId:guid}")] //Matches POST api/favoritearticles/{articleId:guid}
    public IHttpActionResult SetFavorite(Guid articleId) {            
        var favorite = new FavoriteArticle
        {
            UserId = UserInfo.GetUserId(),
            ArticleId = articleId
        };
        _repo.Upsert(favorite);
        _repo.Commit();
        return Ok(new { Success = true, Error = (string)null });
    }

    [HttpDelete]
    [Route("{articleId:guid}")] //Matches DELETE api/favoritearticles/{articleId:guid}
    public IHttpActionResult RemoveFavorite(Guid articleId) {            
        var favorite = _repo.GetAll()
            .First(fa => fa.ArticleId == articleId);

        if(favorite == null) return NotFound();

        _repo.Delete(favorite.Id);
        _repo.Commit();
        return Ok(new { Success = true, Error = (string)null });
    }
}

Controllers should be as lean as possible so even the above should be slimmed down even more via an injected service into the controller.

Error handling is a cross-cutting concern and should also be extracted and handled via the framework's extensibility points.

like image 161
Nkosi Avatar answered Mar 02 '26 21:03

Nkosi


You are configuring api routes inside the RouteConfig rather than WebApiConfig.

You need a WebApiConfig.cs file inside your App_Start folder with this:

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

...so you can remove the routes.MapRoute() for api/{controller}/{action}/{id} inside your RouteConfig.cs.

Then in your Global.asax you call it like this:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas(); // only if you have areas...

    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

With this, both links should work fine for your controller sample:

/api/FavoriteArticles?articleId=a7d944f7-66be-4200-9b89-26eda5173dca&isFavorite=true
/api/FavoriteArticles/SetFavorite?articleId=a7d944f7-66be-4200-9b89-26eda5173dca&isFavorite=true

The way you are calling it via jQuery is correct, there is no need to change it at all.

like image 37
Alisson Reinaldo Silva Avatar answered Mar 02 '26 22:03

Alisson Reinaldo Silva