Żądanie wprowadzenia interfejsu API sieci Web generuje błąd Http 405 Niedozwolona metoda

134

Oto wywołanie PUTmetody w moim interfejsie API sieci Web - trzeci wiersz w metodzie (wywołuję interfejs API sieci Web z interfejsu ASP.NET MVC):

wprowadź opis obrazu tutaj

client.BaseAddressjest http://localhost/CallCOPAPI/.

Oto contactUri:

wprowadź opis obrazu tutaj

Oto contactUri.PathAndQuery:

wprowadź opis obrazu tutaj

I na koniec moja odpowiedź 405:

wprowadź opis obrazu tutaj

Oto plik WebApi.config w moim projekcie interfejsu API sieci 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);

Próbowałem zedrzeć ścieżkę, która prowadzi PutAsJsonAsyncdo string.Format("/api/department/{0}", department.Id)i string.Format("http://localhost/CallCOPAPI/api/department/{0}", department.Id)bez powodzenia.

Czy ktoś ma jakieś pomysły, dlaczego otrzymuję błąd 405?

AKTUALIZACJA

Zgodnie z żądaniem, oto mój kod kontrolera działu (opublikuję zarówno kod kontrolera działu dla mojego projektu frontendowego, jak i kod działu ApiController dla interfejsu WebAPI):

Kontroler działu 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 });
        }
    }
}

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
źródło
Czy nie powinieneś używać [HttpPut]przed definicją metody akcji? ( [HttpPost]i [HttpDelete]tam, gdzie jest to stosowne)
Chris Pratt,
@ChrisPratt Żeby było jasne, masz na myśli [HttpPut]włączenie kontrolera WebAPI (ApiController), prawda? Ponieważ kontroler frontonu dla działu (metoda edycji) ma [HttpPost]atrybut.
Mike Marks
1
@ChrisPratt ValuesController (ten, który jest dostarczany z szablonem WebAPI) nie ma [HttpPut]atrybutów itp. W metodach Put / Post / Delete ..
Mike Marks
Tak, jestem pewien, że potrzebuje tych po stronie interfejsu API sieci Web. Osobiście zawsze używałem AttributeRouting do obsługi interfejsu API sieci Web, więc moje wspomnienia są trochę pobieżne.
Chris Pratt,
Najwyraźniej chodziło o WebDAV ... Sprawdziłem lokalne IIS (funkcje systemu Windows), aby upewnić się, że nie jest zainstalowane i powiedziałem, że nie jest ... w każdym razie opublikowałem odpowiedź na to pytanie, po prostu usuwając moduł WebDAV w mojej sieci .config.
Mike Marks

Odpowiedzi:

304

Więc sprawdziłem Funkcje systemu Windows, aby upewnić się, że nie mam zainstalowanej rzeczy o nazwie WebDAV, i powiedziałem, że nie. W każdym razie poszedłem dalej i umieściłem następujący plik w moim web.config (zarówno interfejs użytkownika, jak i WebAPI, dla pewności) i teraz działa. Włożyłem to do środka <system.webServer>.

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

Ponadto często wymagane jest dodanie następujących elementów do web.configw programach obsługi. Dzięki Babak

<handlers>
    <remove name="WebDAV" />
    ...
</handlers>
Mike Marks
źródło
2
Haha… tak… Już miałem się poddać. Więc tak. WebDAV musi być włączony w Twoim applicationhost.config. Cieszę się, że to naprawiłeś.
Aron,
9
Konieczne może być również dodanie tego:<handlers><remove name="WebDAV" />...
Babak
14
Dodałem to tylko do mojego WebApi web.config i zadziałało.
Fordy
Mimo że w IE10 działało dobrze nawet bez tej konfiguracji, musiałem zrobić tylko w WebApi web.config, aby działał w przeglądarce Chrome.
Dennis R
1
Dzięki za odpowiedź na ten naprawdę irytujący problem. Dlaczego tak się dzieje w pierwszej kolejności?
Scott Wilson,
23

WebDav-SchmebDav .. .. upewnij się, że poprawnie utworzyłeś adres URL z identyfikatorem. Nie wysyłaj jak http://www.fluff.com/api/Fluff?id=MyID , wysyłaj jak http://www.fluff.com/api/Fluff/MyID .

Na przykład.

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

{"Data":"1"}

To rozwalało mi jaja na małą wieczność, totalne zażenowanie.

