ChatGPT解决这个技术问题 Extra ChatGPT

Web API Put Request generates an Http 405 Method Not Allowed error

Here's the call to the PUT method on my Web API - the third line in the method (I am calling the Web API from an ASP.NET MVC front end):

https://i.stack.imgur.com/V9aat.jpg

client.BaseAddress is http://localhost/CallCOPAPI/.

Here's contactUri:

https://i.stack.imgur.com/VsuVL.jpg

Here's contactUri.PathAndQuery:

https://i.stack.imgur.com/Vd8bT.jpg

And finally, here's my 405 response:

https://i.stack.imgur.com/VtCX4.jpg

Here's the WebApi.config in my Web API project:

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

I've tried stripping down the path that gets passed into PutAsJsonAsync to string.Format("/api/department/{0}", department.Id) and string.Format("http://localhost/CallCOPAPI/api/department/{0}", department.Id) with no luck.

Does anyone have any ideas why I'm getting the 405 error?

UPDATE

As per request, here's my Department controller code (I will post both the Department controller code for my front end project, as well as the Department ApiController code for the WebAPI):

Front End Department Controller

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 Department 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);
        }
    }
}
Shouldn't you be using [HttpPut] before the action method definition? ([HttpPost] and [HttpDelete] where appropriate as well)
@ChrisPratt Just to be clear, you mean put [HttpPut] on the WebAPI controller (ApiController), right? Because the front end controller for Department (Edit method) has an [HttpPost] attribute.
@ChrisPratt The ValuesController (the one that comes with the WebAPI template) does not have [HttpPut], etc. attributes on the Put/Post/Delete methods..
Yes, I'm reasonably sure it needs those on the Web API side. Personally, I've always just used AttributeRouting for Web API stuff, so my recollection is a little sketchy.
Apparently it was the WebDAV thing.. I checked my local IIS (Windows Features) to ensure it wasn't installed and it said it wasn't... anyways I posted an answer to this, basically removing the module WebDAV inside my web.config.

C
Christopher Wirt

So, I checked Windows Features to make sure I didn't have this thing called WebDAV installed, and it said I didn't. Anyways, I went ahead and placed the following in my web.config (both front end and WebAPI, just to be sure), and it works now. I placed this inside <system.webServer>.

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

Additionally, it is often required to add the following to web.config in the handlers. Thanks to Babak

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

Haha...yeah...I was about to give up. So yeah. WebDAV must have been enabled in your applicationhost.config. Glad you've fixed it.
You may also need to add this, too: <handlers><remove name="WebDAV" />...
Added this only to my WebApi web.config and it worked.
Even though in IE10 it worked fine even without this config, I had to do only in WebApi web.config to make it work in Chrome browser.
Thanks for the answer to this really annoying problem. Why does this occur in the first place?
M
Molibar

WebDav-SchmebDav.. ..make sure you create the url with the ID correctly. Don't send it like http://www.fluff.com/api/Fluff?id=MyID, send it like http://www.fluff.com/api/Fluff/MyID.

Eg.

PUT http://www.fluff.com/api/Fluff/123 HTTP/1.1
Host: www.fluff.com
Content-Length: 11

{"Data":"1"}

This was busting my balls for a small eternity, total embarrassment.


An additional ball buster for me: PUT actions can't bind data to primitive type parameters. I had to change public int PutFluffColor(int Id, int colorCode) to public int PutFluffColor(int Id, UpdateFluffColorModel model)
Wish I could upvote this twice for the WebDav-SchmebDav
after more than 8 hours of work reach to solution, every one is recommending web.config changes its so amazing no body even did not talk about this possibility .
A
Aron

Add this to your web.config. You need to tell IIS what PUT PATCH DELETE and OPTIONS means. And which IHttpHandler to invoke.

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

Also check you don't have WebDAV enabled.


I already have that. I am assuming this is to be put in the Web API project, not my front end MVC project, right?
I don't have WebDAV installed. Additionally, are you saying that the web.config code above needs to be placed in the web.config of the project that makes the call to the Web API?
It's actually in both web.configs... :(
Oh no...I thought you were referencing a Web API project from an MVC project.
Can you post up the code listing of the DepartmentController? All of it. The problem lies in your Web API project, and it doesn't know how to handle PUT, that is what 405 means. Check that GET works, just to rule out routing. PS. Try to copy paste code rather than screenshot. PPS, DO NOT USE Task.Result, you'll get unrelated threading issues in certain situations. Just turn the whole method into async await instead. Not to mention it creates synchronous, multithreaded blocked code (slower than single threaded).
j
jpgrassi

I'm running an ASP.NET MVC 5 application on IIS 8.5. I tried all the variations posted here, and this is what my web.config looks like:

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

I couldn't uninstall WebDav from my Server because I didn't have admin privileges. Also, sometimes I was getting the method not allowed on .css and .js files. In the end, with the configuration above set up everything started working again.


Worked a treat for me on .NET 4.7.2 and IIS 6.2
В
Владимiръ

Decorating one of the action params with [FromBody] solved the issue for me:

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

However ASP.NET would infer it correctly if complex object was used in the method parameter:

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

A
Adam Levitt

Another cause of this could be if you don't use the default variable name for the "id" which is actually: id.


P
Petr Šugar

In my case the error 405 was invoked by static handler due to route ("api/images") conflicting with the folder of the same name ("~/images").


N
Naveen Kumar G C

You can remove webdav module manually from GUI for the particular in IIS. 1) Goto the IIs. 2) Goto the respective site. 3) Open "Handler Mappings" 4) Scroll downn and select WebDav module. Right click on it and delete it.

Note: this will also update your web.config of the web app.


H
Hannington Mambo

This simple problem can cause a real headache!

I can see your controller EDIT (PUT) method expects 2 parameters: a) an int id, and b) a department object.

It is the default code when you generate this from VS > add controller with read/write options. However, you have to remember to consume this service using the two parameters, otherwise you will get the error 405.

In my case, I did not need the id parameter for PUT, so I just dropped it from the header... after a few hours of not noticing it there! If you keep it there, then the name must also be retained as id, unless you go on to make necessary changes to your configurations.


L
Lev K.

Your client application and server application must be under same domain, for example :

client - localhost

server - localhost

and not :

client - localhost:21234

server - localhost


I dont think so. The aim of creating a service is to call from another domain
You're thinking of a cross domain request, which will give you a 200 response from the server, but the browser will enforce its "no cross domain requests" rule and not accept the response. The question is referring to a 405 "Method Not Allowed" response, a different issue.
CORS will give 405 "Method Not Allowed", for example: Request URL:testapi.nottherealsite.com/api/Reporting/RunReport Request Method:OPTIONS Status Code:405 Method Not Allowed please read here stackoverflow.com/questions/12458444/…
You are referring to CORS issue.