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);
}
}
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With