Molibar
źródło
3
Dodatkowy problem dla mnie: akcje PUT nie mogą wiązać danych z parametrami typu pierwotnego. Musiałem przesiąść się public int PutFluffColor(int Id, int colorCode)napublic int PutFluffColor(int Id, UpdateFluffColorModel model)
Josh Noe
4
Chciałbym móc dwukrotnie głosować za tym głosem na WebDav-SchmebDav
Noel,
1
po ponad 8 godzinach pracy dochodzimy do rozwiązania, każdy poleca zmianę web.config tak niesamowitą, że nikt nawet nie mówił o takiej możliwości.
sairfan
22

Dodaj to do swojego web.config. Musisz powiedzieć usługom IIS, co PUT PATCH DELETEi co OPTIONSoznacza. I IHttpHandlerdo którego przywołać.

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

Sprawdź również, czy nie masz włączonego protokołu WebDAV.

Aron
źródło
Już to mam. Zakładam, że ma to zostać umieszczone w projekcie interfejsu API sieci Web, a nie w moim projekcie frontonu MVC, prawda?
Mike Marks
Nie mam zainstalowanego WebDAV. Ponadto, czy twierdzisz, że powyższy kod web.config należy umieścić w pliku web.config projektu, który wykonuje wywołanie internetowego interfejsu API?
Mike Marks
W rzeczywistości jest w obu plikach web.config ... :(
Mike Marks
O nie ... Myślałem, że odwołujesz się do projektu internetowego interfejsu API z projektu MVC.
Aron,
1
Czy możesz opublikować listę kodów DepartmentController? Wszystko. Problem leży w twoim projekcie Web API i nie wie, jak sobie z tym poradzić PUT, to właśnie oznacza 405. Sprawdź, czy działa GET, aby wykluczyć routing. PS. Spróbuj skopiować kod wklejany zamiast zrzutu ekranu. PPS, NIE UŻYWAJ Task.Result, w niektórych sytuacjach pojawią się niepowiązane problemy z wątkami. Zamiast tego po prostu zmień całą metodę na async await. Nie wspominając o tym, że tworzy synchroniczny, wielowątkowy blokowany kod (wolniejszy niż jednowątkowy).
Aron,
14

Używam aplikacji ASP.NET MVC 5 w usługach IIS 8.5. Wypróbowałem wszystkie zamieszczone tutaj odmiany i tak web.configwygląda mój wygląd:

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

Nie mogłem odinstalować WebDav z mojego serwera, ponieważ nie miałem uprawnień administratora. Czasami otrzymywałem method not allowedpliki .css i .js. W końcu po powyższej konfiguracji wszystko zaczęło działać ponownie.

jpgrassi
źródło
5

Udekorowanie jednego z parametrów działania za pomocą [FromBody] rozwiązało problem:

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

Jednak ASP.NET wywnioskowałby to poprawnie, gdyby w parametrze metody zastosowano obiekt złożony:

public async Task<IHttpActionResult> UpdateEntry(string id, MyEntry entry)
Владимiръ
źródło
1

Inną przyczyną może być to, że nie używasz domyślnej nazwy zmiennej dla „id”, którą w rzeczywistości jest: id.

Adam Levitt
źródło
0

W moim przypadku błąd 405 został wywołany przez procedurę obsługi statycznej z powodu konfliktu trasy („api / images”) z folderem o tej samej nazwie („~ / images”).

Petr Šugar
źródło
0

Możesz ręcznie usunąć moduł webdav z GUI dla konkretnego w IIS.
1) Idź do IIs.
2) Przejdź do odpowiedniej witryny.
3) Otwórz „Handler Mappings”
4) Przewiń w dół i wybierz moduł WebDav. Kliknij go prawym przyciskiem myszy i usuń.

Uwaga: spowoduje to również zaktualizowanie pliku web.config aplikacji internetowej.

Naveen Kumar GC
źródło
-1

Twoja aplikacja kliencka i aplikacja serwerowa muszą znajdować się w tej samej domenie, na przykład:

klient - localhost

serwer - localhost

i nie :

klient - localhost: 21234

serwer - localhost

Lew K.
źródło
2
Nie sądzę. Celem stworzenia usługi jest dzwonienie z innej domeny
Ozan BAYRAM
Myślisz o żądaniu międzydomenowym, które da 200 odpowiedzi z serwera, ale przeglądarka wymusi swoją regułę „brak żądań między domenami” i nie zaakceptuje odpowiedzi. Pytanie dotyczy odpowiedzi 405 „Metoda niedozwolona”, innego problemu.
Josh Noe
CORS poda 405 „Metoda niedozwolona ”, na przykład: Adres URL żądania: testapi.nottherealsite.com/api/Reporting/RunReport Metoda żądania: OPCJE Kod stanu: 405 Metoda niedozwolona, przeczytaj tutaj stackoverflow.com/questions/12458444/…
Lev K.
Masz na myśli problem z CORS.
user3151766