Web API的GET请求出现405错误(不允许的方法)。

3
我是一位能够提供翻译的助手。下面是需要翻译的内容:

当我尝试使用$.getJSON调用我的Web API控制器方法时,我遇到了一个令人沮丧的问题:控制台总是显示以下消息:“未成功加载资源:服务器响应状态为405(方法不允许)”

这是我的控制器:

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

如果有必要的话,BaseController自然地从ApiController派生而来。以下是需要的代码:
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>();
        }
    }
}

下面是发起调用的一个函数:

$.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);
            }
        });
    });
};

我看到很多地方说路由配置可能是问题的源头,因此这里是 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 }
            );
        }
    }
}

有什么想法吗?我感觉对于Web API的工作方式,我还有很多不太清楚的地方...


尝试将link.attr('data-target-id')设置为'/api/FavoriteArticles/SetFavorite'。 - Samvel Petrosov
3个回答

2

正如其他答案中提到的那样,代码配置了错误的路由。对于Web API,请配置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 }
        );
    }
}

确保在MVC路由之前进行配置。

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

关于重构控制器使其更具RESTful风格的一些建议。尝试从操作中返回IHttpActionResult,这可以简化框架并让您更加掌控响应的返回方式。

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

控制器应该尽可能轻量化,因此即使通过将一个服务注入到控制器中,上述内容也应该进一步精简。

错误处理是一个横切关注点,应该通过框架的可扩展性点进行提取和处理。


非常感谢,那确实是我的问题,你的建议非常有帮助。 - ZipionLive

1
您正在 RouteConfig 中配置api路由,而不是 WebApiConfig 。
您需要在 App_Start 文件夹中有一个 WebApiConfig.cs 文件,并包含以下内容:
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

因此,您可以在RouteConfig.cs中删除api/{controller}/{action}/{id}routes.MapRoute()

然后在您的Global.asax中这样调用:

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

通过这样做,你的控制器示例中的两个链接都应该可以正常工作:

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

您使用 jQuery 进行调用的方式是正确的,无需做任何更改。

第二个链接无法正常工作,因为没有路由模板可以接受该动作名称。 - Nkosi
1
非常奇怪。好的。我弄清楚了。它将“SetFavorite”与默认模板中的“{id}”占位符和查询字符串匹配起来,这使得它成为一个错误的结果。如果您给该操作一个id参数,您会发现它是正确的。 - Nkosi
谢谢帮忙! - ZipionLive
@ZipionLive 不用谢,我花了一些时间才弄清楚你在使用 RouteConfig,我甚至创建了一个示例项目,但我很高兴你能解决你的问题。 - Alisson Reinaldo Silva
@ZipionLive,我认为我的答案应该被标记为已接受,而不是Nkosi的。我应该得到这个荣誉,因为我比他早大约三个小时回答了问题。他只是在我的答案上加了一些关于路由属性的建议,这很好(实际上他也承认我先回答了)。我认为他应该编辑我的答案并加入他的建议,而不是在三个小时后发布相同的答案。这只是一个想法。 - Alisson Reinaldo Silva

0

我相信默认情况下JSON请求会阻止GET方法以避免 JSON 劫持。尝试将方法转换为POST(并更新ajax调用)或更改当前函数返回

return Json(new { Success = true, Error = (string)null }, JsonRequestBehavior.AllowGet);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接