Upload file using PUT verb in ASP.Net Web Api 2

12,119

After a few tests it seems the server-side code I posted as an example is correct. Here is an example, stripped out of any authentication/authorization/error handling code:

[HttpPut]
[Route(@"api/storage/{*resourcePath?}")]
public async Task<HttpResponseMessage> PutFile(string resourcePath = "")
{
    // Extract data from request
    Stream fileContent = await this.Request.Content.ReadAsStreamAsync();
    MediaTypeHeaderValue contentTypeHeader = this.Request.Content.Headers.ContentType;
    string contentType =
        contentTypeHeader != null ? contentTypeHeader.MediaType : "application/octet-stream";

    // Save the file to the underlying storage
    bool isNew = await this._dal.SaveFile(resourcePath, contentType, fileContent);

    // Return appropriate HTTP status code
    if (isNew)
    {
        return this.Request.CreateResponse(HttpStatusCode.Created);
    }
    else
    {
        return this.Request.CreateResponse(HttpStatusCode.OK);
    }
}

A simple console app is enough to test it (using Web Api client libraries):

using (var fileContent = new FileStream(@"C:\temp\testfile.txt", FileMode.Open))
using (var client = new HttpClient())
{
    var content = new StreamContent(fileContent);
    content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
    client.BaseAddress = new Uri("http://localhost:81");
    HttpResponseMessage response =
        await client.PutAsync(@"/api/storage/testfile.txt", content);
}

Edit 2018-05-09:

As stated in this comment, if you plan to support file names with an extension ({filename}.{extension}) without forcing the client to append a trailing slash, you will need to modify your web.config to bind IIS to your web api application for these file types, as by default IIS will use a static file handler to handle what looks like file names (i.e. URLs with the last path segment containing a dot). My system.webServer section looks like:

<system.webServer>
    <handlers>
      <!-- Clear all handlers, prevents executing code file extensions or returning any file contents. -->
      <clear />
      <!-- Favicon static handler. -->
      <add name="FaviconStaticFile" path="/favicon.ico" verb="GET" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
      <!-- By default, only map extensionless URLs to ASP.NET -->
      <!-- (the "*." handler mapping is a special syntax that matches extensionless URLs) -->
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <!-- API endpoints must handle path segments including a dot -->
      <add name="ExtensionIncludedUrlHandler-Integrated-4.0" path="/api/storage/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
</system.webServer>

Note that some file names will not be possible because of various limitations. For example you can't name a path segment . or .. because the RFC requires to replace it, Azure hosting services won't allow a colon as the last character of a path segment, and IIS forbids a set of characters by default.

You may also want to increase IIS / ASP.NET file upload size limits:

<!-- Path specific settings -->
<location path="api/storage">
  <system.web>
    <httpRuntime maxRequestLength="200000000" />
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="200000000" />
      </requestFiltering>
    </security>
    </system.webServer>
</location>
Share:
12,119
Maxime Rossini
Author by

Maxime Rossini

Updated on July 25, 2022

Comments

  • Maxime Rossini
    Maxime Rossini almost 2 years

    I would like to expose an ASP.Net Web Api 2 action using the HTTP PUT verb to upload files. This is coherent with our REST model as the API represents a remote file system (similar to WebDAV, but really simplified), so the client chooses the resource names (thus PUT is ideal and POST is not a logical choice).

    The Web Api documentation describes how to upload files using multipart/form-data forms, but does not describe how to do it using PUT methods.

    What would you use to test such an API (HTML multipart forms don't allow PUT verbs)? Would the server implementation look like the multipart implementation described in the web api documentation (using the MultipartStreamProvider), or should it look like this:

    [HttpPut]
    public async Task<HttpResponseMessage> PutFile(string resourcePath)
    {
        Stream fileContent = await this.Request.Content.ReadAsStreamAsync();
        bool isNew = await this._storageManager.UploadFile(resourcePath, fileContent);
        if (isNew)
        {
            return this.Request.CreateResponse(HttpStatusCode.Created);
        }
        else
        {
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }
    }
    
  • James
    James over 7 years
    It would be interesting to see how to write a proper unit test for this, rather than relying on running an HTTP Server.
  • Simon Tewsi
    Simon Tewsi over 7 years
    I had a lot of trouble getting this to work as I kept getting 404 errors. Turns out you may need to change the web.config to accept file names of the form {filename}.{extension}. See this Stackoverflow question for the details: stackoverflow.com/questions/20998816/…