Solicitação de colocação da API da Web gera um erro Http 405 Method Not Allowed

134

Aqui está a chamada para o PUTmétodo na minha API da Web - a terceira linha do método (estou chamando a API da Web de um front end do ASP.NET MVC):

insira a descrição da imagem aqui

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

Aqui está contactUri:

insira a descrição da imagem aqui

Aqui está contactUri.PathAndQuery:

insira a descrição da imagem aqui

E, finalmente, aqui está a minha resposta 405:

insira a descrição da imagem aqui

Aqui está o WebApi.config no meu projeto de API da 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);

Eu tentei seguir o caminho que é passado PutAsJsonAsyncpara string.Format("/api/department/{0}", department.Id)e string.Format("http://localhost/CallCOPAPI/api/department/{0}", department.Id)sem sorte.

Alguém tem alguma idéia de por que estou recebendo o erro 405?

ATUALIZAR

Conforme a solicitação, aqui está o código do meu controlador de departamento (postarei o código do controlador de departamento do meu projeto de front-end, bem como o código do departamento ApiController da WebAPI):

Controlador de departamento 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 });
        }
    }
}

Departamento de API da Web 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
fonte
Você não deveria estar usando [HttpPut]antes da definição do método de ação? ( [HttpPost]e sempre [HttpDelete]que apropriado)
Chris Pratt
@ ChrisPratt Apenas para ficar claro, você quer dizer colocar [HttpPut]o controlador WebAPI (ApiController), certo? Como o controlador front-end do Departamento (método Editar) tem um [HttpPost]atributo
Mike Marcas
1
@ChrisPratt O ValuesController (aquele que vem com o modelo WebAPI) não tem [HttpPut], etc. atributos na Put / Post / métodos excluem ..
Mike Marcas
Sim, tenho quase certeza de que precisa do lado da API da Web. Pessoalmente, sempre usei o AttributeRouting para coisas da API da Web, portanto, minha lembrança é um pouco superficial.
Chris Pratt
Aparentemente, era a coisa do WebDAV. Eu verifiquei meu IIS local (Recursos do Windows) para garantir que não estava instalado e ele disse que não estava ... de qualquer maneira eu postei uma resposta para isso, basicamente removendo o módulo WebDAV da minha web .config.
Mike Marcas

Respostas:

304

Portanto, verifiquei os Recursos do Windows para ter certeza de que não estava instalado o WebDAV, e ele disse que não. Enfim, fui em frente e coloquei o seguinte no meu web.config (front-end e WebAPI, apenas para ter certeza), e ele funciona agora. Eu coloquei isso dentro <system.webServer>.

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

Além disso, geralmente é necessário adicionar o seguinte web.confignos manipuladores. Graças a Babak

<handlers>
    <remove name="WebDAV" />
    ...
</handlers>
Mike Marks
fonte
2
Haha ... sim ... eu estava prestes a desistir. Então sim. O WebDAV deve ter sido ativado no seu applicationhost.config. Fico feliz que você tenha consertado.
Aron3
9
Você também pode precisar adicionar isso:<handlers><remove name="WebDAV" />...
Babak
14
Adicionei isso apenas ao meu WebApi web.config e funcionou.
Fordy
Embora no IE10 funcionasse bem, mesmo sem essa configuração, eu tinha que fazer apenas no WebApi web.config para fazê-lo funcionar no navegador Chrome.
Dennis R
1
Obrigado pela resposta a este problema realmente irritante. Por que isso ocorre em primeiro lugar?
Scott Wilson
23

WebDav-SchmebDav .. .. certifique-se de criar o URL com o ID corretamente. Não envie como http://www.fluff.com/api/Fluff?id=MyID , envie como http://www.fluff.com/api/Fluff/MyID .

Por exemplo.

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

{"Data":"1"}

Isso estava estourando minhas bolas por uma pequena eternidade, total vergonha.

