Web API 的 Put 请求生成了 Http 405 方法不允许的错误。

155

这是我在 ASP.NET MVC 前端中从 Web API 调用 PUT 方法的代码,即方法中的第三行:

enter image description here

client.BaseAddresshttp://localhost/CallCOPAPI/

这是 contactUri

enter image description here

这里是 contactUri.PathAndQuery

enter image description here

最后,这是我的 405 响应:

enter image description here

这是我的 Web API 项目中的 WebApi.config 文件:

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

            config.Routes.MapHttpRoute(
                name: "DefaultApiGet",
                routeTemplate: "api/{controller}/{action}/{regionId}",
                defaults: new { action = "Get" },
                constraints: new { httpMethod = new HttpMethodConstraint("GET") });

            var json = config.Formatters.JsonFormatter;
            json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
            config.Formatters.Remove(config.Formatters.XmlFormatter);

我已经尝试将传递给PutAsJsonAsync的路径简化为 string.Format("/api/department/{0}", department.Id)string.Format("http://localhost/CallCOPAPI/api/department/{0}", department.Id),但都没有成功。

有人有任何想法,为什么我会收到405错误吗?

更新

根据请求,这是我的Department控制器代码(我将同时发布我的前端项目Department控制器代码以及WebAPI的Department ApiController代码):

前端部门控制器

namespace CallCOP.Controllers
{
    public class DepartmentController : Controller
    {
        HttpClient client = new HttpClient();
        HttpResponseMessage response = new HttpResponseMessage();
        Uri contactUri = null;

        public DepartmentController()
        {
            // set base address of WebAPI depending on your current environment
            client.BaseAddress = new Uri(ConfigurationManager.AppSettings[string.Format("APIEnvBaseAddress-{0}", CallCOP.Helpers.ConfigHelper.COPApplEnv)]);

            // Add an Accept header for JSON format.
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
        }

        // need to only get departments that correspond to a Contact ID.
        // GET: /Department/?regionId={0}
        public ActionResult Index(int regionId)
        {
            response = client.GetAsync(string.Format("api/department/GetDeptsByRegionId/{0}", regionId)).Result;
            if (response.IsSuccessStatusCode)
            {
                var departments = response.Content.ReadAsAsync<IEnumerable<Department>>().Result;
                return View(departments);
            }
            else
            {
                LoggerHelper.GetLogger().InsertError(new Exception(string.Format(
                    "Cannot retrieve the list of department records due to HTTP Response Status Code not being successful: {0}", response.StatusCode)));
                return RedirectToAction("Index");
            }

        }

        //
        // GET: /Department/Create

        public ActionResult Create(int regionId)
        {
            return View();
        }

        //
        // POST: /Department/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(int regionId, Department department)
        {
            department.RegionId = regionId;
            response = client.PostAsJsonAsync("api/department", department).Result;
            if (response.IsSuccessStatusCode)
            {
                return RedirectToAction("Edit", "Region", new { id = regionId });
            }
            else
            {
                LoggerHelper.GetLogger().InsertError(new Exception(string.Format(
                    "Cannot create a new department due to HTTP Response Status Code not being successful: {0}", response.StatusCode)));
                return RedirectToAction("Edit", "Region", new { id = regionId });
            }
        }

        //
        // GET: /Department/Edit/5

        public ActionResult Edit(int id = 0)
        {
            response = client.GetAsync(string.Format("api/department/{0}", id)).Result;
            Department department = response.Content.ReadAsAsync<Department>().Result;
            if (department == null)
            {
                return HttpNotFound();
            }
            return View(department);
        }

        //
        // POST: /Department/Edit/5

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(int regionId, Department department)
        {
            response = client.GetAsync(string.Format("api/department/{0}", department.Id)).Result;
            contactUri = response.RequestMessage.RequestUri;
            response = client.PutAsJsonAsync(string.Format(contactUri.PathAndQuery), department).Result;
            if (response.IsSuccessStatusCode)
            {
                return RedirectToAction("Index", new { regionId = regionId });
            }
            else
            {
                LoggerHelper.GetLogger().InsertError(new Exception(string.Format(
                    "Cannot edit the department record due to HTTP Response Status Code not being successful: {0}", response.StatusCode)));
                return RedirectToAction("Index", new { regionId = regionId });
            }
        }

        //
        // GET: /Department/Delete/5

