La solicitud de colocación de API web genera un error de método no permitido Http 405

134

Aquí está la llamada al PUTmétodo en mi API web: la tercera línea del método (estoy llamando a la API web desde una interfaz ASP.NET MVC):

ingrese la descripción de la imagen aquí

client.BaseAddresses http://localhost/CallCOPAPI/.

Aquí está contactUri:

ingrese la descripción de la imagen aquí

Aquí está contactUri.PathAndQuery:

ingrese la descripción de la imagen aquí

Y finalmente, aquí está mi respuesta 405:

ingrese la descripción de la imagen aquí

Aquí está el WebApi.config en mi proyecto de API web:

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

He intentado despojar por el camino que se pasa en PutAsJsonAsynca string.Format("/api/department/{0}", department.Id), y string.Format("http://localhost/CallCOPAPI/api/department/{0}", department.Id)sin suerte.

¿Alguien tiene alguna idea de por qué me sale el error 405?

ACTUALIZAR

Según la solicitud, aquí está mi código de controlador de Departamento (publicaré tanto el código de controlador de Departamento para mi proyecto front-end, como el código de ApiController de Departamento para WebAPI):

Controlador del departamento de front-end

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

API Web Departamento 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
fuente
¿No deberías estar usando [HttpPut]antes de la definición del método de acción? ( [HttpPost]y [HttpDelete]cuando sea apropiado también)
Chris Pratt
@ChrisPratt Solo para que quede claro, te refieres a poner [HttpPut]el controlador WebAPI (ApiController), ¿verdad? Porque el controlador de front-end para Departamento (método de edición) tiene un [HttpPost]atributo.
Mike Marks el
1
@ChrisPratt El ValuesController (el que viene con la plantilla WebAPI) no tiene [HttpPut]atributos, etc. en los métodos Put / Post / Delete ..
Mike Marks
Sí, estoy razonablemente seguro de que necesita los del lado de la API web. Personalmente, siempre he usado AttributeRouting para cosas de API web, por lo que mi recuerdo es un poco incompleto.
Chris Pratt
Aparentemente era lo de WebDAV ... Revisé mi IIS local (características de Windows) para asegurarme de que no estaba instalado y dijo que no ... de todos modos publiqué una respuesta a esto, básicamente eliminando el módulo WebDAV dentro de mi web .config.
Mike Marks el

Respuestas:

304

Entonces, verifiqué las características de Windows para asegurarme de que no tenía instalado este elemento llamado WebDAV, y decía que no. De todos modos, seguí adelante y coloqué lo siguiente en mi web.config (tanto front-end como WebAPI, solo para estar seguro), y funciona ahora. Puse esto adentro <system.webServer>.

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

Además, a menudo se requiere agregar lo siguiente web.configen los controladores. Gracias a Babak

<handlers>
    <remove name="WebDAV" />
    ...
</handlers>
Mike Marks
fuente
2
Jaja ... sí ... estaba a punto de rendirme. Así que sí. WebDAV debe haber sido habilitado en su applicationhost.config. Me alegra que lo hayas arreglado.
Aron
9
Es posible que también deba agregar esto:<handlers><remove name="WebDAV" />...
Babak
14
Agregué esto solo a mi WebApi web.config y funcionó.
Fordy
Aunque en IE10 funcionó bien incluso sin esta configuración, tuve que hacerlo solo en WebApi web.config para que funcione en el navegador Chrome.
Dennis R
1
Gracias por la respuesta a este problema realmente molesto. ¿Por qué ocurre esto en primer lugar?
Scott Wilson
23

WebDav-SchmebDav .. .. asegúrese de crear la url con la ID correctamente. No lo envíe como http://www.fluff.com/api/Fluff?id=MyID , envíelo como http://www.fluff.com/api/Fluff/MyID .

P.ej.

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

{"Data":"1"}

Esto fue reventar mis bolas por una pequeña eternidad, vergüenza total.