Molibar
fonte
3
Um imbecil adicional para mim: as ações PUT não podem vincular dados a parâmetros do tipo primitivo. Eu tive que mudar public int PutFluffColor(int Id, int colorCode)parapublic int PutFluffColor(int Id, UpdateFluffColorModel model)
Josh Noe
4
Gostaria de poder votar isso duas vezes para o WebDav-SchmebDav
Noel
1
depois de mais de 8 horas de trabalho chegarem à solução, todos estão recomendando que o web.config mude tão incrível que nenhum organismo sequer falou sobre essa possibilidade.
sairfan
22

Adicione isso ao seu web.config. Você precisa informar ao IIS o que PUT PATCH DELETEe o que OPTIONSsignifica. E qual 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>

Verifique também se você não tem o WebDAV ativado.

Aron
fonte
Eu já tenho isso. Estou assumindo que isso deve ser colocado no projeto da API da Web, não no meu projeto MVC de front-end, certo?
Mike Marcas
Não tenho o WebDAV instalado. Além disso, você está dizendo que o código web.config acima precisa ser colocado no web.config do projeto que faz a chamada para a API da Web?
Mike Marcas
Na verdade, está nos dois web.configs ... :(
Mike Marks
Ah, não ... eu pensei que você estava fazendo referência a um projeto de API da Web de um projeto MVC.
Aron #
1
Você pode postar a listagem de códigos do DepartmentController? Tudo isso. O problema está no seu projeto de API da Web e ele não sabe como lidar PUT, é isso que significa 405. Verifique se o GET funciona, apenas para descartar o roteamento. PS. Tente copiar o código de colagem em vez da captura de tela. PPS, NÃO USE Task.Result, você terá problemas de segmentação não relacionados em determinadas situações. Basta transformar todo o método em assíncrono aguardar. Sem mencionar que ele cria código bloqueado síncrono e multithread (mais lento que o thread único).
Aron3
14

Estou executando um aplicativo ASP.NET MVC 5 no IIS 8.5. Eu tentei todas as variações postadas aqui, e é assim que minha web.configaparência:

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

Não consegui desinstalar o WebDav do meu servidor porque não tinha privilégios de administrador. Além disso, às vezes eu estava obtendo os method not allowedarquivos .css e .js. No final, com a configuração acima, tudo começou a funcionar novamente.

jpgrassi
fonte
5

Decorar um dos parâmetros de ação com [FromBody] resolveu o problema para mim:

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

No entanto, o ASP.NET inferiria corretamente se objeto complexo fosse usado no parâmetro method:

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

Outra causa disso pode ser se você não usar o nome da variável padrão para o "id", que na verdade é: id.

Adam Levitt
fonte
0

No meu caso, o erro 405 foi invocado pelo manipulador estático devido à rota ("api / images") entrar em conflito com a pasta com o mesmo nome ("~ / images").

Petr Šugar
fonte
0

Você pode remover o módulo webdav manualmente da GUI para o particular no IIS.
1) Vá para os IIs.
2) Vá para o respectivo site.
3) Abra "Mapeamentos de manipulador"
4) Role para baixo e selecione o módulo WebDav. Clique com o botão direito e exclua-o.

Nota: isso também atualizará seu web.config do aplicativo web.

Naveen Kumar GC
fonte
-1

Seu aplicativo cliente e servidor devem estar no mesmo domínio, por exemplo:

client - localhost

servidor - host local

e não :

cliente - localhost: 21234

servidor - host local

Lev K.
fonte
2
Acho que não. O objectivo de criar um serviço é chamar de outro domínio
Ozan BAYRAM
Você está pensando em uma solicitação entre domínios, que fornecerá uma resposta de 200 do servidor, mas o navegador aplicará sua regra "sem solicitações entre domínios" e não aceitará a resposta. A questão está se referindo a uma resposta 405 "Método não permitido", um problema diferente.
Josh Noe
O CORS fornecerá 405 "Método não permitido", por exemplo: URL de solicitação: testapi.nottherealsite.com/api/Reporting/RunReport Método de solicitação: Código de status de OPÇÕES: Método 405 não permitido, leia aqui stackoverflow.com/questions/12458444/…
Lev K.
Você está se referindo à questão do CORS.
user3151766 8/02