        public ActionResult Delete(int id = 0)
        {
            response = client.GetAsync(string.Format("api/department/{0}", id)).Result;
            Department department = response.Content.ReadAsAsync<Department>().Result;

            if (department == null)
            {
                return HttpNotFound();
            }
            return View(department);
        }

        //
        // POST: /Department/Delete/5

        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int regionId, int id)
        {
            response = client.GetAsync(string.Format("api/department/{0}", id)).Result;
            contactUri = response.RequestMessage.RequestUri;
            response = client.DeleteAsync(contactUri).Result;
            return RedirectToAction("Index", new { regionId = regionId });
        }
    }
}

Web API 部门 ApiController

namespace CallCOPAPI.Controllers
{
    public class DepartmentController : ApiController
    {
        private CallCOPEntities db = new CallCOPEntities(HelperClasses.DBHelper.GetConnectionString());

        // GET api/department
        public IEnumerable<Department> Get()
        {
            return db.Departments.AsEnumerable();
        }

        // GET api/department/5
        public Department Get(int id)
        {
            Department dept = db.Departments.Find(id);
            if (dept == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return dept;
        }

        // this should accept a contact id and return departments related to the particular contact record
        // GET api/department/5
        public IEnumerable<Department> GetDeptsByRegionId(int regionId)
        {
            IEnumerable<Department> depts = (from i in db.Departments
                                             where i.RegionId == regionId 
                                             select i);
            return depts;
        }

        // POST api/department
        public HttpResponseMessage Post(Department department)
        {
            if (ModelState.IsValid)
            {
                db.Departments.Add(department);
                db.SaveChanges();

                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, department);
                return response;
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }

        // PUT api/department/5
        public HttpResponseMessage Put(int id, Department department)
        {
            if (!ModelState.IsValid)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }

            if (id != department.Id)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }

            db.Entry(department).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // DELETE api/department/5
        public HttpResponseMessage Delete(int id)
        {
            Department department = db.Departments.Find(id);
            if (department == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            db.Departments.Remove(department);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK, department);
        }
    }
}

