25

On my Web API project, I cannot perform an HTTP PUT to my resources. I've read through some similar questions on this problem and I've followed the recommended advice.

First off, I uninstalled WebDAV completely on my machine (Windows 7 64-bit) and subsequently rebooted my machine.

Secondly, the WebDAV handlers were specified as removed in my web.config and the HTTP PUT verb was specified as being allowed for the Extensionless URL Handler.

<modules runAllManagedModulesForAllRequests="false">
  <remove name="WebDAVModule"/>
</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-Integrated-4.0"
       path="*."
       verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
       type="System.Web.Handlers.TransferRequestHandler"
       resourceType="Unspecified"
       requireAccess="Script"
       preCondition="integratedMode,runtimeVersionv4.0" />
  <add name="AttributeRouting" path="routes.axd" verb="*" type="AttributeRouting.Web.Logging.LogRoutesHandler, AttributeRouting.Web" />
</handlers>

I even tried adding the ISAPI Extensionless URL Handler (32-bit and 64-bit) and changing my application from the integrated pipeline App Pool to the classic App Pool.

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

I'm currently using Thinktecture IdentityModel to enable Cross Origin Resource Sharing (CORS) support. For my sanity, I've gone with the nuclear option of enabling everything to make sure the HTTP PUT is actually allowed.

config.RegisterGlobal(httpConfig);

config.ForAllResources()
      .ForAllOrigins()
      .AllowAllMethods()
      .AllowAllRequestHeaders();

The Attribute Routing NuGet package is configured to pick up all routes from the current assembly and any subtypes of ApiController.

config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
config.AddRoutesFromControllersOfType<ApiController>();

My resource has the PUT attribute properly specified as well.

[PUT("/API/Authenticate/Link/{key}/{identifier}")]
public Boolean LinkUser(Guid key, String identifier) { ... }

Every resource I look up on this matter recommends the same exact thing: Uninstalling WebDAV, disabling the WebDAV handlers and making sure that the Extensionless URL handler is properly configured. I've done all that and it still doesn't work.

In fiddler, I'm getting the following:

PUT https://localhost/Test/API/Authenticate/Link/Foo/Bar

{"Message":"The requested resource does not support http method 'PUT'."}

What am I doing wrong?

Community
  • 1
  • 1
Mike Bailey
  • 12,479
  • 14
  • 66
  • 123

7 Answers7

20

Apparently, there is a known problem within AttributeRouting wherein the HttpPut methods are currently non-functional in ASP.NET Web API.

The currently accepted workaround is add the appropriate verb onto the route until a proper fix comes along:

Web API RC sealed a vital interface for route detection by the underlying framework. Though the interface is now public, the change won't be released until vNext. So here are some workarounds:

  • Use AR attributes in combination with HttpGet, HttpPost, HttpPut, or HttpDelete attributes from System.Web.Http:
[GET("some/url"), HttpGet]
public string Method1() {}

[PUT("some/url"), HttpPut]
public string Method2() {}

[POST("some/url"), HttpPost]
public string Method3() {}

[DELETE("some/url"), HttpDelete]
public string Method4() {}
Mike Bailey
  • 12,479
  • 14
  • 66
  • 123
  • was just about to write this, +1 – Filip W Jan 14 '13 at 17:04
  • @FilipW: It took me quite a while to find this. I placed a comment on the link in my answer asking if it would be possible to better document this in the code (e.g. `ObsoleteException`). This is quite a confusing bug considering there's already two known completely unrelated issues that cause the same problem. – Mike Bailey Jan 14 '13 at 20:12
14

Double check that you're using [HttpPut] from System.Web.Http.

Under some circumstances you can end up using the attribute from System.Web.Mvc.

This was resulting in 405s for us.

Nick Josevski
  • 4,156
  • 3
  • 43
  • 63
4

I had the same error, and traced it to a custom route that I'd defined like so:

config.Routes.MapHttpRoute(
    name: "SomeCall",
    routeTemplate: "api/somecall/{id}",
    defaults: new { controller = "SomeCall", action = "Get" }
);

The problem here is the action = "Get" which prevented the PUT action of the same URI to respond. Removing the default action fixed the issue.