Molibar
fuente
3
Un juego de pelota adicional para mí: las acciones PUT no pueden vincular datos a parámetros de tipo primitivos. Tuve que cambiar public int PutFluffColor(int Id, int colorCode)apublic int PutFluffColor(int Id, UpdateFluffColorModel model)
Josh Noe
44
Ojalá pudiera votar esto dos veces para el WebDav-SchmebDav
Noel
1
Después de más de 8 horas de trabajo para llegar a la solución, cada uno recomienda web.config cambia, es tan sorprendente que ningún cuerpo ni siquiera habló de esta posibilidad.
sairfan
22

Agrega esto a tu web.config. Debe decirle a IIS qué PUT PATCH DELETEy qué OPTIONSsignifica. Y cuál IHttpHandlerinvocar.

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

También verifique que no tenga WebDAV habilitado.

Aron
fuente
Ya tengo eso. Supongo que esto se incluirá en el proyecto API web, no en mi proyecto MVC front-end, ¿verdad?
Mike Marks el
No tengo instalado WebDAV. Además, ¿está diciendo que el código web.config anterior debe colocarse en la web.config del proyecto que realiza la llamada a la API web?
Mike Marks el
En realidad está en ambos web.configs ... :(
Mike Marks
Oh no ... pensé que estabas haciendo referencia a un proyecto de API web de un proyecto MVC.
Aron
1
¿Puedes publicar la lista de códigos del DepartmentController? Todo ello. El problema radica en su proyecto de API web, y no sabe cómo manejarlo PUT, eso es lo que significa 405. Verifique que GET funcione, solo para descartar el enrutamiento. PD. Intente copiar el código de pegado en lugar de la captura de pantalla. PPS, NO LO USE Task.Result, obtendrá problemas de subprocesos no relacionados en ciertas situaciones. Simplemente convierta todo el método en asíncrono en su lugar. Sin mencionar que crea un código bloqueado sincrónico y multiproceso (más lento que un único subproceso).
Aron
14

Estoy ejecutando una aplicación ASP.NET MVC 5 en IIS 8.5. Probé todas las variaciones publicadas aquí, y así es web.configcomo se ve mi :

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

No pude desinstalar WebDav de mi servidor porque no tenía privilegios de administrador. Además, a veces recibía los method not allowedarchivos .css y .js. Al final, con la configuración anterior configurada, todo comenzó a funcionar nuevamente.

jpgrassi
fuente
5

Decorar uno de los parámetros de acción con [FromBody] resolvió el problema para mí:

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

Sin embargo, ASP.NET lo inferiría correctamente si se usara un objeto complejo en el parámetro del método:

public async Task<IHttpActionResult> UpdateEntry(string id, MyEntry entry)
Владимiръ
fuente
1

Otra causa de esto podría ser si no utiliza el nombre de variable predeterminado para el "id" que en realidad es: id.

Adam Levitt
fuente
0

En mi caso, el error 405 fue invocado por el controlador estático debido a que la ruta ("api / images") está en conflicto con la carpeta del mismo nombre ("~ / images").

Petr Šugar
fuente
0

Puede eliminar el módulo webdav manualmente de la GUI para el particular en IIS.
1) Ir a los IIs.
2) Ir al sitio respectivo.
3) Abra "Asignaciones
de controladores " 4) Desplácese hacia abajo y seleccione el módulo WebDav. Haga clic derecho sobre él y elimínelo.

Nota: esto también actualizará su web.config de la aplicación web.

Naveen Kumar GC
fuente
-1

Su aplicación cliente y la aplicación del servidor deben estar bajo el mismo dominio, por ejemplo:

cliente - localhost

servidor - localhost

y no :

cliente - localhost: 21234

servidor - localhost

Lev K.
fuente
2
No lo creo. El objetivo de crear un servicio es llamar desde otro dominio
Ozan BAYRAM
Está pensando en una solicitud de dominio cruzado, que le dará una respuesta 200 del servidor, pero el navegador aplicará su regla de "no hay solicitudes de dominio cruzado" y no aceptará la respuesta. La pregunta se refiere a una respuesta 405 "Método no permitido", un tema diferente.
Josh Noe
CORS proporcionará 405 "Método no permitido", por ejemplo: URL de solicitud: testapi.nottherealsite.com/api/Reporting/RunReport Método de solicitud: OPCIONES Código de estado: 405 Método no permitido, lea aquí stackoverflow.com/questions/12458444/…
Lev K.
Te refieres a la cuestión CORS.
user3151766