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

10

155

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

enter image description here

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

Here's contactUri:

enter image description here

Here's contactUri.PathAndQuery:

enter image description here

And finally, here's my 405 response:

enter image description here

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);
        }
    }
}
Homegrown answered 3/10, 2013 at 15:13 Comment(7)
Shouldn't you be using [HttpPut] before the action method definition? ([HttpPost] and [HttpDelete] where appropriate as well)Savino
@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.Homegrown
@ChrisPratt The ValuesController (the one that comes with the WebAPI template) does not have [HttpPut], etc. attributes on the Put/Post/Delete methods..Homegrown
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.Savino
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.Homegrown
In my case it was 'cause a tried to connect via http instead of https. ;-DNarrow
Please look at https://mcmap.net/q/151834/-asp-net-web-api-405-http-verb-used-to-access-this-page-is-not-allowed-how-to-set-handler-mappingsFortitude
H
338

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>
Homegrown answered 3/10, 2013 at 16:17 Comment(13)
Haha...yeah...I was about to give up. So yeah. WebDAV must have been enabled in your applicationhost.config. Glad you've fixed it.Gemination
You may also need to add this, too: <handlers><remove name="WebDAV" />...Bumkin
Added this only to my WebApi web.config and it worked.Ornithic
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.Lichter
For reference, Hanselman's warning about RAMMFAR: "You want to avoid having this option turned on if your configuration and architecture can handle it. This does exactly what it says. All managed modules will run for all requests. That means *.* folks. PNGs, PDFs, everything including static files ends up getting seen by ASP.NET and the full pipeline. If you can let IIS handle a request before ASP.NET sees it, that's better."Hamite
I have a doubt. I removed only the WebDAV module and it works for me. It really necessary to remove the handler too? When I need to remove the handler?Erine
Any other solution? When I access WebAPI over POST from HttpClient it fails ... from Postman it worksSubgroup
Thanks for the answer to this really annoying problem. Why does this occur in the first place?Munt
I hosted wordpress hosted on IIS. I am using wordpress json API v2, and I try to delete post using DELETE request. This not help for me :(Redding
I used it without the infamous runAllManagedModulesForAllRequests (just used it like this: <modules><remove name="WebDAVModule" /></modules> <handlers>remove name="WebDAV" /></handlers>) and it solved the problem for me!Amati
@Ornithic , when I do this I get 500.21 "has a bad module "ManagedPipelineHandler" in its module list"Shark
Took me 3 hours trying to figure out why it was working on 3 servers and not 1, all down to this windows feature. Thanks.Nenitanenney
Thanks @Mike Marks for the answer. In my case, just adding the below code worked perfect - <modules runAllManagedModulesForAllRequests="true"> <remove name="WebDAVModule"/> </modules>Spirit
K
27

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.

Klehm answered 29/8, 2014 at 23:29 Comment(3)
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)Eastern
Wish I could upvote this twice for the WebDav-SchmebDavDusa
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 .Eba
G
23

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.

Gemination answered 3/10, 2013 at 15:24 Comment(10)
I already have that. I am assuming this is to be put in the Web API project, not my front end MVC project, right?Homegrown
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?Homegrown
It's actually in both web.configs... :(Homegrown
Oh no...I thought you were referencing a Web API project from an MVC project.Gemination
so here's the architecture. I have a WebAPI project on one server, and my front end MVC project on another. Locally, they're both on localhost. So anyways, I make the call (the Http Request) to the WebAPI from my front end MVC project as indicated in the Edit method above (the Edit method above is in my MVC front end project).Homegrown
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).Gemination
do you want to see the Department controller for my front end, or the Department controller (ApiController) for my WebAPI?Homegrown
I posted both controller code (normal front end controller, and the Web API ApiController).Homegrown
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.Homegrown
if you want to form an answer saying to remove WebDAV module from the web.config (like my answer to this question), I'll mark it as the right answer.Homegrown
G
21

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.

Gooey answered 22/1, 2016 at 12:32 Comment(1)
Worked a treat for me on .NET 4.7.2 and IIS 6.2Pussyfoot
E
5

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)
Effectually answered 25/5, 2016 at 22:15 Comment(0)
A
1

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

Attempt answered 27/7, 2016 at 22:58 Comment(0)
J
0

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").

Joyance answered 24/9, 2017 at 10:15 Comment(0)
C
0

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.

Cohleen answered 20/12, 2019 at 9:31 Comment(0)
W
0

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.

Wollastonite answered 13/12, 2020 at 19:33 Comment(0)
G
-1

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

client - localhost

server - localhost

and not :

client - localhost:21234

server - localhost

Gwen answered 15/3, 2015 at 9:30 Comment(4)
I dont think so. The aim of creating a service is to call from another domainWeichsel
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.Eastern
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 #12458944Gwen
You are referring to CORS issue.Outgo

© 2022 - 2024 — McMap. All rights reserved.