Andrew
  • 12,991
  • 15
  • 55
  • 85
  • Thanks @MikeBantegui. In researching the problem, your question was the most comprehensive and closest match to my configuration - excepting the Attribute Routing - so I thought I'd put up my solution in case it helps someone else – Andrew Jan 30 '13 at 12:19
  • Fair enough. Do you have any idea why the default action was preventing the `PUT` action? It seems like that may be a bug. – Mike Bailey Jan 30 '13 at 13:54
  • I wondered if it might be a bug too - it can't really be called 'defaults' if the actual Request method can't superceed it! Though I admit that I haven't looked into the reason why – Andrew Jan 31 '13 at 07:55
3

What worked for me was to add a Route Attribute as I already had one defined for a GET request that was overloaded as below:

    // GET api/Transactions/5
    [Route("api/Transactions/{id:int}")]
    public Transaction Get(int id)
    {
        return _transactionRepository.GetById(id);
    }

    [Route("api/Transactions/{code}")]
    public Transaction Get(string code)
    {
        try
        {
            return _transactionRepository.Search(p => p.Code == code).Single();
        }
        catch (Exception Ex)
        {
            System.IO.File.WriteAllText(@"C:\Users\Public\ErrorLog\Log.txt",
                Ex.Message + Ex.StackTrace + Ex.Source + Ex.InnerException.InnerException.Message);
        }

        return null;
    }

So I added for the PUT:

    // PUT api/Transactions/5
    [Route("api/Transactions/{id:int}")]
    public HttpResponseMessage Put(int id, Transaction transaction)
    {
        try
        {
            if (_transactionRepository.Save(transaction))
            {
                return Request.CreateResponse<Transaction>(HttpStatusCode.Created, transaction);
            }
        }
        catch (Exception Ex)
        {
            System.IO.File.WriteAllText(@"C:\Users\Public\ErrorLog\Log.txt",
                Ex.Message + Ex.StackTrace + Ex.Source + Ex.InnerException.InnerException.Message);
        }

        return Request.CreateResponse<Transaction>(HttpStatusCode.InternalServerError, transaction);
    }
Oladipo Olasemo
  • 2,010
  • 24
  • 31
0

I think this is no more the case, perhaps this issue has been fixed now. ASP.NET MVC Web API now allows $http.put and here is the code to test.

AngularJS Script code

$scope.UpdateData = function () {
        var data = $.param({
            firstName: $scope.firstName,
            lastName: $scope.lastName,
            age: $scope.age
        });

        $http.put('/api/Default?'+ data)
        .success(function (data, status, headers) {
            $scope.ServerResponse = data;
        })
        .error(function (data, status, header, config) {
            $scope.ServerResponse =  htmlDecode("Data: " + data +
                "\n\n\n\nstatus: " + status +
                "\n\n\n\nheaders: " + header +
                "\n\n\n\nconfig: " + config);
        });
    };

Html code

<div ng-app="myApp" ng-controller="HttpPutController">
<h2>AngularJS Put request </h2>
<form ng-submit="UpdateData()">
    <p>First Name: <input type="text" name="firstName" ng-model="firstName" required /></p>
    <p>Last Name: <input type="text" name="lastName" ng-model="lastName" required /></p>
    <p>Age : <input type="number" name="age" ng-model="age" required /></p>
    <input type="submit" value="Submit" />
    <hr />
    {{ ServerResponse }}
</form></div>

ASP.NET MVC Web API Controller action method

 public class DefaultController : ApiController
{

    public HttpResponseMessage PutDataResponse(string firstName, string lastName, int age)
    {
        string msg =  "Updated: First name: " + firstName +
            " | Last name: " + lastName +
            " | Age: " + age;

        return Request.CreateResponse(HttpStatusCode.OK, msg);
    }
}

(Change the url to send request to) When we click on the Submit button, it sends HttpPut request to '/api/default' (DefaultController) where PutDataResponse action method is declared. This method will be called and user gets its response.

This solution was originally written here

Sheo Narayan
  • 1,196
  • 2
  • 14
  • 17
0

For me it was because I had not set the media type in the json content string for my http client request:

new StringContent(json, Encoding.UTF32, "application/json");

All sorts of weird behavior if this is not set.

ComeIn
  • 1,519
  • 17
  • 12
-1

in my case I put in postman a parameter id:

http://localhost:55038/api/documento/pruebadetallecatalogo?id=100

but, I changed the url request to:

http://localhost:55038/api/documento/pruebadetallecatalogo/100

that works for me!!!

Peter Csala
  • 17,736
  • 16
  • 35
  • 75