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);
        }
    }
}
Mike Marks
  • 10,017
  • 17
  • 69
  • 128
  • Shouldn't you be using `[HttpPut]` before the action method definition? (`[HttpPost]` and `[HttpDelete]` where appropriate as well) – Chris Pratt Oct 03 '13 at 15:57
  • @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. – Mike Marks Oct 03 '13 at 16:03
  • 1
    @ChrisPratt The ValuesController (the one that comes with the WebAPI template) does not have `[HttpPut]`, etc. attributes on the Put/Post/Delete methods.. – Mike Marks Oct 03 '13 at 16:06
  • 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. – Chris Pratt Oct 03 '13 at 16:09
  • 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. – Mike Marks Oct 03 '13 at 16:19
  • In my case it was 'cause a tried to connect via http instead of https. ;-D – Thomas Jul 13 '18 at 08:28
  • Please look at https://stackoverflow.com/a/55134621/4746570 – BehrouzMoslem Mar 13 '19 at 14:22

10 Answers10

337

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>
Christopher Wirt
  • 1,108
  • 1
  • 10
  • 21
Mike Marks
  • 10,017
  • 17
  • 69
  • 128
  • 2
    Haha...yeah...I was about to give up. So yeah. WebDAV must have been enabled in your `applicationhost.config`. Glad you've fixed it. – Aron Oct 03 '13 at 16:20
  • 9
    You may also need to add this, too: `...` – Babak Jul 03 '14 at 21:05
  • 14
    Added this _only_ to my WebApi web.config and it worked. – Fordy Oct 16 '14 at 15:50
  • 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. – Dennis R May 08 '16 at 08:03
  • For reference, [Hanselman's warning about RAMMFAR](http://www.hanselman.com/blog/BackToBasicsDynamicImageGenerationASPNETControllersRoutingIHttpHandlersAndRunAllManagedModulesForAllRequests.aspx): "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." – harpo Jul 15 '16 at 15:45
  • 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? – Rodrigo Kiguti Jul 27 '16 at 14:43
  • Any other solution? When I access WebAPI over POST from HttpClient it fails ... from Postman it works – nhaberl May 15 '17 at 18:37
  • 2
    Thanks for the answer to this really annoying problem. Why does this occur in the first place? – Scott Wilson Jul 03 '17 at 05:27
  • 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 :( – Vlado Pandžić Oct 10 '17 at 12:43
  • I used it without the infamous runAllManagedModulesForAllRequests (just used it like this: ` remove name="WebDAV" />`) and it solved the problem for me! – BornToCode Aug 12 '18 at 18:06
  • @Fordy , when I do this I get 500.21 "has a bad module "ManagedPipelineHandler" in its module list" – KumarHarsh Sep 07 '18 at 11:24
  • 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. – apc Apr 15 '20 at 16:58
  • Thanks @Mike Marks for the answer. In my case, just adding the below code worked perfect - – Gazi May 05 '23 at 05:57
26

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.

Molibar
  • 865
  • 1
  • 9
  • 21
  • 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)` – Josh Noe May 01 '15 at 17:31
  • 4
    Wish I could upvote this twice for the WebDav-SchmebDav – Noel Dec 18 '17 at 19:28
  • 1
    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 . – sairfan Oct 29 '18 at 21:00
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.

Aron
  • 15,464
  • 3
  • 31
  • 64
  • I already have that. I am assuming this is to be put in the Web API project, not my front end MVC project, right? – Mike Marks Oct 03 '13 at 15:26
  • 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? – Mike Marks Oct 03 '13 at 15:29
  • It's actually in both web.configs... :( – Mike Marks Oct 03 '13 at 15:30
  • Oh no...I thought you were referencing a Web API project from an MVC project. – Aron Oct 03 '13 at 15:34
  • 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). – Mike Marks Oct 03 '13 at 15:36
  • 1
    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). – Aron Oct 03 '13 at 15:38
  • do you want to see the Department controller for my front end, or the Department controller (ApiController) for my WebAPI? – Mike Marks Oct 03 '13 at 15:42
  • I posted both controller code (normal front end controller, and the Web API ApiController). – Mike Marks Oct 03 '13 at 15:46
  • 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. – Mike Marks Oct 03 '13 at 16:18
  • 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. – Mike Marks Oct 03 '13 at 16:21
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.

jpgrassi
  • 5,482
  • 2
  • 36
  • 55
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)
1

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

Adam Levitt
  • 10,316
  • 26
  • 84
  • 145
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").

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.

Naveen Kumar G C
  • 1,332
  • 1
  • 10
  • 12
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.

Hannington Mambo
  • 998
  • 2
  • 13
  • 28
-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

Lev K.
  • 344
  • 4
  • 4
  • 2
    I dont think so. The aim of creating a service is to call from another domain – Ozan BAYRAM Apr 14 '15 at 21:54
  • 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. – Josh Noe May 01 '15 at 16:31
  • CORS will give 405 "Method Not Allowed", for example: Request URL:http://testapi.nottherealsite.com/api/Reporting/RunReport Request Method:OPTIONS Status Code:405 Method Not Allowed please read here http://stackoverflow.com/questions/12458444/enabling-cross-origin-resource-sharing-on-iis7 – Lev K. May 02 '15 at 17:25
  • You are referring to CORS issue. – Rahul Jain Feb 08 '20 at 11:44