Bizarro Welt: ASP.NET MVC – PUT ist kaputt

Jeder, der in seinem Leben einmal Software entwickelt hat, kam bestimmt irgendwann an einen Punkt, dass nach einer harmlosen Änderung im Quellcode, der Installation einer x-beliebigen Softwarekomponente oder dem Ändern eines Konfigurationsparameters, der sonst nie Seiteneffekte mit sich brachte, die Welt plötzlich Kopf steht: Anwendungen stürzen ab, Unit Test produzieren nur noch rote Balken oder auch nach mehrmaligen Redeployment mit offensichtlichen Änderungen à la MessageBox.Show() verhält sich eine Anwendung stets identisch.

Diese seltsamen Phänomene bezeichne ich als Probleme aus der Bizarro Welt. Ein Problem aus de Bizarro Welt weist einige interessante Eigenschaften auf:

  • Die Lösung (oder ein Workaround) für ein solches Problem ist vergleichsweise simpel zu realisieren.
  • Die Erklärung der jeweiligen Ursache für ein solches Problem ist vergleichsweise kompliziert, so dass sie niemand wirklich Lust auf eine Erklärung hat (die Lösung ist ja simpel…).
  • Eine Lösung oder ein Workaround für das Problem ist im Web nicht aufzuspüren – Live Search wie auch Google lassen den Suchenden ratlos zurück, weil man entweder gar nichts zu den anwendbaren Stichworten findet oder die Suchergebnisse nutzlos sind und im schlimmsten Fall sogar in die Irre führen.

Die letztgenannte Eigenschaft ist besonders tückisch, da viele Probleme aus der Bizarro Welt augenscheinlich Tausende von Entwicklern betreffen oder von den jeweiligen Technologie-Anbietern als Bugs/Issues/FAQ dokumentiert sein sollten – das ist zumindest immer mein erster Gedanke.

Damit diese bemerkenswerte Gattung von technischen Problemenfällen endlich eine hinreichende Würdigung erhält, möchte ich eine Blog-Serie über (meine) Probleme aus Bizarro Welt starten. Den Auftakt macht dabei ein Problem, das ASP.NET MVC-Entwickler ereilen kann, die eine RESTful Architektur verwenden wollen.

Das Phänomen

Gegeben ein RESTful entworfener Controller wie dieses Mini-Beispiel, dessen MVC-Anwendung unter IIS 7 läuft…

    1:  public class RestController : Controller
    2:  {
    3:      private ActionResult CoreAction(int id)
    4:      {
    5:          RestViewModel model = new RestViewModel()
    6:          {
    7:              Id = id,
    8:              Action = Request.HttpMethod.ToUpperInvariant()
    9:          };
   10:          return Content(model.ToString());
   11:      }
   12:   
   13:      //
   14:      // GET: /Rest/Get/1
   15:   
   16:      [AcceptVerbs(HttpVerbs.Get)]
   17:      public ActionResult Get(int id)
   18:      {
   19:          return CoreAction(id);
   20:      }
   21:   
   22:      //
   23:      // POST: /Rest/Post/1
   24:   
   25:      [AcceptVerbs(HttpVerbs.Post)]
   26:      public ActionResult Post(int id)
   27:      {
   28:          return CoreAction(id);
   29:      }
   30:   
   31:      //
   32:      // PUT: /Rest/Put/1
   33:   
   34:      [AcceptVerbs(HttpVerbs.Put)]
   35:      public ActionResult Put(int id)
   36:      {
   37:          return CoreAction(id);
   38:      }
   39:   
   40:      //
   41:      // DELETE: /Rest/Delete/1
   42:   
   43:      [AcceptVerbs(HttpVerbs.Delete)]
   44:      public ActionResult Delete(int id)
   45:      {
   46:          return CoreAction(id);
   47:      }
   48:  }

 

… warum fahren Clients, die PUT oder DELETE Requests senden, mit The remote server returned an error: (405) Method Not Allowed an die Wand?

405

An unserem Beispielcode kann es ja wohl kaum liegen, oder?

Die Lösung

In diesem Fall heißt der Schuldige WebDAV. Genauer gesagt ist es das WebDAV HTTP-Modul, das auch wenn das WebDAV-Feature selbst deaktiviert ist, sich trotzdem alle PUT- und DELETE-Requests schnappt und überprüft. Wenn aber WebDAV nicht aktiviert ist, verwirft es PUT und DELETE, ohne dass unser Controller den Request zu sehen bekommt.

Auch das Aktivieren des Feature ist hier nutzlos, da das Modul dann PUT und DELETE (vergeblich) zu verarbeiten versucht – wieder bleibt unser Controller bei der Request-Verarbeitung außen vor.

Die Lösung in diesem Fall ist simpel: Das Modul muss in der Web.config der MVC-Applikation deaktiviert werden.

    1:  <system.webServer>
    2:      <validation validateIntegratedModeConfiguration="false"/>
    3:      <modules runAllManagedModulesForAllRequests="true">
    4:          <remove name="WebDAVModule" />
    5:        ...
    6:      </modules>
    7:      ...
    8:  </system.webServer>

Der Effekt tritt übrigens sowohl bei Version 7.0 als auch 7.5 der WebDAV-Extension für IIS 7 auf.

Und wer WebDAV gar nicht installiert hat? Ja, er oder sie sollte auch nicht auf dieses Problem stoßen – oder wurde Opfer eines ganz anderen Effekts aus der Bizarro Welt…

Jörg