你不应该在动作方法定义之前使用 [HttpPut] 吗?(如果适用,也可以使用 [HttpPost][HttpDelete] - Chris Pratt
@ChrisPratt 为了明确起见,您是指在WebAPI控制器(ApiController)上放置[HttpPut],对吗?因为Department的前端控制器(Edit方法)具有[HttpPost]属性。 - Mike Marks
1
@ChrisPratt WebAPI模板自带的ValuesController在其Put/Post/Delete方法上没有[HttpPut]等属性。 - Mike Marks
是的,我相当确定它需要在Web API端使用这些。就个人而言,我一直都是为Web API使用AttributeRouting,所以我的回忆有点模糊。 - Chris Pratt
显然这是WebDAV的问题。我检查了我的本地IIS(Windows功能),以确保它没有安装,结果显示它没有...无论如何,我发布了一个答案,基本上是在我的web.config中删除WebDAV模块。 - Mike Marks
显示剩余2条评论
10个回答

338

所以,我检查了Windows功能,确保我没有安装这个叫做WebDAV的东西,它说我没有。 无论如何,我继续在我的web.config文件中添加了以下内容(前端和WebAPI都加了一遍,以确保),现在它可以工作了。 我将其放置在<system.webServer>标签内。

<modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule"/> <!-- add this -->
</modules>

此外,通常需要将以下内容添加到 web.config 的处理程序中。感谢Babak。

<handlers>
    <remove name="WebDAV" />
    ...
</handlers>

2
哈哈...是啊...我差点放弃了。所以,是的。在你的applicationhost.config中必须启用WebDAV。很高兴你已经解决了它。 - Aron
9
你可能还需要添加这个内容:<handlers><remove name="WebDAV" />... - Babak
14
我只在我的WebApi的web.config文件中添加了这个,然后它就起作用了。 - Fordy
2
感谢您对这个非常烦人的问题的回答。为什么会出现这种情况呢? - Scott Wilson
我在IIS上托管了WordPress。我正在使用WordPress JSON API v2,并尝试使用DELETE请求删除文章。但这对我没有帮助 :( - Vlado Pandžić
显示剩余8条评论

26

3
对我来说又是一个考验:PUT动作无法将数据绑定到原始类型参数。我不得不将 public int PutFluffColor(int Id, int colorCode) 改为 public int PutFluffColor(int Id, UpdateFluffColorModel model) - Josh Noe
4
愿意给这个WebDav-SchmebDav点赞两次,可惜不能。 - Noel
1
经过8个多小时的工作,我们终于找到了解决方案。每个人都推荐修改web.config文件,这真是太神奇了,甚至没有人谈论过这种可能性。 - sairfan

23

将以下代码添加至你的web.config文件中。你需要告诉IIS如何理解PUTPATCHDELETEOPTIONS请求方式,并指定相应的IHttpHandler处理程序。

<configuation>
    <system.webServer>
    <handlers>
    <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
    <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    </system.webServer>
</configuration>

同时请检查您是否未开启WebDAV功能。


我已经有了。我假设这是要放在Web API项目中,而不是我的前端MVC项目中,对吗? - Mike Marks
我没有安装WebDAV。此外,您是说上面的web.config代码需要放置在调用Web API的项目的web.config中吗? - Mike Marks
它实际上在两个web.config文件中... :( - Mike Marks
1
你能把DepartmentController的代码清单全部贴出来吗?问题出在你的Web API项目上,它不知道如何处理PUT,这就是405的意思。检查一下GET是否可用,以排除路由问题。PS.尝试复制粘贴代码而不是截图。PPS,请勿使用Task.Result,否则在某些情况下会出现无关的线程问题。最好将整个方法转换为async await。更不用说它会创建同步、多线程阻塞的代码(比单线程慢)。 - Aron
我发布了控制器代码(普通前端控制器和Web API ApiController)。 - Mike Marks
显示剩余5条评论

21

我正在IIS 8.5上运行一个ASP.NET MVC 5应用程序。我尝试了所有在这里发布的变体,这是我的web.config的内容:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
        <remove name="WebDAVModule"/> <!-- add this -->
    </modules>  
    <handlers>      
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="WebDAV" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers> 
</system.webServer>

由于我没有管理员权限,无法卸载我的服务器上的WebDav。有时候,我会在.css和.js文件上遇到“方法不允许”的错误。最终,通过以上配置设置,一切都重新开始工作了。


在我的.NET 4.7.2和IIS 6.2上运行得非常好。 - GrahamJ

5

在其中一个操作参数上使用 [FromBody] 装饰符解决了我的问题:

public async Task<IHttpActionResult> SetAmountOnEntry(string id, [FromBody]int amount)

然而,如果在方法参数中使用了复杂对象,ASP.NET将会自动推断其正确性:

public async Task<IHttpActionResult> UpdateEntry(string id, MyEntry entry)

1
另一个可能的原因是您未使用默认变量名称"id",实际上应该是:id。

0

这个简单的问题可能会引起真正的头痛!

我可以看到你的控制器 EDIT (PUT) 方法需要两个参数:a) 一个 int id,和 b) 一个部门对象。

这是从 VS 中生成带有读/写选项的控制器时的默认代码。但是,你必须记住使用这两个参数来调用此服务,否则你将会得到错误 405。

在我的情况下,我不需要 PUT 的 id 参数,所以我只是从头文件中删除了它... 在几个小时内没有注意到它的存在!如果你保留它,那么名称也必须保留为 id,除非你继续对配置进行必要的更改。


0
在我的情况下,错误405是由静态处理程序引发的,原因是路由("api/images")与同名文件夹("~/images")发生冲突。

0

您可以在IIS中手动删除特定的WebDAV模块。


步骤如下:

1. 进入IIS。
2. 进入相应的站点。
3. 打开“处理程序映射”。
4. 向下滚动并选择WebDAV模块。右键单击它并将其删除。

注意:这也会更新您的Web应用程序的web.config文件。


-1

您的客户端应用程序和服务器应用程序必须在同一域下,例如:

客户端 - localhost

服务器 - localhost

而不是:

客户端 - localhost:21234

服务器 - localhost


2
我并不这样认为。创建服务的目的是从另一个域调用。 - Ozan BAYRAM
你正在考虑跨域请求,这将从服务器获得一个200响应,但浏览器会执行其“禁止跨域请求”的规则,并拒绝接受响应。该问题涉及到405“方法不允许”响应,是一个不同的问题。 - Josh Noe
CORS会返回405“方法不允许”,例如:请求URL:http://testapi.nottherealsite.com/api/Reporting/RunReport 请求方法:OPTIONS 状态码:405方法不允许请阅读此处https://dev59.com/12cs5IYBdhLWcg3w84Zo - Lev K.
您正在提到CORS问题。 - Rahul Jain

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