Search code examples
jqueryasp.net-web-apiasp.net-routing

WebApi HttpDelete woes


I have a controller like this (I will post all the code just so you can see how it is all working).

/// <summary>
/// Handles all of the Upload functions, including GetAll, Get and Create.
/// </summary>
[RoutePrefix("Api/Uploads")]
public class UploadsController : BaseController
{
    private readonly UploadService service;

    /// <summary>
    /// Parameterless constructor in which the upload service is instantiated.
    /// </summary>
    public UploadsController()
    {
        this.service = new UploadService(UnitOfWork);
    }

    // GET api/uploads
    /// <summary>
    /// Gets a list of all the Uploads.
    /// </summary>
    /// <returns>A list of uploads.</returns>
    [HttpGet]
    [Route("")]
    [ResponseType(typeof(IList<UploadViewModel>))]
    public async Task<IHttpActionResult> Get()
    {
        try
        {
            var uploads = await this.service.GetAllAsync();
            var models = uploads.Select(model => new UploadViewModel(model)).ToList();

            return Ok(models);
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

    // GET api/uploads
    /// <summary>
    /// Gets an upload by the required parameter; id.
    /// </summary>
    /// <param name="id">The required id paramter of the upload.</param>
    /// <returns>An upload view model.</returns>
    [HttpGet]
    [Route("{id:int}")]
    [ResponseType(typeof(UploadViewModel))]
    public async Task<IHttpActionResult> Get(int id)
    {
        try
        {
            var model = new UploadViewModel(await this.service.GetAsync(id));

            return Ok(model);
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

    // POST api/uploads
    /// <summary>
    /// Creates an Upload.
    /// </summary>
    /// <param name="model">The model representing the upload.</param>
    /// <returns>Nothing.</returns>
    [HttpPost]
    [Route("")]
    [Authorize]
    public async Task<IHttpActionResult> Post(UploadBindingViewModel model)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        var upload = new Upload
        {
            Id = model.Id,
            Name = model.Name,
            Url = model.Url
        };

        try
        {
            this.service.Create(upload);

            await this.UnitOfWork.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }

        return Ok(upload.Id);
    }

    // DELETE api/uploads
    /// <summary>
    /// Deletes an upload.
    /// </summary>
    /// <param name="id">The id of the upload.</param>
    /// <returns></returns>
    [HttpDelete]
    [Route("{id:int}")]
    [Authorize]
    public async Task<IHttpActionResult> Delete(int id)
    {
        try
        {
            await this.service.RemoveAsync(id);

            await this.UnitOfWork.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }

        return Ok();
    }
}

At the bottom of the controller, I have the delete function which currently looks like this:

// DELETE api/uploads
/// <summary>
/// Deletes an upload.
/// </summary>
/// <param name="id">The id of the upload.</param>
/// <returns></returns>
[HttpDelete]
[Route("{id:int}")]
[Authorize]
public async Task<IHttpActionResult> Delete(int id)
{
    try
    {
        await this.service.RemoveAsync(id);

        await this.UnitOfWork.SaveChangesAsync();
    }
    catch (Exception ex)
    {
        return InternalServerError(ex);
    }

    return Ok();
}

now, if I call this from jQuery like this:

$.ajax({
    type: "DELETE",
    url: uploadUrl + "?id=" + self.id()
}).fail(function () {
    toastr.error("Failed to remove the upload from our system.");
});

or like this:

$.ajax({
    type: "DELETE",
    url: uploadUrl,
    data: { id: self.id() }
}).fail(function () {
    toastr.error("Failed to remove the upload from our system.");
});

I get this error:

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

but, if I change my controller method to this:

// DELETE api/uploads
/// <summary>
/// Deletes an upload.
/// </summary>
/// <param name="id">The id of the upload.</param>
/// <returns></returns>
[HttpDelete]
[Route("")]
[Authorize]
public async Task<IHttpActionResult> Delete(int id)
{
    try
    {
        await this.service.RemoveAsync(id);

        await this.UnitOfWork.SaveChangesAsync();
    }
    catch (Exception ex)
    {
        return InternalServerError(ex);
    }

    return Ok();
}

and then consume it like this:

$.ajax({
    type: "DELETE",
    url: uploadUrl + "?id=" + self.id()
}).fail(function () {
    toastr.error("Failed to remove the upload from our system.");
});

it works, but if I change to this:

$.ajax({
    type: "DELETE",
    url: uploadUrl,
    data: { id: self.id() }
}).fail(function () {
    toastr.error("Failed to remove the upload from our system.");
});

I get the same error message as before.

I would like to call the web api using the latter statement and keep the {id:int} declaration in my controller. Could someone tell me what I am doing wrong?


Solution

  • I can answer this now. The DELETE HttpMethod does not accept body data, it can only accept query string parameters which is why the appended URL method worked.

    Also, the default parameter for a Web API method is already an int so the Route should be specified as [Route("")]. This will mean that the actual path for the call should be /Api/Uploads using the DELETE HttpMethod.

    I hope that has cleared this up for anyone else having the same issue.